mirror of
https://github.com/long2icc/sonicnote-sync.git
synced 2026-07-22 07:45:14 +00:00
first commit
This commit is contained in:
parent
e0a2af6b3c
commit
9a018c4494
11 changed files with 339 additions and 87 deletions
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"id": "znote-sync",
|
||||
"name": "Znote Sync",
|
||||
"id": "sonicnote-sync",
|
||||
"name": "SonicNote Sync",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Sync recordings from Znote (SonicNote) to Obsidian as Markdown files",
|
||||
"author": "EasyLink",
|
||||
"description": "Sync recordings from SonicNote to Obsidian as Markdown files",
|
||||
"author": "EasyLinkIn",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"name": "znote-sync",
|
||||
"name": "sonicnote-sync",
|
||||
"version": "1.0.0",
|
||||
"description": "Sync recordings from Znote (SonicNote) to Obsidian as Markdown files",
|
||||
"description": "Sync recordings from SonicNote to Obsidian as Markdown files",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production"
|
||||
},
|
||||
"keywords": ["obsidian", "znote", "recording", "sync"],
|
||||
"keywords": ["obsidian", "sonicnote", "recording", "sync"],
|
||||
"author": "EasyLink",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
|
|
|
|||
252
release/obsidian-plugin.html
Normal file
252
release/obsidian-plugin.html
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>SonicNote Sync — Obsidian 插件</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f7f8fa;
|
||||
--surface: #ffffff;
|
||||
--border: #ebedf0;
|
||||
--text: #1d2129;
|
||||
--text-secondary: #656a72;
|
||||
--accent: #333;
|
||||
--accent-light: #f0f1f3;
|
||||
--code-bg: #1e1e2e;
|
||||
--code-text: #cdd6f4;
|
||||
--radius: 8px;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.65;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
.header {
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 36px 24px 28px;
|
||||
text-align: center;
|
||||
}
|
||||
.header .version {
|
||||
display: inline-block;
|
||||
background: var(--accent-light);
|
||||
padding: 2px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.header h1 { font-size: 1.6rem; font-weight: 700; letter-spacing: -0.3px; margin-bottom: 6px; }
|
||||
.header p { font-size: 0.92rem; color: var(--text-secondary); margin-bottom: 18px; }
|
||||
.download-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
padding: 9px 28px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.92rem;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
.download-btn:hover { opacity: 0.82; }
|
||||
.download-btn svg { width: 16px; height: 16px; }
|
||||
.container { max-width: 700px; margin: 0 auto; padding: 24px 20px 60px; }
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 22px 26px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.card h2 {
|
||||
font-size: 1.05rem;
|
||||
margin-bottom: 14px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.card h3 { font-size: 0.92rem; margin: 16px 0 6px; font-weight: 600; }
|
||||
.card p, .card li { color: var(--text-secondary); font-size: 0.9rem; }
|
||||
.card ol, .card ul { padding-left: 18px; margin: 4px 0; }
|
||||
.card li { margin-bottom: 2px; }
|
||||
.code-wrap {
|
||||
position: relative;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.code-wrap pre {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.55;
|
||||
margin: 0;
|
||||
}
|
||||
.copy-btn {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 4px 7px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s;
|
||||
color: #aaa;
|
||||
line-height: 1;
|
||||
}
|
||||
.code-wrap:hover .copy-btn { opacity: 1; }
|
||||
.copy-btn:hover { background: rgba(255,255,255,0.2); color: #fff; }
|
||||
.copy-btn.copied { opacity: 1; color: #4ade80; }
|
||||
code { font-family: "SF Mono", "Fira Code", "Cascadia Code", Consolas, monospace; }
|
||||
.inline-code {
|
||||
background: var(--accent-light);
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.85em;
|
||||
color: var(--text);
|
||||
}
|
||||
table { width: 100%; border-collapse: collapse; margin: 8px 0; }
|
||||
th { text-align: left; padding: 7px 12px; background: var(--accent-light); font-weight: 600; font-size: 0.85rem; }
|
||||
td { padding: 7px 12px; border-bottom: 1px solid var(--border); font-size: 0.88rem; }
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.header { padding: 28px 16px 22px; }
|
||||
.header h1 { font-size: 1.35rem; }
|
||||
.card { padding: 18px 16px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="header">
|
||||
<div class="version">v1.0.0</div>
|
||||
<h1>SonicNote Sync</h1>
|
||||
<p>将 SonicNote 妙记的录音、转录、总结同步到 Obsidian,以 Markdown 文件管理。</p>
|
||||
<a class="download-btn" href="sonicnote-sync.zip" download>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||||
下载插件
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="card">
|
||||
<h2>前置要求</h2>
|
||||
<ul>
|
||||
<li>Obsidian 桌面版 v0.15.0 及以上(<a href="https://obsidian.md/download" target="_blank">下载地址</a>)</li>
|
||||
<li>SonicNote 妙记账号</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>安装</h2>
|
||||
<ol>
|
||||
<li>点击上方按钮下载 <span class="inline-code">sonicnote-sync.zip</span></li>
|
||||
<li>解压 zip 文件,得到以下文件:
|
||||
<ul style="margin-top:4px;">
|
||||
<li><span class="inline-code">main.js</span></li>
|
||||
<li><span class="inline-code">manifest.json</span></li>
|
||||
<li><span class="inline-code">styles.css</span></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>打开 Obsidian Vault 所在文件夹,进入插件目录:</li>
|
||||
</ol>
|
||||
<div class="code-wrap"><button class="copy-btn" title="复制"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></button><pre><code>.obsidian/plugins/</code></pre></div>
|
||||
<ol start="4">
|
||||
<li>在 <span class="inline-code">plugins</span> 目录下新建文件夹 <span class="inline-code">sonicnote-sync</span>,将解压的 3 个文件放进去:</li>
|
||||
</ol>
|
||||
<div class="code-wrap"><button class="copy-btn" title="复制"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></button><pre><code>.obsidian/plugins/sonicnote-sync/
|
||||
├── main.js
|
||||
├── manifest.json
|
||||
└── styles.css</code></pre></div>
|
||||
<ol start="5">
|
||||
<li>重启 Obsidian,进入 <strong>设置 → 第三方/社区插件</strong>,在列表中找到 <strong>SonicNote Sync</strong> 并启用</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>使用方法</h2>
|
||||
<h3>登录</h3>
|
||||
<ol>
|
||||
<li>进入 <strong>设置 → SonicNote Sync</strong></li>
|
||||
<li>点击"登录"按钮</li>
|
||||
<li>输入 SonicNote 妙记的手机号和密码(使用 APP 验证码或者申领长期密码)</li>
|
||||
</ol>
|
||||
|
||||
<h3>同步录音</h3>
|
||||
<p>登录成功后,通过以下任一方式触发同步:</p>
|
||||
<ul>
|
||||
<li>点击左侧功能区的 <strong>耳机图标</strong></li>
|
||||
<li>命令面板(Ctrl/Cmd + P)输入 <strong>"SonicNote: 同步录音"</strong></li>
|
||||
</ul>
|
||||
<p style="margin-top:6px;">同步完成后,Vault 中会生成 <span class="inline-code">SonicNoteSync/</span> 文件夹,每条录音对应一个 Markdown 文件。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>同步内容</h2>
|
||||
<p>每个录音文件包含:</p>
|
||||
<table>
|
||||
<thead><tr><th>内容</th><th>说明</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Frontmatter</td><td>录音元数据(ID、时间、时长、设备名、标签等)</td></tr>
|
||||
<tr><td>笔记</td><td>你在 App 中手写的笔记内容</td></tr>
|
||||
<tr><td>转录内容</td><td>AI 转写文本(含说话人、时间戳)</td></tr>
|
||||
<tr><td>AI 总结</td><td>智能总结内容(要点、行动项等)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>设置项</h2>
|
||||
<table>
|
||||
<thead><tr><th>设置</th><th>默认值</th><th>说明</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>同步文件夹</td><td><span class="inline-code">SonicNoteSync</span></td><td>Markdown 文件存放目录</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>卸载</h2>
|
||||
<ol>
|
||||
<li>进入 <strong>设置 → 社区插件</strong>,找到 SonicNote Sync,点击禁用并卸载</li>
|
||||
<li>手动删除 <span class="inline-code">.obsidian/plugins/sonicnote-sync/</span> 文件夹</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<footer>SonicNote Sync © 2026</footer>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('.copy-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const code = btn.parentElement.querySelector('code');
|
||||
navigator.clipboard.writeText(code.textContent).then(() => {
|
||||
btn.classList.add('copied');
|
||||
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>';
|
||||
setTimeout(() => {
|
||||
btn.classList.remove('copied');
|
||||
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>';
|
||||
}, 1500);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
BIN
release/sonicnote-sync.zip
Normal file
BIN
release/sonicnote-sync.zip
Normal file
Binary file not shown.
39
src/api.ts
39
src/api.ts
|
|
@ -1,8 +1,8 @@
|
|||
import { requestUrl, RequestUrlParam } from 'obsidian';
|
||||
import { BackendResponse, Recording, TranscriptSegment, SummaryData, ZnotePluginSettings } from './types';
|
||||
import { BackendResponse, Recording, TranscriptSegment, SummaryData, SonicNotePluginSettings } from './types';
|
||||
|
||||
export class ZnoteApiClient {
|
||||
constructor(private getSettings: () => ZnotePluginSettings) {}
|
||||
export class SonicNoteApiClient {
|
||||
constructor(private getSettings: () => SonicNotePluginSettings) {}
|
||||
|
||||
isAuthenticated(): boolean {
|
||||
return this.getSettings().token !== '';
|
||||
|
|
@ -31,19 +31,25 @@ export class ZnoteApiClient {
|
|||
headers['Authorization'] = `Bearer ${this.token}`;
|
||||
}
|
||||
|
||||
const params: RequestUrlParam = {
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
};
|
||||
|
||||
if (method === 'POST' && options?.body) {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
params.body = JSON.stringify(options.body);
|
||||
}
|
||||
|
||||
const response = await requestUrl(params);
|
||||
return response.json as BackendResponse;
|
||||
try {
|
||||
const response = await requestUrl({
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
body: method === 'POST' && options?.body ? JSON.stringify(options.body) : undefined,
|
||||
});
|
||||
return response.json as BackendResponse;
|
||||
} catch (e: any) {
|
||||
// requestUrl throws on non-2xx status codes; try to extract the body
|
||||
if (e?.json) {
|
||||
return e.json as BackendResponse;
|
||||
}
|
||||
throw new Error(e?.message || `请求失败: ${method} ${path}`);
|
||||
}
|
||||
}
|
||||
|
||||
async login(phone: string, code: string): Promise<{ token: string; userId: string }> {
|
||||
|
|
@ -53,9 +59,14 @@ export class ZnoteApiClient {
|
|||
if (res.code !== 200) {
|
||||
throw new Error(res.msg || '登录失败');
|
||||
}
|
||||
const data = res.data;
|
||||
const token = typeof data === 'string' ? data : data?.token;
|
||||
if (!token) {
|
||||
throw new Error('登录响应中缺少 token');
|
||||
}
|
||||
return {
|
||||
token: res.data.token || res.data,
|
||||
userId: res.data.userId || '',
|
||||
token,
|
||||
userId: data?.user?.userId || data?.userId || '',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export function generateFrontmatter(recording: Recording, syncTime: string): str
|
|||
}
|
||||
|
||||
lines.push('tags:');
|
||||
lines.push(' - znote');
|
||||
lines.push(' - sonicnote');
|
||||
|
||||
const recordTypeLabel = recording.recordType === '00' ? '通话' : '录音';
|
||||
lines.push(` - ${recordTypeLabel}`);
|
||||
|
|
|
|||
42
src/main.ts
42
src/main.ts
|
|
@ -1,12 +1,12 @@
|
|||
import { Notice, Plugin } from 'obsidian';
|
||||
import { ZnoteApiClient } from './api';
|
||||
import { SonicNoteApiClient } from './api';
|
||||
import { SyncService } from './sync';
|
||||
import { ZnoteSettingTab } from './settings';
|
||||
import { DEFAULT_SETTINGS, ZnotePluginSettings } from './types';
|
||||
import { SonicNoteSettingTab } from './settings';
|
||||
import { DEFAULT_SETTINGS, SonicNotePluginSettings } from './types';
|
||||
|
||||
export default class ZnoteSyncPlugin extends Plugin {
|
||||
settings: ZnotePluginSettings = DEFAULT_SETTINGS;
|
||||
private api!: ZnoteApiClient;
|
||||
export default class SonicNoteSyncPlugin extends Plugin {
|
||||
settings: SonicNotePluginSettings = DEFAULT_SETTINGS;
|
||||
private api!: SonicNoteApiClient;
|
||||
private syncService!: SyncService;
|
||||
private statusBarEl!: HTMLElement;
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ export default class ZnoteSyncPlugin extends Plugin {
|
|||
await this.loadSettings();
|
||||
|
||||
// Initialize API client and sync service
|
||||
this.api = new ZnoteApiClient(() => this.settings);
|
||||
this.api = new SonicNoteApiClient(() => this.settings);
|
||||
this.syncService = new SyncService(
|
||||
this.app,
|
||||
this.api,
|
||||
|
|
@ -27,42 +27,42 @@ export default class ZnoteSyncPlugin extends Plugin {
|
|||
this.updateStatusBar();
|
||||
|
||||
// Ribbon icon
|
||||
this.addRibbonIcon('headphones', 'Znote Sync: 同步录音', () => {
|
||||
this.addRibbonIcon('headphones', 'SonicNote Sync: 同步录音', () => {
|
||||
this.triggerSync();
|
||||
});
|
||||
|
||||
// Commands
|
||||
this.addCommand({
|
||||
id: 'znote-sync:sync',
|
||||
id: 'sonicnote-sync:sync',
|
||||
name: '同步录音',
|
||||
callback: () => this.triggerSync(),
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: 'znote-sync:login',
|
||||
id: 'sonicnote-sync:login',
|
||||
name: '登录',
|
||||
callback: () => {
|
||||
// @ts-ignore - internal API to open settings
|
||||
this.app.setting.open();
|
||||
// @ts-ignore
|
||||
this.app.setting.openTabById('znote-sync');
|
||||
this.app.setting.openTabById('sonicnote-sync');
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: 'znote-sync:logout',
|
||||
id: 'sonicnote-sync:logout',
|
||||
name: '登出',
|
||||
callback: async () => {
|
||||
this.settings.token = '';
|
||||
this.settings.phoneNumber = '';
|
||||
await this.saveSettings();
|
||||
new Notice('已登出 Znote');
|
||||
new Notice('已登出 SonicNote');
|
||||
this.updateStatusBar();
|
||||
},
|
||||
});
|
||||
|
||||
// Settings tab
|
||||
this.addSettingTab(new ZnoteSettingTab(
|
||||
this.addSettingTab(new SonicNoteSettingTab(
|
||||
this.app,
|
||||
this,
|
||||
this.api,
|
||||
|
|
@ -70,11 +70,11 @@ export default class ZnoteSyncPlugin extends Plugin {
|
|||
() => this.saveSettings()
|
||||
));
|
||||
|
||||
console.log('Znote Sync plugin loaded');
|
||||
console.log('SonicNote Sync plugin loaded');
|
||||
}
|
||||
|
||||
onunload() {
|
||||
console.log('Znote Sync plugin unloaded');
|
||||
console.log('SonicNote Sync plugin unloaded');
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
|
|
@ -87,15 +87,15 @@ export default class ZnoteSyncPlugin extends Plugin {
|
|||
|
||||
private async triggerSync() {
|
||||
if (!this.api.isAuthenticated()) {
|
||||
new Notice('请先登录 Znote(设置 → Znote Sync)');
|
||||
new Notice('请先登录 SonicNote(设置 → SonicNote Sync)');
|
||||
return;
|
||||
}
|
||||
|
||||
this.statusBarEl.setText('Znote: 同步中...');
|
||||
this.statusBarEl.setText('SonicNote: 同步中...');
|
||||
|
||||
try {
|
||||
const result = await this.syncService.syncAll((msg) => {
|
||||
this.statusBarEl.setText(`Znote: ${msg}`);
|
||||
this.statusBarEl.setText(`SonicNote: ${msg}`);
|
||||
});
|
||||
|
||||
let message = `同步完成: ${result.synced} 条新/更新`;
|
||||
|
|
@ -115,9 +115,9 @@ export default class ZnoteSyncPlugin extends Plugin {
|
|||
const lastSync = this.settings.lastSyncTime
|
||||
? `上次同步: ${this.settings.lastSyncTime.substring(0, 16).replace('T', ' ')}`
|
||||
: '未同步';
|
||||
this.statusBarEl.setText(`Znote: ${lastSync}`);
|
||||
this.statusBarEl.setText(`SonicNote: ${lastSync}`);
|
||||
} else {
|
||||
this.statusBarEl.setText('Znote: 未登录');
|
||||
this.statusBarEl.setText('SonicNote: 未登录');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import { App, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
||||
import { ZnoteApiClient } from './api';
|
||||
import { ZnotePluginSettings, DEFAULT_SETTINGS } from './types';
|
||||
import { SonicNoteApiClient } from './api';
|
||||
import { SonicNotePluginSettings, DEFAULT_SETTINGS } from './types';
|
||||
|
||||
export class ZnoteSettingTab extends PluginSettingTab {
|
||||
private api: ZnoteApiClient;
|
||||
private getSettings: () => ZnotePluginSettings;
|
||||
export class SonicNoteSettingTab extends PluginSettingTab {
|
||||
private api: SonicNoteApiClient;
|
||||
private getSettings: () => SonicNotePluginSettings;
|
||||
private saveSettings: () => Promise<void>;
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
plugin: Plugin,
|
||||
api: ZnoteApiClient,
|
||||
getSettings: () => ZnotePluginSettings,
|
||||
api: SonicNoteApiClient,
|
||||
getSettings: () => SonicNotePluginSettings,
|
||||
saveSettings: () => Promise<void>
|
||||
) {
|
||||
super(app, plugin);
|
||||
|
|
@ -25,26 +25,14 @@ export class ZnoteSettingTab extends PluginSettingTab {
|
|||
const settings = this.getSettings();
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl('h2', { text: 'Znote Sync 设置' });
|
||||
|
||||
// Server URL
|
||||
new Setting(containerEl)
|
||||
.setName('服务器地址')
|
||||
.setDesc('智音笔记后端服务地址')
|
||||
.addText(text => text
|
||||
.setPlaceholder('http://localhost:8048')
|
||||
.setValue(settings.serverUrl)
|
||||
.onChange(async (value) => {
|
||||
settings.serverUrl = value;
|
||||
await this.saveSettings();
|
||||
}));
|
||||
containerEl.createEl('h2', { text: 'SonicNote Sync 设置' });
|
||||
|
||||
// Sync folder
|
||||
new Setting(containerEl)
|
||||
.setName('同步文件夹')
|
||||
.setDesc('录音 Markdown 文件存放的文件夹(相对于 Vault 根目录)')
|
||||
.addText(text => text
|
||||
.setPlaceholder('ZnoteSync')
|
||||
.setPlaceholder('SonicNoteSync')
|
||||
.setValue(settings.syncFolder)
|
||||
.onChange(async (value) => {
|
||||
settings.syncFolder = value;
|
||||
|
|
@ -83,15 +71,15 @@ export class ZnoteSettingTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
class LoginModal extends Modal {
|
||||
private api: ZnoteApiClient;
|
||||
private getSettings: () => ZnotePluginSettings;
|
||||
private api: SonicNoteApiClient;
|
||||
private getSettings: () => SonicNotePluginSettings;
|
||||
private saveSettings: () => Promise<void>;
|
||||
private onCloseCallback: () => void;
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
api: ZnoteApiClient,
|
||||
getSettings: () => ZnotePluginSettings,
|
||||
api: SonicNoteApiClient,
|
||||
getSettings: () => SonicNotePluginSettings,
|
||||
saveSettings: () => Promise<void>,
|
||||
onCloseCallback: () => void
|
||||
) {
|
||||
|
|
@ -106,7 +94,7 @@ class LoginModal extends Modal {
|
|||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
|
||||
contentEl.createEl('h2', { text: '登录智音笔记' });
|
||||
contentEl.createEl('h2', { text: '登录 SonicNote' });
|
||||
|
||||
let phone = '';
|
||||
let code = '';
|
||||
|
|
@ -118,9 +106,10 @@ class LoginModal extends Modal {
|
|||
.onChange((value) => { phone = value; }));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('验证码')
|
||||
.setName('密码')
|
||||
.setDesc('使用 APP 验证码或者申领长期密码')
|
||||
.addText(text => text
|
||||
.setPlaceholder('请输入验证码')
|
||||
.setPlaceholder('请输入密码')
|
||||
.onChange((value) => { code = value; }));
|
||||
|
||||
new Setting(contentEl)
|
||||
|
|
@ -129,7 +118,7 @@ class LoginModal extends Modal {
|
|||
.setCta()
|
||||
.onClick(async () => {
|
||||
if (!phone || !code) {
|
||||
new Notice('请输入手机号和验证码');
|
||||
new Notice('请输入手机号和密码');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
|
|
|||
16
src/sync.ts
16
src/sync.ts
|
|
@ -1,18 +1,18 @@
|
|||
import { App, Notice, TFile, TFolder } from 'obsidian';
|
||||
import { ZnoteApiClient } from './api';
|
||||
import { ZnotePluginSettings, LocalFileInfo, Recording, SyncResult, TranscriptSegment, SummaryData } from './types';
|
||||
import { SonicNoteApiClient } from './api';
|
||||
import { SonicNotePluginSettings, LocalFileInfo, Recording, SyncResult, TranscriptSegment, SummaryData } from './types';
|
||||
import { formatFileName, toMarkdown } from './formatter';
|
||||
|
||||
export class SyncService {
|
||||
private api: ZnoteApiClient;
|
||||
private api: SonicNoteApiClient;
|
||||
private app: App;
|
||||
private getSettings: () => ZnotePluginSettings;
|
||||
private getSettings: () => SonicNotePluginSettings;
|
||||
private saveSettings: () => Promise<void>;
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
api: ZnoteApiClient,
|
||||
getSettings: () => ZnotePluginSettings,
|
||||
api: SonicNoteApiClient,
|
||||
getSettings: () => SonicNotePluginSettings,
|
||||
saveSettings: () => Promise<void>
|
||||
) {
|
||||
this.app = app;
|
||||
|
|
@ -113,7 +113,7 @@ export class SyncService {
|
|||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
private async processRecording(recording: Recording, localIndex: Map<string, LocalFileInfo>, settings: ZnotePluginSettings): Promise<void> {
|
||||
private async processRecording(recording: Recording, localIndex: Map<string, LocalFileInfo>, settings: SonicNotePluginSettings): Promise<void> {
|
||||
const syncTime = new Date().toISOString();
|
||||
|
||||
// Check if we need to update
|
||||
|
|
@ -150,7 +150,7 @@ export class SyncService {
|
|||
});
|
||||
}
|
||||
|
||||
private async writeRecordingToNewFile(recording: Recording, syncTime: string, settings: ZnotePluginSettings, conflictWithPath?: string): Promise<void> {
|
||||
private async writeRecordingToNewFile(recording: Recording, syncTime: string, settings: SonicNotePluginSettings, conflictWithPath?: string): Promise<void> {
|
||||
const content = await this.buildRecordingContent(recording, syncTime);
|
||||
let fileName = formatFileName(recording);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// ===== 插件设置 =====
|
||||
|
||||
export interface ZnotePluginSettings {
|
||||
export interface SonicNotePluginSettings {
|
||||
serverUrl: string;
|
||||
syncFolder: string;
|
||||
pageSize: number;
|
||||
|
|
@ -9,9 +9,9 @@ export interface ZnotePluginSettings {
|
|||
lastSyncTime: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: ZnotePluginSettings = {
|
||||
serverUrl: 'http://ainote.easylinkin.com:8048',
|
||||
syncFolder: 'ZnoteSync',
|
||||
export const DEFAULT_SETTINGS: SonicNotePluginSettings = {
|
||||
serverUrl: 'https://ainote.easylinkin.com:18048/prod-api',
|
||||
syncFolder: 'SonicNoteSync',
|
||||
pageSize: 50,
|
||||
token: '',
|
||||
phoneNumber: '',
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
/* Znote Sync Plugin Styles */
|
||||
/* SonicNote Sync Plugin Styles */
|
||||
|
||||
/* Login modal */
|
||||
.znote-login-modal .setting-item {
|
||||
.sonicnote-login-modal .setting-item {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.znote-login-modal .setting-item-control input {
|
||||
.sonicnote-login-modal .setting-item-control input {
|
||||
width: 200px;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue