From 83abc0033902348d2bf7562ab6e09f1ac35dbc04 Mon Sep 17 00:00:00 2001
From: Lan_zhijiang
Date: Mon, 21 Sep 2026 08:32:06 +0800
Subject: [PATCH 1/5] =?UTF-8?q?chore(docs):=20=E6=9B=B4=E6=96=B0=E5=85=B1?=
=?UTF-8?q?=E4=BA=AB=E6=8F=92=E4=BB=B6=E6=96=87=E6=A1=A3=E6=A8=A1=E6=9D=BF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 固定到支持静态文档构建与发布协议的 Hub 提交
---
docs/_shared | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/_shared b/docs/_shared
index 5d0d8d7..5aa97e6 160000
--- a/docs/_shared
+++ b/docs/_shared
@@ -1 +1 @@
-Subproject commit 5d0d8d7facfe68227c5bbc93361ba77f4573e7fa
+Subproject commit 5aa97e6243dce02dd27c5f58eda345be2ba47297
From f29a407ae02980b221e9419bb38421aa95805771 Mon Sep 17 00:00:00 2001
From: Lan_zhijiang
Date: Mon, 21 Sep 2026 08:33:22 +0800
Subject: [PATCH 2/5] =?UTF-8?q?feat(memos):=20=E6=8F=90=E4=BE=9B=E5=8F=AF?=
=?UTF-8?q?=E8=A7=86=E5=8C=96=E8=BF=9E=E6=8E=A5=E5=87=86=E5=A4=87=E5=90=91?=
=?UTF-8?q?=E5=AF=BC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增 Memos Module Federation 扩展及中英文安装文档
- 通过可视化流程生成并复用连接 PAT,支持中断恢复
- 将包结构和工作区约束纳入现有检查
---
.changeset/memos-connection-setup.md | 5 +
.../module-federation/.vitepress/config.mts | 9 +
.../.vitepress/theme/index.ts | 1 +
.../memos/docs/module-federation/index.md | 32 ++
extensions/memos/index.html | 12 +
extensions/memos/package.json | 45 +++
extensions/memos/src/DevApp.vue | 7 +
extensions/memos/src/MemosSetup.vue | 339 ++++++++++++++++++
extensions/memos/src/index.ts | 5 +
extensions/memos/src/main.ts | 10 +
extensions/memos/src/setup.ts | 71 ++++
extensions/memos/tsconfig.json | 14 +
extensions/memos/vite.config.ts | 21 ++
pnpm-lock.yaml | 46 +++
scripts/check-package-contract.mjs | 1 +
scripts/check-workspace-contract.mjs | 6 +
16 files changed, 624 insertions(+)
create mode 100644 .changeset/memos-connection-setup.md
create mode 100644 extensions/memos/docs/module-federation/.vitepress/config.mts
create mode 100644 extensions/memos/docs/module-federation/.vitepress/theme/index.ts
create mode 100644 extensions/memos/docs/module-federation/index.md
create mode 100644 extensions/memos/index.html
create mode 100644 extensions/memos/package.json
create mode 100644 extensions/memos/src/DevApp.vue
create mode 100644 extensions/memos/src/MemosSetup.vue
create mode 100644 extensions/memos/src/index.ts
create mode 100644 extensions/memos/src/main.ts
create mode 100644 extensions/memos/src/setup.ts
create mode 100644 extensions/memos/tsconfig.json
create mode 100644 extensions/memos/vite.config.ts
diff --git a/.changeset/memos-connection-setup.md b/.changeset/memos-connection-setup.md
new file mode 100644
index 0000000..4f23174
--- /dev/null
+++ b/.changeset/memos-connection-setup.md
@@ -0,0 +1,5 @@
+---
+'@inkcre/ext-memos': minor
+---
+
+新增 Memos 可视化连接准备向导,生成或复用 PAT、启用 Core 并提供客户端连接信息;首次 MF 发行对齐已有 Python 0.2.0。
diff --git a/extensions/memos/docs/module-federation/.vitepress/config.mts b/extensions/memos/docs/module-federation/.vitepress/config.mts
new file mode 100644
index 0000000..da0a092
--- /dev/null
+++ b/extensions/memos/docs/module-federation/.vitepress/config.mts
@@ -0,0 +1,9 @@
+import { extensionDocs } from '../../../../../docs/_shared/website/extension-docs/preset.mts'
+
+export default extensionDocs({
+ name: 'inkcre/memos',
+ title: 'Memos Web Setup',
+ scope: 'module-federation',
+ description: 'Prepare a Memos client connection from the Web interface.',
+ sidebar: [{ text: 'Connection Setup', link: '/' }],
+})
diff --git a/extensions/memos/docs/module-federation/.vitepress/theme/index.ts b/extensions/memos/docs/module-federation/.vitepress/theme/index.ts
new file mode 100644
index 0000000..e23b068
--- /dev/null
+++ b/extensions/memos/docs/module-federation/.vitepress/theme/index.ts
@@ -0,0 +1 @@
+export { default } from '../../../../../../docs/_shared/website/.vitepress/theme/index'
diff --git a/extensions/memos/docs/module-federation/index.md b/extensions/memos/docs/module-federation/index.md
new file mode 100644
index 0000000..3e7e75c
--- /dev/null
+++ b/extensions/memos/docs/module-federation/index.md
@@ -0,0 +1,32 @@
+---
+description: Use the Memos Web Extension to prepare connection details for a compatible app.
+---
+
+# Prepare your client connection
+
+The Memos Web Extension provides a connection-preparation interface. It does not create a Source,
+schedule collection, or write a test note. The Core and browser must use matching Distributions of
+the same installed Extension Release.
+
+1. In **Extensions**, enable Memos in this browser, then open **Setup**.
+2. Choose the online Core providing the service. A single eligible Core is selected automatically.
+3. If its address is missing, set the Core's Public HTTP Base URL in **Clients → Config** and
+ select **Refresh status**. The address must be reachable from your client device.
+4. If you have no saved PAT, select **Generate PAT** or enter one you already prepared, then select
+ **Save PAT and enable Memos**. It must have the `memos_pat_` prefix and 32 ASCII letters or digits.
+5. If a PAT already exists, the page reuses it. Select **Enable Memos on this Core** if necessary.
+6. Copy **Server URL** and **Personal Access Token** into your compatible client's sign-in screen.
+ Keep `/memos` in the URL; do not add `/api/v1` or use the PostgREST address.
+
+An already configured connection opens directly on these connection details. Opening or refreshing
+the page never generates or rotates a PAT. The result says that the information is ready; verify
+the actual sign-in in the external app. The established compatibility baseline is MoeMemos Android
+2.0.4, not every Memos version.
+
+Saving and enabling are separate operations. If saving fails, the draft remains available. If
+enablement fails after saving, retry with that saved PAT. An uncertain response first requires
+reading the actual saved state; do not generate another credential just to retry. If copying is
+unavailable, reveal the token and manually select it. Hide it again before sharing a screenshot.
+
+PAT replacement and revocation remain explicit Extension Config operations. Replacing a token
+requires updating every app that used the old token. This setup interface never does it silently.
diff --git a/extensions/memos/index.html b/extensions/memos/index.html
new file mode 100644
index 0000000..f9c1874
--- /dev/null
+++ b/extensions/memos/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Memos Extension
+
+
+
+
+
+
diff --git a/extensions/memos/package.json b/extensions/memos/package.json
new file mode 100644
index 0000000..699ed1a
--- /dev/null
+++ b/extensions/memos/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "@inkcre/ext-memos",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "type-check": "vue-tsc --noEmit"
+ },
+ "dependencies": {
+ "@inkcre/core": "workspace:*"
+ },
+ "devDependencies": {
+ "@inkcre/ext-dev-utils": "workspace:*",
+ "@inkcre/ui-web": "2.0.1",
+ "@module-federation/vite": "^1.1.0",
+ "@vitejs/plugin-vue": "^6.0.1",
+ "@vue/tsconfig": "^0.7.0",
+ "typescript": "catalog:",
+ "vite": "^7.0.6",
+ "vue-tsc": "catalog:"
+ },
+ "peerDependencies": {
+ "@vueuse/core": "^14.0.0",
+ "pinia": "^3.0.0",
+ "vue": "^3.5.25",
+ "vue-router": "^4.5.0",
+ "zod": "^4.1.0"
+ },
+ "inkcre": {
+ "name": "inkcre/memos",
+ "nickname": "Memos",
+ "module_federation": {
+ "host_sdk": "@inkcre/core",
+ "host_sdk_version": ">=0.3.0 <0.4.0"
+ }
+ },
+ "intent": {
+ "skills": [
+ "@inkcre/ui-web"
+ ]
+ }
+}
diff --git a/extensions/memos/src/DevApp.vue b/extensions/memos/src/DevApp.vue
new file mode 100644
index 0000000..24f0141
--- /dev/null
+++ b/extensions/memos/src/DevApp.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/extensions/memos/src/MemosSetup.vue b/extensions/memos/src/MemosSetup.vue
new file mode 100644
index 0000000..f74fe2b
--- /dev/null
+++ b/extensions/memos/src/MemosSetup.vue
@@ -0,0 +1,339 @@
+
+
+
+
+
+ {{
+ tr(
+ 'Connect a Memos-compatible app to this InKCre instance. No separate Memos server is needed.',
+ '将兼容 Memos 的 App 连接到此 InKCre 实例,无需另建 Memos 服务器。'
+ )
+ }}
+
+ {{
+ tr(
+ 'Set this Core’s Public HTTP Base URL in Clients → Config, then refresh. Use the externally reachable Core address, not PostgREST.',
+ '请在 Clients → Config 配置此 Core 的公共 HTTP 基址,再刷新。填写外部可访问的 Core 地址,不是 PostgREST 地址。'
+ )
+ }}
+
+
+
+
+ {{
+ savedToken
+ ? tr(
+ 'Your saved PAT will be reused. This does not replace credentials in existing apps.',
+ '将复用已保存的 PAT,不会替换已有 App 的凭据。'
+ )
+ : tr(
+ 'Generate a PAT or enter your own. It is not usable until saved.',
+ '生成一枚 PAT,或输入自备 PAT。保存前只是草稿,尚不可使用。'
+ )
+ }}
+
+
+
+
+
+
+
+
+
{{ tr('Connection details are ready', '连接信息已准备好') }}
+
+ {{
+ tr(
+ 'Paste these values into your app. This page cannot confirm that the app has connected.',
+ '请将下面两项填入 App。本页不能确认外部 App 已成功连接。'
+ )
+ }}
+
+
+
+
+
+
+
+
+
+
+ {{
+ tr(
+ 'Open your client’s server sign-in screen. The tested compatibility baseline is MoeMemos Android 2.0.4.',
+ '打开客户端的服务器登录界面。已验证的兼容基线是 MoeMemos Android 2.0.4。'
+ )
+ }}
+
+
+ {{
+ tr(
+ 'Paste the Server URL exactly as shown, including /memos. Paste the PAT into its token field, then sign in.',
+ '原样填写 Server URL(包括 /memos),将 PAT 填入 token 栏,然后登录。'
+ )
+ }}
+
+
+ {{
+ tr(
+ 'If sign-in fails, check that your device can reach this Core and that Memos remains enabled.',
+ '如果登录失败,请检查设备能否访问此 Core,以及 Memos 是否仍已启用。'
+ )
+ }}
+
+
+
+ {{
+ tr(
+ 'To replace or revoke the PAT, use Extension Config. Replacement disconnects every app using the old PAT.',
+ '如需替换或撤销 PAT,请使用 Extension Config。替换后,所有使用旧 PAT 的 App 都需更新凭据。'
+ )
+ }}
+
import { computed, inject, onMounted, onUnmounted, ref, shallowRef } from 'vue'
import { InkButton, InkDropdown, InkForm, InkInput, INK_I18N_KEY } from '@inkcre/ui-web'
-import { ExtensionModel, type InstalledExtension, type Peer } from '@inkcre/core'
+import {
+ ExtensionModel,
+ PeerOutcomeUnknown,
+ type InstalledExtension,
+ type Peer,
+} from '@inkcre/core'
+import { manageExtensionOnPeer } from '@inkcre/extension-runtime-client-web'
import {
extensionName,
generateToken,
- manageMemos,
+ MemosConnectionError,
personalAccessToken,
+ readHelpUrl,
readSetup,
- serverUrl,
- tokenPattern,
+ readServerUrl,
} from './setup'
const emit = defineEmits<{ close: [] }>()
@@ -20,41 +26,77 @@ const extension = shallowRef(null)
const peers = shallowRef([])
const selected = ref('')
const draft = ref('')
+const address = ref('')
const busy = ref(false)
const loaded = ref(false)
+const requiresRefresh = ref(false)
const reveal = ref(false)
const error = ref('')
const notice = ref('')
+const helpUrl = ref(null)
+const helpStatus = ref<'loading' | 'available' | 'missing' | 'unavailable'>('loading')
let mounted = true
onUnmounted(() => {
mounted = false
})
const peer = computed(() => peers.value.find((candidate) => candidate.id === selected.value))
-const address = computed(() => (peer.value ? serverUrl(peer.value) : null))
const savedToken = computed(() => (extension.value ? personalAccessToken(extension.value) : null))
const enabled = computed(() => extension.value?.enabled.includes(selected.value) ?? false)
const ready = computed(() => loaded.value && address.value && savedToken.value && enabled.value)
const options = computed(() =>
peers.value.map((candidate) => ({ label: candidate.name, value: candidate.id }))
)
-const validDraft = computed(() => tokenPattern.test(draft.value))
+
+async function loadHelp(version: string) {
+ helpUrl.value = null
+ helpStatus.value = 'loading'
+ try {
+ const url = await readHelpUrl(version)
+ if (!mounted || extension.value?.version !== version) return
+ helpUrl.value = url
+ helpStatus.value = url ? 'available' : 'missing'
+ } catch {
+ if (mounted && extension.value?.version === version) helpStatus.value = 'unavailable'
+ }
+}
+
+async function loadAddress() {
+ address.value = ''
+ if (!peer.value || !savedToken.value || !enabled.value) return
+ try {
+ address.value = await readServerUrl(peer.value.id)
+ } catch (cause) {
+ error.value =
+ cause instanceof MemosConnectionError && cause.status === 409
+ ? tr(
+ 'PAT saved and Memos enabled. Set this Core’s Public HTTP Base URL in Clients → Config, then refresh.',
+ 'PAT 已保存,Memos 已启用。请在 Clients → Config 设置此 Core 的公共 HTTP 基址,再刷新。'
+ )
+ : tr(
+ 'PAT saved and Memos enabled, but its address is unavailable. Check Core and refresh.',
+ 'PAT 已保存,Memos 已启用,但暂时无法读取服务地址。请检查 Core 后刷新。'
+ )
+ }
+}
async function refresh() {
if (busy.value) return
busy.value = true
error.value = ''
notice.value = ''
+ address.value = ''
reveal.value = false
try {
const current = await readSetup()
if (!mounted) return
extension.value = current.extension
+ void loadHelp(current.extension.version)
peers.value = current.peers
- if (!current.peers.some((candidate) => candidate.id === selected.value)) {
- selected.value = current.peers.length === 1 ? current.peers[0]!.id : ''
- }
+ if (!selected.value && current.peers.length === 1) selected.value = current.peers[0]!.id
loaded.value = true
+ requiresRefresh.value = false
+ await loadAddress()
} catch {
loaded.value = false
error.value = tr(
@@ -66,63 +108,72 @@ async function refresh() {
}
}
+async function selectPeer() {
+ reveal.value = false
+ error.value = ''
+ notice.value = ''
+ busy.value = true
+ try {
+ await loadAddress()
+ } finally {
+ busy.value = false
+ }
+}
+
async function prepare() {
- if (busy.value || !loaded.value || !peer.value || !address.value) return
- const target = peer.value
- const previousToken = savedToken.value
- const token = previousToken ?? draft.value
- if (!tokenPattern.test(token)) return
+ if (busy.value || !loaded.value || !peer.value || requiresRefresh.value) return
+ const target = peer.value.id
busy.value = true
error.value = ''
notice.value = ''
- let phase: 'save' | 'enable' = 'save'
+ address.value = ''
+ let phase: 'read' | 'save' | 'enable' = 'read'
try {
- // Recheck after user deliberation. A newly configured PAT must not be silently replaced.
const current = await ExtensionModel.get(extensionName)
if (!current) throw new Error('Memos is no longer installed.')
extension.value = current
- if (personalAccessToken(current) !== previousToken) {
- error.value = tr(
- 'Configuration changed elsewhere. Review the current PAT before continuing.',
- '配置已在其他位置变更。请核对当前 PAT 后继续。'
- )
- return
- }
- if (!previousToken) {
- extension.value = await manageMemos(target, {
+ if (!personalAccessToken(current)) {
+ phase = 'save'
+ draft.value ||= generateToken()
+ extension.value = await manageExtensionOnPeer(target, {
action: 'patch_config',
extension: extensionName,
- patch: { personal_access_token: token },
+ patch: { personal_access_token: draft.value },
})
- if (personalAccessToken(extension.value) !== token) throw new Error('PAT readback differs.')
}
phase = 'enable'
- if (!extension.value.enabled.includes(target.id)) {
- extension.value = await manageMemos(target, { action: 'enable', extension: extensionName })
+ if (!extension.value.enabled.includes(target)) {
+ extension.value = await manageExtensionOnPeer(target, {
+ action: 'enable',
+ extension: extensionName,
+ })
}
draft.value = ''
- } catch {
- // A lost response is not evidence of failure. Read canonical state before allowing a retry.
- try {
- const current = await ExtensionModel.get(extensionName)
- if (!current) throw new Error('Memos is no longer installed.')
- extension.value = current
+ await loadAddress()
+ } catch (cause) {
+ if (cause instanceof PeerOutcomeUnknown) {
+ requiresRefresh.value = true
error.value =
phase === 'enable'
? tr(
- 'The PAT is saved. Check Core availability, then retry enabling with the same PAT.',
- 'PAT 已保存。请检查 Core 是否可用,再用同一枚 PAT 重试启用。'
+ 'PAT saved. Enablement outcome is unknown; refresh before continuing.',
+ 'PAT 已保存,启用结果未知;请先刷新再继续。'
)
: tr(
- 'The save was not confirmed. Current saved state has been refreshed; your draft is retained.',
- '保存未获确认。已刷新当前保存状态,并保留输入草稿。'
+ 'Save outcome is unknown; refresh before continuing.',
+ '保存结果未知;请先刷新再继续。'
+ )
+ } else {
+ error.value =
+ phase === 'enable'
+ ? tr(
+ 'PAT saved, but Memos could not be enabled. Check Core, then try again.',
+ 'PAT 已保存,但 Memos 未能启用。请检查 Core 后重试。'
+ )
+ : tr(
+ 'Connection preparation failed. Check Core and try again.',
+ '连接准备失败,请检查 Core 后重试。'
)
- } catch {
- loaded.value = false
- error.value = tr(
- 'The result is unknown. Refresh to read saved state before retrying; your draft is retained.',
- '结果未知。请先刷新已保存状态再重试;输入草稿已保留。'
- )
}
} finally {
busy.value = false
@@ -152,14 +203,10 @@ onMounted(refresh)
{{
- tr(
- 'Connect a Memos-compatible app to this InKCre instance. No separate Memos server is needed.',
- '将兼容 Memos 的 App 连接到此 InKCre 实例,无需另建 Memos 服务器。'
- )
+ tr('Connect a Memos-compatible app to this instance.', '将兼容 Memos 的 App 连接到此实例。')
}}
{{ error }}
-
{{
@@ -171,141 +218,67 @@ onMounted(refresh)
-
{{ tr('Core', 'Core') }}: {{ peer?.name }}
-
- {{
- tr(
- 'Set this Core’s Public HTTP Base URL in Clients → Config, then refresh. Use the externally reachable Core address, not PostgREST.',
- '请在 Clients → Config 配置此 Core 的公共 HTTP 基址,再刷新。填写外部可访问的 Core 地址,不是 PostgREST 地址。'
- )
- }}
-
-
-
-
- {{
- savedToken
- ? tr(
- 'Your saved PAT will be reused. This does not replace credentials in existing apps.',
- '将复用已保存的 PAT,不会替换已有 App 的凭据。'
- )
- : tr(
- 'Generate a PAT or enter your own. It is not usable until saved.',
- '生成一枚 PAT,或输入自备 PAT。保存前只是草稿,尚不可使用。'
- )
- }}
-
-
-
-
-
-
-
-
-
{{ tr('Connection details are ready', '连接信息已准备好') }}
-
- {{
- tr(
- 'Paste these values into your app. This page cannot confirm that the app has connected.',
- '请将下面两项填入 App。本页不能确认外部 App 已成功连接。'
- )
- }}
-
-
+
Core: {{ peer.name }}
+
+
+
{{ tr('Connection details are ready', '连接信息已准备好') }}
+
+
+
+
-
-
-
-
-
-
-
- {{
- tr(
- 'Open your client’s server sign-in screen. The tested compatibility baseline is MoeMemos Android 2.0.4.',
- '打开客户端的服务器登录界面。已验证的兼容基线是 MoeMemos Android 2.0.4。'
- )
- }}
-
-
- {{
- tr(
- 'Paste the Server URL exactly as shown, including /memos. Paste the PAT into its token field, then sign in.',
- '原样填写 Server URL(包括 /memos),将 PAT 填入 token 栏,然后登录。'
- )
- }}
-
-
- {{
- tr(
- 'If sign-in fails, check that your device can reach this Core and that Memos remains enabled.',
- '如果登录失败,请检查设备能否访问此 Core,以及 Memos 是否仍已启用。'
- )
- }}
-
-
-
- {{
- tr(
- 'To replace or revoke the PAT, use Extension Config. Replacement disconnects every app using the old PAT.',
- '如需替换或撤销 PAT,请使用 Extension Config。替换后,所有使用旧 PAT 的 App 都需更新凭据。'
- )
- }}
-
-
+
+
+
+ {{
+ tr(
+ 'Paste both values into your app. Keep the PAT private.',
+ '将两项信息填入 App,并妥善保管 PAT。'
+ )
+ }}
+