From a2862334d217c0e07cfef9eedfc9b5861e7c7780 Mon Sep 17 00:00:00 2001 From: "shi.changliang" Date: Mon, 15 Jun 2026 12:18:32 +0800 Subject: [PATCH] refactor: remove Obsync compatibility naming --- README.md | 4 + .../assets/settings-s3-endpoint-sanitized.svg | 2 +- docs/obsidian-oss-sync-mvp.md | 8 +- src/core/ids.ts | 2 +- src/core/paths.test.ts | 10 +- src/core/paths.ts | 2 +- src/main.test.ts | 14 +- src/main.ts | 50 +++--- src/settings/SettingsApp.vue | 140 ++++++++-------- src/store/s3.test.ts | 22 +-- src/styles.scss | 154 +++++++++--------- 11 files changed, 206 insertions(+), 202 deletions(-) diff --git a/README.md b/README.md index 556913e..ba96e74 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,16 @@ Cohere is a community plugin for syncing vault files through OSS / S3-compatible object storage. It supports manual sync, automatic sync, deletion sync, conflict copies, optional empty directory sync, and connection configuration import/export. +Cohere works with S3-compatible providers such as Cloudflare R2, Aliyun OSS, Tencent COS, Qiniu Cloud Kodo, MinIO, and other compatible object storage services. + The plugin ID is `cohere`. ## 中文说明 Cohere 用于通过 OSS / S3 兼容对象存储同步当前 vault 文件。 +Cohere 适合希望通过对象存储实现笔记同步、多端同步、私有云同步的用户,支持 S3 / OSS 兼容服务,例如阿里云 OSS、腾讯云 COS、七牛云、Cloudflare R2、MinIO 等。 + ![Cohere banner](docs/assets/cohere-banner.webp) ## 当前能力 diff --git a/docs/assets/settings-s3-endpoint-sanitized.svg b/docs/assets/settings-s3-endpoint-sanitized.svg index 2a5ceea..38c8f79 100644 --- a/docs/assets/settings-s3-endpoint-sanitized.svg +++ b/docs/assets/settings-s3-endpoint-sanitized.svg @@ -2,7 +2,7 @@ - Obsync 设置 + Cohere 设置 通过对象存储同步当前 Obsidian 仓库。 diff --git a/docs/obsidian-oss-sync-mvp.md b/docs/obsidian-oss-sync-mvp.md index e3febf9..505da15 100644 --- a/docs/obsidian-oss-sync-mvp.md +++ b/docs/obsidian-oss-sync-mvp.md @@ -52,7 +52,7 @@ accountKey = normalize(user input, default: "default") vaultKey = normalize(user input, default: normalize(app.vault.getName())) vaultId = "vlt_" + base32url( - sha256("obsync-vault-v1:" + accountKey + ":" + vaultKey) + sha256("cohere-vault-v1:" + accountKey + ":" + vaultKey) ).slice(0, 26) ``` @@ -113,7 +113,7 @@ deviceId = "dev_" + base32url(randomBytes(16)).slice(0, 26) "schemaVersion": 1, "endpoint": "https://oss-cn-example.aliyuncs.com", "bucket": "my-obsidian-sync", - "rootPrefix": "obsync/v1", + "rootPrefix": "cohere/v1", "accountKey": "default", "vaultKey": "personal-notes", "vaultId": "vlt_7K4N9J2Q8X5M3P1A" @@ -138,13 +138,13 @@ oss:////vaults// 默认 `rootPrefix`: ```text -obsync/v1 +cohere/v1 ``` 完整结构: ```text -obsync/ +cohere/ v1/ vaults/ / diff --git a/src/core/ids.ts b/src/core/ids.ts index 97dcf0b..b3a9e6b 100644 --- a/src/core/ids.ts +++ b/src/core/ids.ts @@ -11,7 +11,7 @@ export function normalizeKey(value: string): string { } export async function createVaultId(accountKey: string, vaultKey: string): Promise { - const input = `obsync-vault-v1:${normalizeKey(accountKey)}:${normalizeKey(vaultKey)}`; + const input = `cohere-vault-v1:${normalizeKey(accountKey)}:${normalizeKey(vaultKey)}`; const bytes = new TextEncoder().encode(input); const hash = await crypto.subtle.digest("SHA-256", bytes); return `vlt_${base32Url(new Uint8Array(hash)).slice(0, 26)}`; diff --git a/src/core/paths.test.ts b/src/core/paths.test.ts index 547c90b..0fe5728 100644 --- a/src/core/paths.test.ts +++ b/src/core/paths.test.ts @@ -4,14 +4,14 @@ import { createRemoteLayout } from "./paths"; describe("remote layout", () => { test("builds normalized object keys under a vault prefix", () => { const layout = createRemoteLayout({ - rootPrefix: "/obsync/v1/", + rootPrefix: "/cohere/v1/", vaultId: "vlt_TEST", }); - expect(layout.manifestKey).toBe("obsync/v1/vaults/vlt_TEST/manifest.json"); - expect(layout.fileKey("notes/today.md")).toBe("obsync/v1/vaults/vlt_TEST/files/notes/today.md"); - expect(layout.fileKey("/attachments/image.png")).toBe("obsync/v1/vaults/vlt_TEST/files/attachments/image.png"); - expect(layout.lockKey).toBe("obsync/v1/vaults/vlt_TEST/locks/sync.lock"); + expect(layout.manifestKey).toBe("cohere/v1/vaults/vlt_TEST/manifest.json"); + expect(layout.fileKey("notes/today.md")).toBe("cohere/v1/vaults/vlt_TEST/files/notes/today.md"); + expect(layout.fileKey("/attachments/image.png")).toBe("cohere/v1/vaults/vlt_TEST/files/attachments/image.png"); + expect(layout.lockKey).toBe("cohere/v1/vaults/vlt_TEST/locks/sync.lock"); }); }); diff --git a/src/core/paths.ts b/src/core/paths.ts index a4c3063..73e3362 100644 --- a/src/core/paths.ts +++ b/src/core/paths.ts @@ -12,7 +12,7 @@ export interface RemoteLayout { } export function createRemoteLayout(input: RemoteLayoutInput): RemoteLayout { - const rootPrefix = trimSlashes(input.rootPrefix || "obsync/v1"); + const rootPrefix = trimSlashes(input.rootPrefix || "cohere/v1"); const vaultPrefix = `${rootPrefix}/vaults/${input.vaultId}`; return { diff --git a/src/main.test.ts b/src/main.test.ts index 70770e3..5b7f066 100644 --- a/src/main.test.ts +++ b/src/main.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { TFile, TFolder } from "obsidian"; -import ObsyncPlugin from "./main"; +import CoherePlugin from "./main"; import { ObsidianVaultIO } from "./vault-io"; vi.mock("obsidian", () => ({ @@ -144,7 +144,7 @@ describe("connection config import and export", () => { const plugin = createPlugin({ accessKeyId: "AKIA_TEST", secretAccessKey: "SECRET_TEST", - rootPrefix: "obsync/v2", + rootPrefix: "cohere/v2", accountKey: "team", vaultKey: "notes", vaultId: "vlt_notes", @@ -156,7 +156,7 @@ describe("connection config import and export", () => { bucket: "vault", addressingStyle: "auto", region: "auto", - rootPrefix: "obsync/v2", + rootPrefix: "cohere/v2", accountKey: "team", vaultKey: "notes", vaultId: "vlt_notes", @@ -178,7 +178,7 @@ describe("connection config import and export", () => { bucket: "shared-vault", addressingStyle: "virtual-hosted", region: "auto", - rootPrefix: "obsync/v2", + rootPrefix: "cohere/v2", accountKey: "team", vaultKey: "product", vaultId: "ignored-remote-id", @@ -189,7 +189,7 @@ describe("connection config import and export", () => { bucket: "shared-vault", addressingStyle: "virtual-hosted", region: "auto", - rootPrefix: "obsync/v2", + rootPrefix: "cohere/v2", accountKey: "team", vaultKey: "product", accessKeyId: "existing-key", @@ -261,7 +261,7 @@ describe("vault IO compatibility", () => { }); function createPlugin(settings: Partial = {}): TestPlugin { - const plugin = Object.create(ObsyncPlugin.prototype) as TestPlugin; + const plugin = Object.create(CoherePlugin.prototype) as TestPlugin; plugin.app = { vault: { getName: () => "ichaly" } }; plugin.settings = { endpoint: "https://example.com", @@ -270,7 +270,7 @@ function createPlugin(settings: Partial = {}): TestPlugin { region: "auto", accessKeyId: "key", secretAccessKey: "secret", - rootPrefix: "obsync/v1", + rootPrefix: "cohere/v1", accountKey: "default", vaultKey: "vault", vaultId: "vlt_test", diff --git a/src/main.ts b/src/main.ts index 3d4293b..0d1c0de 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,7 +7,7 @@ import { releaseDeletedContent, syncOnce, type LocalSyncState } from "./sync/eng import { S3ObjectStore, type S3AddressingStyle } from "./store/s3"; import { ObsidianVaultIO } from "./vault-io"; -interface ObsyncSettings { +interface CohereSettings { endpoint: string; bucket: string; addressingStyle: S3AddressingStyle; @@ -40,14 +40,14 @@ interface ConnectionConfig { secretAccessKey?: string; } -const DEFAULT_SETTINGS: ObsyncSettings = { +const DEFAULT_SETTINGS: CohereSettings = { endpoint: "", bucket: "", addressingStyle: "auto", region: "", accessKeyId: "", secretAccessKey: "", - rootPrefix: "obsync/v2", + rootPrefix: "cohere/v1", accountKey: "default", vaultKey: "", vaultId: "", @@ -66,8 +66,8 @@ const FILE_EVENT_SUPPRESSION_MS = 1_000; type SyncTrigger = "manual" | "auto"; -export default class ObsyncPlugin extends Plugin { - settings: ObsyncSettings = DEFAULT_SETTINGS; +export default class CoherePlugin extends Plugin { + settings: CohereSettings = DEFAULT_SETTINGS; private autoSyncTimer: number | null = null; private autoSyncRunning = false; private autoSyncQueued = false; @@ -80,7 +80,7 @@ export default class ObsyncPlugin extends Plugin { async onload(): Promise { await this.loadSettings(); this.statusBarItem = this.addStatusBarItem(); - this.statusBarItem.classList.add("obsync-status-bar-item"); + this.statusBarItem.classList.add("cohere-status-bar-item"); this.clearOperationStatus(); this.addCommand({ @@ -91,10 +91,10 @@ export default class ObsyncPlugin extends Plugin { }, }); - const syncRibbonIcon = this.addRibbonIcon("refresh-cw", "Obsync 立即同步", async () => { + const syncRibbonIcon = this.addRibbonIcon("refresh-cw", "Cohere 立即同步", async () => { await this.syncNow("manual"); }); - syncRibbonIcon.classList.add("obsync-ribbon-sync"); + syncRibbonIcon.classList.add("cohere-ribbon-sync"); this.app.workspace.onLayoutReady(() => { this.moveRibbonIconToBottom(syncRibbonIcon); }); @@ -104,7 +104,7 @@ export default class ObsyncPlugin extends Plugin { name: "复制连接配置", callback: async () => { await navigator.clipboard.writeText(JSON.stringify(this.getConnectionConfig(), null, 2)); - new Notice("Obsync 连接配置已复制。"); + new Notice("Cohere 连接配置已复制。"); }, }); @@ -116,7 +116,7 @@ export default class ObsyncPlugin extends Plugin { }, }); - this.addSettingTab(new ObsyncSettingTab(this.app, this)); + this.addSettingTab(new CohereSettingTab(this.app, this)); this.registerAutoSyncTriggers(); } @@ -125,7 +125,7 @@ export default class ObsyncPlugin extends Plugin { this.autoSyncQueued = true; if (trigger === "manual") { - this.showNotice("Obsync 正在同步,稍后会再同步一次。"); + this.showNotice("Cohere 正在同步,稍后会再同步一次。"); } return; @@ -135,7 +135,7 @@ export default class ObsyncPlugin extends Plugin { this.startFileEventSuppression(); try { - await this.runConfiguredOperation("同步中...", "Obsync 同步失败", async () => { + await this.runConfiguredOperation("同步中...", "Cohere 同步失败", async () => { const store = this.createObjectStore(); const result = await syncOnce({ vault: new ObsidianVaultIO(this.app), @@ -157,7 +157,7 @@ export default class ObsyncPlugin extends Plugin { } if (trigger === "manual") { - this.showNotice("Obsync 同步完成。"); + this.showNotice("Cohere 同步完成。"); } }, { notifyMissingConfig: trigger === "manual", @@ -258,7 +258,7 @@ export default class ObsyncPlugin extends Plugin { this.pruneLocalDeletedState(); await this.saveSettings(); - this.showNotice(`Obsync 已释放:清理文件删除记录 ${result.deletedTombstones},目录删除记录 ${result.deletedDirectoryTombstones},删除 Blob ${result.deletedBlobs}。`); + this.showNotice(`Cohere 已释放:清理文件删除记录 ${result.deletedTombstones},目录删除记录 ${result.deletedDirectoryTombstones},删除 Blob ${result.deletedBlobs}。`); }); } @@ -371,15 +371,15 @@ export default class ObsyncPlugin extends Plugin { } async loadSettings(): Promise { - const loaded = await this.loadData() as Partial | null; + const loaded = await this.loadData() as Partial | null; this.settings = Object.assign({}, DEFAULT_SETTINGS, loaded); if (!this.settings.vaultKey) { this.settings.vaultKey = normalizeKey(this.app.vault.getName()); } - if (!loaded?.rootPrefix || loaded.rootPrefix === "obsync/v1") { - this.settings.rootPrefix = "obsync/v2"; + if (!loaded?.rootPrefix) { + this.settings.rootPrefix = "cohere/v1"; } if (!this.settings.deviceId) { @@ -402,7 +402,7 @@ export default class ObsyncPlugin extends Plugin { await this.saveData(this.settings); } - async updateSettings(update: Partial): Promise { + async updateSettings(update: Partial): Promise { Object.assign(this.settings, update); this.settings.accountKey = normalizeKey(this.settings.accountKey || "default"); this.settings.vaultKey = normalizeKey(this.settings.vaultKey || this.app.vault.getName()); @@ -501,7 +501,7 @@ function parseConnectionConfig(configText: string): ConnectionConfig { bucket: readRequiredString(parsed, "bucket"), addressingStyle: readAddressingStyle(parsed, "addressingStyle", "auto"), region: readOptionalString(parsed, "region", "auto"), - rootPrefix: readOptionalString(parsed, "rootPrefix", "obsync/v1"), + rootPrefix: readOptionalString(parsed, "rootPrefix", "cohere/v1"), accountKey: readOptionalString(parsed, "accountKey", "default"), vaultKey: readRequiredString(parsed, "vaultKey"), vaultId: readOptionalString(parsed, "vaultId", ""), @@ -558,11 +558,11 @@ function readOptionalString(record: Record, key: string, fallba return value.trim() || fallback; } -class ObsyncSettingTab extends PluginSettingTab { - plugin: ObsyncPlugin; +class CohereSettingTab extends PluginSettingTab { + plugin: CoherePlugin; vueApp: VueApp | null = null; - constructor(app: App, plugin: ObsyncPlugin) { + constructor(app: App, plugin: CoherePlugin) { super(app, plugin); this.plugin = plugin; } @@ -572,15 +572,15 @@ class ObsyncSettingTab extends PluginSettingTab { this.vueApp?.unmount(); containerEl.empty(); - const mountEl = containerEl.createDiv({ cls: "obsync-settings-root" }); - const settings = reactive(this.plugin.settings) as ObsyncSettings; + const mountEl = containerEl.createDiv({ cls: "cohere-settings-root" }); + const settings = reactive(this.plugin.settings) as CohereSettings; const connectionConfig = reactive(this.plugin.getConnectionConfig()); this.plugin.settings = settings; this.vueApp = createApp(SettingsApp, { settings, connectionConfig, - onUpdate: async (update: Partial) => { + onUpdate: async (update: Partial) => { await this.plugin.updateSettings(update); Object.assign(connectionConfig, this.plugin.getConnectionConfig()); }, diff --git a/src/settings/SettingsApp.vue b/src/settings/SettingsApp.vue index dda7fb4..e7bf1ce 100644 --- a/src/settings/SettingsApp.vue +++ b/src/settings/SettingsApp.vue @@ -2,7 +2,7 @@ import { setIcon } from "obsidian"; import { computed, ref, watchEffect } from "vue"; -type ObsyncSettings = { +type CohereSettings = { endpoint: string; bucket: string; addressingStyle: "auto" | "path" | "virtual-hosted"; @@ -21,12 +21,12 @@ type ObsyncSettings = { }; const props = defineProps<{ - settings: ObsyncSettings; + settings: CohereSettings; connectionConfig: Record; }>(); const emit = defineEmits<{ - update: [update: Partial]; + update: [update: Partial]; copyDeviceId: []; copyConnectionConfig: [includeSecrets: boolean]; pasteConnectionConfig: []; @@ -61,21 +61,21 @@ watchEffect(() => {