refactor: remove Obsync compatibility naming

This commit is contained in:
shi.changliang 2026-06-15 12:18:32 +08:00
parent ef414c959d
commit a2862334d2
11 changed files with 206 additions and 202 deletions

View file

@ -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)
## 当前能力

View file

@ -2,7 +2,7 @@
<rect width="1440" height="920" rx="36" fill="#f7f7f8"/>
<rect x="72" y="70" width="1296" height="780" rx="18" fill="#f1f1f3"/>
<text x="126" y="136" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif" font-size="34" font-weight="700" fill="#222">Obsync 设置</text>
<text x="126" y="136" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif" font-size="34" font-weight="700" fill="#222">Cohere 设置</text>
<text x="126" y="188" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif" font-size="25" fill="#5f5f68">通过对象存储同步当前 Obsidian 仓库。</text>
<rect x="126" y="240" width="1188" height="134" rx="14" fill="#ffffff" stroke="#d9d9de"/>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -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://<bucket>/<rootPrefix>/vaults/<vaultId>/
默认 `rootPrefix`
```text
obsync/v1
cohere/v1
```
完整结构:
```text
obsync/
cohere/
v1/
vaults/
<vaultId>/

View file

@ -11,7 +11,7 @@ export function normalizeKey(value: string): string {
}
export async function createVaultId(accountKey: string, vaultKey: string): Promise<string> {
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)}`;

View file

@ -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");
});
});

View file

@ -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 {

View file

@ -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<TestSettings> = {}): 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<TestSettings> = {}): TestPlugin {
region: "auto",
accessKeyId: "key",
secretAccessKey: "secret",
rootPrefix: "obsync/v1",
rootPrefix: "cohere/v1",
accountKey: "default",
vaultKey: "vault",
vaultId: "vlt_test",

View file

@ -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<void> {
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<void> {
const loaded = await this.loadData() as Partial<ObsyncSettings> | null;
const loaded = await this.loadData() as Partial<CohereSettings> | 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<ObsyncSettings>): Promise<void> {
async updateSettings(update: Partial<CohereSettings>): Promise<void> {
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<string, unknown>, key: string, fallba
return value.trim() || fallback;
}
class ObsyncSettingTab extends PluginSettingTab {
plugin: ObsyncPlugin;
class CohereSettingTab extends PluginSettingTab {
plugin: CoherePlugin;
vueApp: VueApp<Element> | 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<ObsyncSettings>) => {
onUpdate: async (update: Partial<CohereSettings>) => {
await this.plugin.updateSettings(update);
Object.assign(connectionConfig, this.plugin.getConnectionConfig());
},

View file

@ -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<string, string | number>;
}>();
const emit = defineEmits<{
update: [update: Partial<ObsyncSettings>];
update: [update: Partial<CohereSettings>];
copyDeviceId: [];
copyConnectionConfig: [includeSecrets: boolean];
pasteConnectionConfig: [];
@ -61,21 +61,21 @@ watchEffect(() => {
</script>
<template>
<main class="obsync-panel">
<header class="obsync-header">
<main class="cohere-panel">
<header class="cohere-header">
<div>
<h2 class="obsync-title">Obsync 设置</h2>
<p class="obsync-subtitle">通过对象存储同步当前 Obsidian 仓库</p>
<h2 class="cohere-title">Cohere 设置</h2>
<p class="cohere-subtitle">通过对象存储同步当前 Obsidian 仓库</p>
</div>
</header>
<section class="obsync-card">
<div class="obsync-row">
<div class="obsync-row-copy">
<section class="cohere-card">
<div class="cohere-row">
<div class="cohere-row-copy">
<h3>服务端点</h3>
<p>S3 兼容对象存储地址例如 Cloudflare R2 endpoint</p>
</div>
<div class="obsync-control obsync-control-wide">
<div class="cohere-control cohere-control-wide">
<input
:value="props.settings.endpoint"
placeholder="https://xxx.r2.cloudflarestorage.com"
@ -84,12 +84,12 @@ watchEffect(() => {
</div>
</div>
<div class="obsync-row">
<div class="obsync-row-copy">
<div class="cohere-row">
<div class="cohere-row-copy">
<h3>Bucket</h3>
<p>用于保存同步文件的对象存储桶</p>
</div>
<div class="obsync-control">
<div class="cohere-control">
<input
:value="props.settings.bucket"
placeholder="ideabase"
@ -98,15 +98,15 @@ watchEffect(() => {
</div>
</div>
<div class="obsync-row">
<div class="obsync-row-copy">
<div class="cohere-row">
<div class="cohere-row-copy">
<h3>地址风格</h3>
<p>自动适配常见服务也可手动指定请求地址格式</p>
</div>
<div class="obsync-control">
<div class="cohere-control">
<select
:value="props.settings.addressingStyle"
@change="emit('update', { addressingStyle: ($event.target as HTMLSelectElement).value as ObsyncSettings['addressingStyle'] })"
@change="emit('update', { addressingStyle: ($event.target as HTMLSelectElement).value as CohereSettings['addressingStyle'] })"
>
<option value="auto">自动</option>
<option value="path">路径风格</option>
@ -115,12 +115,12 @@ watchEffect(() => {
</div>
</div>
<div class="obsync-row">
<div class="obsync-row-copy">
<div class="cohere-row">
<div class="cohere-row-copy">
<h3>Region</h3>
<p>R2 通常填写 auto其他 S3 服务按实际区域填写</p>
</div>
<div class="obsync-control">
<div class="cohere-control">
<input
:value="props.settings.region"
placeholder="auto"
@ -129,26 +129,26 @@ watchEffect(() => {
</div>
</div>
<div class="obsync-row">
<div class="obsync-row-copy">
<div class="cohere-row">
<div class="cohere-row-copy">
<h3>根路径</h3>
<p>同一个 bucket 内的插件数据前缀</p>
</div>
<div class="obsync-control">
<div class="cohere-control">
<input
:value="props.settings.rootPrefix"
placeholder="obsync/v2"
@input="emit('update', { rootPrefix: ($event.target as HTMLInputElement).value.trim() || 'obsync/v2' })"
placeholder="cohere/v1"
@input="emit('update', { rootPrefix: ($event.target as HTMLInputElement).value.trim() || 'cohere/v1' })"
/>
</div>
</div>
<div class="obsync-row">
<div class="obsync-row-copy">
<div class="cohere-row">
<div class="cohere-row-copy">
<h3>Access Key ID</h3>
<p>对象存储访问密钥 ID仅保存在本地</p>
</div>
<div class="obsync-control">
<div class="cohere-control">
<input
:value="props.settings.accessKeyId"
autocomplete="off"
@ -157,13 +157,13 @@ watchEffect(() => {
</div>
</div>
<div class="obsync-row">
<div class="obsync-row-copy">
<div class="cohere-row">
<div class="cohere-row-copy">
<h3>Secret Access Key</h3>
<p>不会包含在连接配置中</p>
</div>
<div class="obsync-control obsync-control-wide">
<div class="obsync-secret-field">
<div class="cohere-control cohere-control-wide">
<div class="cohere-secret-field">
<input
:type="showSecretAccessKey ? 'text' : 'password'"
:value="props.settings.secretAccessKey"
@ -172,7 +172,7 @@ watchEffect(() => {
/>
<button
ref="secretAccessKeyButton"
class="obsync-icon-button"
class="cohere-icon-button"
type="button"
:aria-label="showSecretAccessKey ? '隐藏密钥' : '显示密钥'"
:title="showSecretAccessKey ? '隐藏密钥' : '显示密钥'"
@ -183,27 +183,27 @@ watchEffect(() => {
</div>
</section>
<section class="obsync-card">
<div class="obsync-row">
<div class="obsync-row-copy">
<section class="cohere-card">
<div class="cohere-row">
<div class="cohere-row-copy">
<h3>释放空间</h3>
<p>清理已删除文件的远端记录和不再引用的 Blob</p>
</div>
<div class="obsync-control obsync-release-control">
<button type="button" class="obsync-secondary" @click="emit('releaseDeletedContent')">
<div class="cohere-control cohere-release-control">
<button type="button" class="cohere-secondary" @click="emit('releaseDeletedContent')">
释放已删除内容
</button>
</div>
</div>
</section>
<section class="obsync-card">
<div class="obsync-row">
<div class="obsync-row-copy">
<section class="cohere-card">
<div class="cohere-row">
<div class="cohere-row-copy">
<h3>账号标识</h3>
<p>同一用户或团队在所有设备上保持一致</p>
</div>
<div class="obsync-control">
<div class="cohere-control">
<input
:value="props.settings.accountKey"
@input="emit('update', { accountKey: ($event.target as HTMLInputElement).value })"
@ -211,12 +211,12 @@ watchEffect(() => {
</div>
</div>
<div class="obsync-row">
<div class="obsync-row-copy">
<div class="cohere-row">
<div class="cohere-row-copy">
<h3>仓库标识</h3>
<p>同一个 vault 在所有设备上保持一致</p>
</div>
<div class="obsync-control">
<div class="cohere-control">
<input
:value="props.settings.vaultKey"
@input="emit('update', { vaultKey: ($event.target as HTMLInputElement).value })"
@ -224,25 +224,25 @@ watchEffect(() => {
</div>
</div>
<div class="obsync-row">
<div class="obsync-row-copy">
<div class="cohere-row">
<div class="cohere-row-copy">
<h3>Vault ID</h3>
<p>根据账号标识和仓库标识自动计算</p>
</div>
<div class="obsync-readonly">
<div class="cohere-readonly">
<code>{{ props.settings.vaultId }}</code>
</div>
</div>
</section>
<section class="obsync-card">
<div class="obsync-row">
<div class="obsync-row-copy">
<section class="cohere-card">
<div class="cohere-row">
<div class="cohere-row-copy">
<h3>设备名称</h3>
<p>用于冲突文件名和界面展示</p>
</div>
<div class="obsync-control obsync-device-control">
<button type="button" class="obsync-link-button" @click="emit('copyDeviceId')">
<div class="cohere-control cohere-device-control">
<button type="button" class="cohere-link-button" @click="emit('copyDeviceId')">
复制设备 ID
</button>
<input
@ -252,12 +252,12 @@ watchEffect(() => {
</div>
</div>
<div class="obsync-row obsync-toggle-row">
<div class="obsync-row-copy">
<div class="cohere-row cohere-toggle-row">
<div class="cohere-row-copy">
<h3>自动同步</h3>
<p>启动恢复前台和文件变化时自动同步</p>
</div>
<label class="obsync-switch">
<label class="cohere-switch">
<input
type="checkbox"
:checked="props.settings.autoSync"
@ -267,12 +267,12 @@ watchEffect(() => {
</label>
</div>
<div class="obsync-row obsync-toggle-row">
<div class="obsync-row-copy">
<div class="cohere-row cohere-toggle-row">
<div class="cohere-row-copy">
<h3>同步空目录</h3>
<p>同步没有文件的文件夹关闭后不会删除已有本地目录</p>
</div>
<label class="obsync-switch">
<label class="cohere-switch">
<input
type="checkbox"
:checked="props.settings.syncEmptyDirectories"
@ -283,15 +283,15 @@ watchEffect(() => {
</div>
</section>
<section class="obsync-card">
<div class="obsync-row obsync-row-block">
<div class="obsync-row-copy">
<div class="obsync-row-title">
<section class="cohere-card">
<div class="cohere-row cohere-row-block">
<div class="cohere-row-copy">
<div class="cohere-row-title">
<h3>连接配置</h3>
<label class="obsync-inline-switch">
<label class="cohere-inline-switch">
<span>包含密钥</span>
<span class="obsync-switch">
<span class="cohere-switch">
<input
type="checkbox"
:checked="includeSecrets"
@ -306,14 +306,14 @@ watchEffect(() => {
</p>
</div>
<pre class="obsync-config-display">{{ JSON.stringify(connectionConfigPreview, null, 2) }}</pre>
<pre class="cohere-config-display">{{ JSON.stringify(connectionConfigPreview, null, 2) }}</pre>
<div class="obsync-action-row">
<button type="button" class="obsync-primary" @click="emit('copyConnectionConfig', includeSecrets)">
<div class="cohere-action-row">
<button type="button" class="cohere-primary" @click="emit('copyConnectionConfig', includeSecrets)">
{{ includeSecrets ? "复制完整配置" : "复制连接配置" }}
</button>
<button type="button" class="obsync-secondary" @click="emit('pasteConnectionConfig')">
<button type="button" class="cohere-secondary" @click="emit('pasteConnectionConfig')">
粘贴并导入
</button>
</div>

View file

@ -27,7 +27,7 @@ describe("S3ObjectStore", () => {
await store.writeObject("files/notes/today.md", new TextEncoder().encode("hello"));
expect(requests[0]?.method).toBe("PUT");
expect(requests[0]?.url).toBe("https://s3.example.com/my-bucket/obsync/v1/vaults/vlt_TEST/files/notes/today.md");
expect(requests[0]?.url).toBe("https://s3.example.com/my-bucket/cohere/v1/vaults/vlt_TEST/files/notes/today.md");
expect(requests[0]?.headers.Authorization).toContain("AWS4-HMAC-SHA256");
expect(requests[0]?.headers["x-amz-content-sha256"]).toMatch(/^[a-f0-9]{64}$/);
});
@ -46,7 +46,7 @@ describe("S3ObjectStore", () => {
await store.deleteObject("files/notes/today.md");
expect(requests[0]?.method).toBe("DELETE");
expect(requests[0]?.url).toBe("https://s3.example.com/my-bucket/obsync/v1/vaults/vlt_TEST/files/notes/today.md");
expect(requests[0]?.url).toBe("https://s3.example.com/my-bucket/cohere/v1/vaults/vlt_TEST/files/notes/today.md");
});
test("lists object keys under the vault prefix", async () => {
@ -58,8 +58,8 @@ describe("S3ObjectStore", () => {
text: [
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<ListBucketResult>",
"<Contents><Key>obsync/v1/vaults/vlt_TEST/blobs/sha256/aa/bb/hash1</Key></Contents>",
"<Contents><Key>obsync/v1/vaults/vlt_TEST/blobs/sha256/cc/dd/hash2</Key></Contents>",
"<Contents><Key>cohere/v1/vaults/vlt_TEST/blobs/sha256/aa/bb/hash1</Key></Contents>",
"<Contents><Key>cohere/v1/vaults/vlt_TEST/blobs/sha256/cc/dd/hash2</Key></Contents>",
"</ListBucketResult>",
].join(""),
arrayBuffer: new ArrayBuffer(0),
@ -72,7 +72,7 @@ describe("S3ObjectStore", () => {
]);
expect(requests[0]?.method).toBe("GET");
expect(requests[0]?.url).toBe(
"https://s3.example.com/my-bucket?list-type=2&prefix=obsync%2Fv1%2Fvaults%2Fvlt_TEST%2Fblobs%2Fsha256%2F",
"https://s3.example.com/my-bucket?list-type=2&prefix=cohere%2Fv1%2Fvaults%2Fvlt_TEST%2Fblobs%2Fsha256%2F",
);
});
@ -84,14 +84,14 @@ describe("S3ObjectStore", () => {
"<ListBucketResult>",
"<IsTruncated>true</IsTruncated>",
"<NextContinuationToken>page 2</NextContinuationToken>",
"<Contents><Key>obsync/v1/vaults/vlt_TEST/blobs/sha256/aa/bb/hash1</Key></Contents>",
"<Contents><Key>cohere/v1/vaults/vlt_TEST/blobs/sha256/aa/bb/hash1</Key></Contents>",
"</ListBucketResult>",
].join(""),
[
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<ListBucketResult>",
"<IsTruncated>false</IsTruncated>",
"<Contents><Key>obsync/v1/vaults/vlt_TEST/blobs/sha256/cc/dd/hash2</Key></Contents>",
"<Contents><Key>cohere/v1/vaults/vlt_TEST/blobs/sha256/cc/dd/hash2</Key></Contents>",
"</ListBucketResult>",
].join(""),
];
@ -129,7 +129,7 @@ describe("S3ObjectStore", () => {
await store.writeObject("files/notes/today.md", new TextEncoder().encode("hello"));
expect(requests[0]?.url).toBe("https://cohere-test.oss-cn-beijing.aliyuncs.com/obsync/v1/vaults/vlt_TEST/files/notes/today.md");
expect(requests[0]?.url).toBe("https://cohere-test.oss-cn-beijing.aliyuncs.com/cohere/v1/vaults/vlt_TEST/files/notes/today.md");
});
test("can force path style addressing", async () => {
@ -149,7 +149,7 @@ describe("S3ObjectStore", () => {
await store.writeObject("files/notes/today.md", new TextEncoder().encode("hello"));
expect(requests[0]?.url).toBe("https://oss-cn-beijing.aliyuncs.com/cohere-test/obsync/v1/vaults/vlt_TEST/files/notes/today.md");
expect(requests[0]?.url).toBe("https://oss-cn-beijing.aliyuncs.com/cohere-test/cohere/v1/vaults/vlt_TEST/files/notes/today.md");
});
test("can force virtual-hosted style addressing", async () => {
@ -169,7 +169,7 @@ describe("S3ObjectStore", () => {
await store.writeObject("files/notes/today.md", new TextEncoder().encode("hello"));
expect(requests[0]?.url).toBe("https://cohere-test.s3.example.com/obsync/v1/vaults/vlt_TEST/files/notes/today.md");
expect(requests[0]?.url).toBe("https://cohere-test.s3.example.com/cohere/v1/vaults/vlt_TEST/files/notes/today.md");
});
});
@ -184,7 +184,7 @@ function createStore(
region: "auto",
accessKeyId: "AKIA_TEST",
secretAccessKey: "SECRET_TEST",
rootPrefix: "obsync/v1",
rootPrefix: "cohere/v1",
vaultId: "vlt_TEST",
deviceId: "dev_TEST",
now: () => 1000,

View file

@ -1,40 +1,40 @@
@reference "tailwindcss";
.obsync-settings-root {
.cohere-settings-root {
container-type: inline-size;
}
.obsync-status-bar-item {
.cohere-status-bar-item {
@apply flex items-center gap-1 text-xs;
color: var(--text-muted);
}
.obsync-status-bar-item.is-hidden {
.cohere-status-bar-item.is-hidden {
display: none;
}
.obsync-panel {
.cohere-panel {
@apply w-full max-w-3xl pb-6;
color: var(--text-normal);
}
.obsync-panel *,
.obsync-panel *::before,
.obsync-panel *::after {
.cohere-panel *,
.cohere-panel *::before,
.cohere-panel *::after {
@apply box-border;
}
.obsync-header {
.cohere-header {
@apply mb-4 flex items-start justify-between gap-3;
}
.obsync-subtitle,
.obsync-row-copy p {
.cohere-subtitle,
.cohere-row-copy p {
@apply m-0;
color: var(--text-muted);
}
.obsync-title {
.cohere-title {
@apply m-0 p-0 text-2xl font-bold leading-tight;
color: var(--text-normal);
margin-inline-start: 0 !important;
@ -43,20 +43,20 @@
text-indent: 0;
}
.obsync-subtitle {
.cohere-subtitle {
@apply mt-1 text-sm leading-6;
}
.obsync-card {
.cohere-card {
@apply mb-5 rounded-xl p-0;
background: var(--background-secondary);
}
.obsync-row {
.cohere-row {
@apply relative flex items-center justify-between gap-5 px-5 py-4;
}
.obsync-row::after {
.cohere-row::after {
content: "";
position: absolute;
inset-inline: 1.25rem;
@ -65,11 +65,11 @@
background: var(--background-modifier-border);
}
.obsync-row:last-child::after {
.cohere-row:last-child::after {
display: none;
}
.obsync-row-copy {
.cohere-row-copy {
@apply min-w-0 flex-1 p-0 text-left;
margin-inline-start: 0 !important;
padding-inline-start: 0 !important;
@ -90,11 +90,11 @@
}
}
.obsync-row-title {
.cohere-row-title {
@apply flex items-center justify-between gap-3;
}
.obsync-inline-switch {
.cohere-inline-switch {
@apply flex h-6 shrink-0 cursor-pointer items-center gap-2 self-center whitespace-nowrap text-sm font-medium leading-none;
color: var(--text-muted);
@ -102,12 +102,12 @@
@apply flex h-6 items-center;
}
.obsync-switch {
.cohere-switch {
@apply mt-0;
}
}
.obsync-link-button {
.cohere-link-button {
@apply shrink-0 cursor-pointer rounded px-2 py-1 text-xs font-semibold leading-4 transition;
background: transparent;
border: 0;
@ -115,35 +115,35 @@
color: var(--text-muted);
}
.obsync-link-button:hover {
.cohere-link-button:hover {
background: var(--background-modifier-hover);
color: var(--text-normal);
}
.obsync-control,
.obsync-control-wide {
.cohere-control,
.cohere-control-wide {
@apply w-80 shrink-0;
}
.obsync-control input,
.obsync-control select {
.cohere-control input,
.cohere-control select {
@apply h-9 w-full min-w-0 rounded-md px-3 py-2 text-sm outline-none transition;
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
color: var(--text-normal);
}
.obsync-control input:focus,
.obsync-control select:focus {
.cohere-control input:focus,
.cohere-control select:focus {
border-color: var(--interactive-accent);
box-shadow: 0 0 0 2px var(--background-modifier-border-focus);
}
.obsync-control input::placeholder {
.cohere-control input::placeholder {
color: var(--text-faint);
}
.obsync-secret-field {
.cohere-secret-field {
@apply flex h-9 min-w-0 items-center rounded-md;
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
@ -163,7 +163,7 @@
box-shadow: none;
}
.obsync-icon-button {
.cohere-icon-button {
@apply mr-1 flex h-8 w-8 shrink-0 cursor-pointer items-center justify-center rounded transition;
appearance: none;
background: transparent !important;
@ -174,59 +174,59 @@
padding: 0 !important;
}
.obsync-icon-button .svg-icon,
.obsync-icon-button svg {
.cohere-icon-button .svg-icon,
.cohere-icon-button svg {
width: 24px !important;
height: 24px !important;
stroke-width: 2;
}
.obsync-icon-button:hover {
.cohere-icon-button:hover {
background: var(--background-modifier-hover);
color: var(--text-normal);
}
.obsync-icon-button:focus,
.obsync-icon-button:focus-visible {
.cohere-icon-button:focus,
.cohere-icon-button:focus-visible {
box-shadow: none !important;
outline: none;
}
}
.obsync-readonly {
.cohere-readonly {
@apply flex min-w-0 shrink-0 items-center justify-end gap-3;
width: min(380px, 58%);
}
.obsync-readonly code {
.cohere-readonly code {
@apply min-w-0 flex-1 break-all text-right text-xs leading-5;
color: var(--text-normal);
}
.obsync-primary {
.cohere-primary {
@apply shrink-0 cursor-pointer rounded-md border-0 px-3 py-2 text-sm font-semibold leading-5 transition;
background: var(--interactive-accent);
color: var(--text-on-accent);
}
.obsync-secondary {
.cohere-secondary {
@apply shrink-0 cursor-pointer rounded-md px-3 py-2 text-sm font-semibold leading-5 transition;
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
color: var(--text-normal);
}
.obsync-primary:hover,
.obsync-secondary:hover {
.cohere-primary:hover,
.cohere-secondary:hover {
@apply opacity-90;
}
.obsync-primary:active,
.obsync-secondary:active {
.cohere-primary:active,
.cohere-secondary:active {
@apply translate-y-px;
}
.obsync-switch {
.cohere-switch {
@apply relative block h-6 w-11 shrink-0 cursor-pointer;
input {
@ -253,22 +253,22 @@
}
}
.obsync-toggle-row {
.cohere-toggle-row {
@apply flex items-center justify-between gap-5;
.obsync-row-copy {
.cohere-row-copy {
@apply mb-0;
}
}
.obsync-release-control {
.cohere-release-control {
@apply flex items-center justify-end;
}
.obsync-device-control {
.cohere-device-control {
@apply flex items-center gap-3;
.obsync-link-button {
.cohere-link-button {
@apply h-9;
}
@ -277,15 +277,15 @@
}
}
.obsync-row-block {
.cohere-row-block {
@apply block;
}
.obsync-row-block .obsync-row-copy {
.cohere-row-block .cohere-row-copy {
@apply mb-3;
}
.obsync-config-display {
.cohere-config-display {
@apply min-h-48 rounded-md p-3 font-mono text-xs leading-5;
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
@ -294,94 +294,94 @@
white-space: pre-wrap;
}
.obsync-action-row {
.cohere-action-row {
@apply mt-3 flex gap-2;
}
.obsync-action-row button {
.cohere-action-row button {
@apply flex-1;
}
.obsync-action-row .obsync-secondary {
.cohere-action-row .cohere-secondary {
@apply mt-0;
}
@container (max-width: 640px) {
.obsync-header {
.cohere-header {
@apply block;
padding-top: max(2rem, env(safe-area-inset-top, 0px));
}
.obsync-card {
.cohere-card {
@apply mb-4 rounded-lg;
}
.obsync-row {
.cohere-row {
@apply block px-4 py-4;
}
.obsync-row::after {
.cohere-row::after {
inset-inline: 1rem;
}
.obsync-row-copy {
.cohere-row-copy {
@apply mb-2;
}
.obsync-toggle-row {
.cohere-toggle-row {
@apply flex gap-4;
.obsync-row-copy {
.cohere-row-copy {
@apply mb-0;
}
}
.obsync-row-title {
.cohere-row-title {
@apply items-center;
}
.obsync-row:not(.obsync-toggle-row) .obsync-inline-switch .obsync-switch {
.cohere-row:not(.cohere-toggle-row) .cohere-inline-switch .cohere-switch {
@apply mt-0;
}
.obsync-control,
.obsync-control-wide,
.obsync-readonly {
.cohere-control,
.cohere-control-wide,
.cohere-readonly {
@apply w-full;
}
.obsync-readonly {
.cohere-readonly {
@apply flex-col items-stretch gap-2;
}
.obsync-readonly code {
.cohere-readonly code {
@apply text-left;
}
.obsync-primary,
.obsync-secondary {
.cohere-primary,
.cohere-secondary {
@apply w-full;
}
.obsync-release-control {
.cohere-release-control {
@apply justify-stretch;
}
.obsync-device-control {
.cohere-device-control {
@apply flex-col items-stretch gap-2;
.obsync-link-button {
.cohere-link-button {
@apply w-full;
}
}
.obsync-row:not(.obsync-toggle-row) > .obsync-switch {
.cohere-row:not(.cohere-toggle-row) > .cohere-switch {
@apply mt-2;
}
}
@media (max-width: 560px) {
.obsync-title {
.cohere-title {
@apply text-xl;
}
}