|
@@ -10,21 +10,16 @@ const FALLBACK_SETTINGS = {
|
|
|
autostart: true,
|
|
autostart: true,
|
|
|
alwaysOnTop: false,
|
|
alwaysOnTop: false,
|
|
|
hotkey: 'CmdOrCtrl+Shift+G',
|
|
hotkey: 'CmdOrCtrl+Shift+G',
|
|
|
- notesUrl: 'https://m.scwy.net',
|
|
|
|
|
- controlUrl: 'https://a7.scwy.net/4191',
|
|
|
|
|
- blogUrl: 'https://i.scwy.net',
|
|
|
|
|
- chatUrl: 'https://c.scwy.net',
|
|
|
|
|
- tabOrder: ['notes', 'control', 'blog', 'chat', 'links', 'news', 'github', 'stock', 'record', 'hardware', 'host', 'settings'],
|
|
|
|
|
|
|
+ tabOrder: ['links', 'news', 'github', 'stock', 'todo', 'tools', 'host', 'settings'],
|
|
|
stockCodes: ['sh000001', 'sh600519', 'sz000001'],
|
|
stockCodes: ['sh000001', 'sh600519', 'sz000001'],
|
|
|
links: [{name: '煎蛋', url: 'https://jandan.net/'}],
|
|
links: [{name: '煎蛋', url: 'https://jandan.net/'}],
|
|
|
newsKeywords: '',
|
|
newsKeywords: '',
|
|
|
biliCookie: '', // B站登录 cookie(含 SESSDATA),用于抓取「关注的人最新动态」
|
|
biliCookie: '', // B站登录 cookie(含 SESSDATA),用于抓取「关注的人最新动态」
|
|
|
githubRepos: [{url: 'https://github.com/wailsapp/wails', desc: 'Wails 框架'}],
|
|
githubRepos: [{url: 'https://github.com/wailsapp/wails', desc: 'Wails 框架'}],
|
|
|
- customTabs: [], // 用户自定义 Tab 页面(名字 + 网址)
|
|
|
|
|
|
|
+ customTabs: [], // 用户自定义 Tab 页面(名字 + 地址;http/https 为网页,其余为本地文件)
|
|
|
apiAddr: '127.0.0.1:17890',
|
|
apiAddr: '127.0.0.1:17890',
|
|
|
- recordPath: 'D:\\My\\腾图\\记录.txt',
|
|
|
|
|
- todoPath: 'D:\\My\\腾图\\待办.txt',
|
|
|
|
|
- hardwareUrl: 'http://192.168.100.190/',
|
|
|
|
|
|
|
+ todoPath: '', // 待办路径:无默认值,空值提示用户到设置页配置
|
|
|
|
|
+ backupDir: '', // 备份目录:无默认值,空值提示用户到设置页配置
|
|
|
stockUpColor: '#ff5a5a', // 股票上涨颜色(红涨)
|
|
stockUpColor: '#ff5a5a', // 股票上涨颜色(红涨)
|
|
|
stockDownColor: '#4d8dff', // 股票下跌颜色(蓝跌)
|
|
stockDownColor: '#4d8dff', // 股票下跌颜色(蓝跌)
|
|
|
stockRefreshSecs: 30, // 股市自动刷新间隔(秒)
|
|
stockRefreshSecs: 30, // 股市自动刷新间隔(秒)
|
|
@@ -61,24 +56,17 @@ const poemReadBtn = document.getElementById('poem-read'); // 朗读按
|
|
|
|
|
|
|
|
// Tab 名称映射:id -> 显示名(用于顺序编辑与 footer 注释)
|
|
// Tab 名称映射:id -> 显示名(用于顺序编辑与 footer 注释)
|
|
|
const TAB_NAMES = {
|
|
const TAB_NAMES = {
|
|
|
- notes: '笔记', control: '控制', blog: '博客', chat: '聊天',
|
|
|
|
|
links: '网址', news: '新闻', github: 'Github', stock: '股市',
|
|
links: '网址', news: '新闻', github: 'Github', stock: '股市',
|
|
|
- record: '记录', todo: '待办', hardware: '硬件', tools: '工具', host: '服务', settings: '设置',
|
|
|
|
|
|
|
+ todo: '待办', tools: '工具', host: '服务', settings: '设置',
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// Tab 注释映射:id -> 在 footer 显示的说明文字(切换时显示,不占页面空间)
|
|
// Tab 注释映射:id -> 在 footer 显示的说明文字(切换时显示,不占页面空间)
|
|
|
const TAB_DESCS = {
|
|
const TAB_DESCS = {
|
|
|
- notes: '记录灵感与备忘,地址可在设置中修改。',
|
|
|
|
|
- control: '远程控制中心,地址可在设置中修改。',
|
|
|
|
|
- blog: '个人博客,地址可在设置中修改。',
|
|
|
|
|
- chat: '聊天页面,地址可在设置中修改。',
|
|
|
|
|
links: '常用网址卡片,点击在浏览器打开,列表在设置中维护。',
|
|
links: '常用网址卡片,点击在浏览器打开,列表在设置中维护。',
|
|
|
news: '聚合煎蛋与头条热榜,命中关键词置顶高亮。',
|
|
news: '聚合煎蛋与头条热榜,命中关键词置顶高亮。',
|
|
|
github: '监听仓库提交与 release,本地版本低于最新版提示更新。',
|
|
github: '监听仓库提交与 release,本地版本低于最新版提示更新。',
|
|
|
stock: '自选股实时行情,点击行查看分时 / 日K。',
|
|
stock: '自选股实时行情,点击行查看分时 / 日K。',
|
|
|
- record: '记录零碎信息,文件路径可在设置中修改。',
|
|
|
|
|
todo: '每行一个待办任务,可勾选完成、编辑、删除,保存后写入本地文件。',
|
|
todo: '每行一个待办任务,可勾选完成、编辑、删除,保存后写入本地文件。',
|
|
|
- hardware: '硬件监控页面,地址可在设置中修改。',
|
|
|
|
|
tools: '开发/运维辅助工具:Web 服务(选目录开启浏览)、翻译(中英互译)、二维码生成。',
|
|
tools: '开发/运维辅助工具:Web 服务(选目录开启浏览)、翻译(中英互译)、二维码生成。',
|
|
|
host: '内置 API 服务状态、电源命令与 Gin 日志。',
|
|
host: '内置 API 服务状态、电源命令与 Gin 日志。',
|
|
|
settings: '修改后点击保存立即生效,焦点离开时自动保存。',
|
|
settings: '修改后点击保存立即生效,焦点离开时自动保存。',
|
|
@@ -93,11 +81,17 @@ function getTabName(id) {
|
|
|
return c ? c.name : id;
|
|
return c ? c.name : id;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 取 Tab 说明:优先内置 TAB_DESCS,自定义 Tab 用页面地址
|
|
|
|
|
|
|
+// 取 Tab 说明:优先内置 TAB_DESCS,自定义 Tab 用备注(desc),无备注时回退页面地址
|
|
|
function getTabDesc(id) {
|
|
function getTabDesc(id) {
|
|
|
if (TAB_DESCS[id]) return TAB_DESCS[id];
|
|
if (TAB_DESCS[id]) return TAB_DESCS[id];
|
|
|
const c = customTabs.find(t => t.id === id);
|
|
const c = customTabs.find(t => t.id === id);
|
|
|
- return c ? `自定义页面:${c.url}` : '';
|
|
|
|
|
|
|
+ if (!c) return '';
|
|
|
|
|
+ return c.desc ? `自定义页面:${c.desc}` : `自定义页面:${c.url}`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 判断地址是否为网页(http/https 开头),否则按本地文件处理
|
|
|
|
|
+function isWebUrl(url) {
|
|
|
|
|
+ return /^https?:\/\//i.test(url || '');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 删除旧的自定义 Tab 按钮与面板,再按 settings.customTabs 重建(插入到「设置」Tab 之前)
|
|
// 删除旧的自定义 Tab 按钮与面板,再按 settings.customTabs 重建(插入到「设置」Tab 之前)
|
|
@@ -111,7 +105,7 @@ async function applyCustomTabs(tabs) {
|
|
|
// 重建自定义 Tab(按钮插入到设置按钮之前,面板插入到设置面板之前)
|
|
// 重建自定义 Tab(按钮插入到设置按钮之前,面板插入到设置面板之前)
|
|
|
const settingsBtn = tabBar.querySelector('.tab-btn[data-tab="settings"]');
|
|
const settingsBtn = tabBar.querySelector('.tab-btn[data-tab="settings"]');
|
|
|
const settingsPanel = content.querySelector('#tab-settings');
|
|
const settingsPanel = content.querySelector('#tab-settings');
|
|
|
- customTabs = (tabs || []).map((t, i) => ({ id: `custom-${i}`, name: t.name, url: t.url }));
|
|
|
|
|
|
|
+ customTabs = (tabs || []).map((t, i) => ({ id: `custom-${i}`, name: t.name, url: t.url, desc: t.desc || '', isWeb: isWebUrl(t.url) }));
|
|
|
for (const t of customTabs) {
|
|
for (const t of customTabs) {
|
|
|
const btn = document.createElement('button');
|
|
const btn = document.createElement('button');
|
|
|
btn.className = 'tab-btn';
|
|
btn.className = 'tab-btn';
|
|
@@ -122,28 +116,100 @@ async function applyCustomTabs(tabs) {
|
|
|
btn.addEventListener('click', () => switchTab(t.id)); // 点击切换
|
|
btn.addEventListener('click', () => switchTab(t.id)); // 点击切换
|
|
|
|
|
|
|
|
const panel = document.createElement('div');
|
|
const panel = document.createElement('div');
|
|
|
- panel.className = 'tab-panel panel-embed panel-custom';
|
|
|
|
|
|
|
+ panel.className = t.isWeb ? 'tab-panel panel-embed panel-custom' : 'tab-panel panel-custom panel-custom-file';
|
|
|
panel.id = `tab-${t.id}`;
|
|
panel.id = `tab-${t.id}`;
|
|
|
panel.dataset.custom = '1';
|
|
panel.dataset.custom = '1';
|
|
|
- // 常驻工具条:在新窗口打开(内网自签名 / 禁止嵌入的站点可兜底打开)
|
|
|
|
|
- const bar = document.createElement('div');
|
|
|
|
|
- bar.className = 'custom-tab-bar';
|
|
|
|
|
- const openBtn = document.createElement('button');
|
|
|
|
|
- openBtn.className = 'btn-ghost';
|
|
|
|
|
- openBtn.textContent = '在新窗口打开';
|
|
|
|
|
- openBtn.title = t.url;
|
|
|
|
|
- openBtn.addEventListener('click', (e) => {
|
|
|
|
|
- e.stopPropagation(); // 不触发 Tab 切换
|
|
|
|
|
- window.open(t.url, '_blank'); // 用系统浏览器打开(浏览器自会提示证书)
|
|
|
|
|
- });
|
|
|
|
|
- bar.appendChild(openBtn);
|
|
|
|
|
- const frame = document.createElement('iframe');
|
|
|
|
|
- frame.id = `frame-${t.id}`;
|
|
|
|
|
- frame.src = embedSrc(t.url); // 直接使用原地址(已取消代理)
|
|
|
|
|
- frame.title = t.name;
|
|
|
|
|
- frame.setAttribute('loading', 'lazy');
|
|
|
|
|
- panel.appendChild(bar);
|
|
|
|
|
- panel.appendChild(frame);
|
|
|
|
|
|
|
+ if (t.isWeb) {
|
|
|
|
|
+ // 网页类型:内嵌 iframe,不显示工具条(已取消「在新窗口打开」)
|
|
|
|
|
+ const frame = document.createElement('iframe');
|
|
|
|
|
+ frame.id = `frame-${t.id}`;
|
|
|
|
|
+ frame.src = embedSrc(t.url); // 直接使用原地址(已取消代理)
|
|
|
|
|
+ frame.title = t.name;
|
|
|
|
|
+ frame.setAttribute('loading', 'lazy');
|
|
|
|
|
+ panel.appendChild(frame);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 文件类型:工具条(编辑/预览切换 + 保存 + 刷新)+ 编辑框 + Markdown 预览容器
|
|
|
|
|
+ const bar = document.createElement('div');
|
|
|
|
|
+ bar.className = 'custom-tab-bar';
|
|
|
|
|
+
|
|
|
|
|
+ const modeGroup = document.createElement('div');
|
|
|
|
|
+ modeGroup.className = 'custom-file-mode';
|
|
|
|
|
+ const editBtn = document.createElement('button');
|
|
|
|
|
+ editBtn.className = 'custom-file-mode-btn is-active';
|
|
|
|
|
+ editBtn.textContent = '编辑';
|
|
|
|
|
+ const previewBtn = document.createElement('button');
|
|
|
|
|
+ previewBtn.className = 'custom-file-mode-btn';
|
|
|
|
|
+ previewBtn.textContent = '预览';
|
|
|
|
|
+ modeGroup.appendChild(editBtn);
|
|
|
|
|
+ modeGroup.appendChild(previewBtn);
|
|
|
|
|
+
|
|
|
|
|
+ const saveBtn = document.createElement('button');
|
|
|
|
|
+ saveBtn.className = 'btn-ghost';
|
|
|
|
|
+ saveBtn.textContent = '保存';
|
|
|
|
|
+ saveBtn.addEventListener('click', (e) => {
|
|
|
|
|
+ e.stopPropagation();
|
|
|
|
|
+ saveCustomFileTab(t);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const refreshBtn = document.createElement('button');
|
|
|
|
|
+ refreshBtn.className = 'btn-ghost';
|
|
|
|
|
+ refreshBtn.textContent = '刷新';
|
|
|
|
|
+ refreshBtn.title = t.url;
|
|
|
|
|
+ refreshBtn.addEventListener('click', (e) => {
|
|
|
|
|
+ e.stopPropagation();
|
|
|
|
|
+ loadCustomFileTab(t);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const backupBtn = document.createElement('button');
|
|
|
|
|
+ backupBtn.className = 'btn-ghost';
|
|
|
|
|
+ backupBtn.textContent = '备份';
|
|
|
|
|
+ backupBtn.title = '把当前文件按时间备份到「设置 → 文本备份目录」';
|
|
|
|
|
+ backupBtn.addEventListener('click', (e) => {
|
|
|
|
|
+ e.stopPropagation();
|
|
|
|
|
+ backupCustomFileTab(t);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ bar.appendChild(modeGroup);
|
|
|
|
|
+ // 状态提示(保存/备份/刷新结果),margin-right:auto 推到最左,编辑模式下也可见
|
|
|
|
|
+ const status = document.createElement('span');
|
|
|
|
|
+ status.className = 'custom-file-status';
|
|
|
|
|
+ status.id = `status-${t.id}`;
|
|
|
|
|
+ bar.appendChild(status);
|
|
|
|
|
+ bar.appendChild(saveBtn);
|
|
|
|
|
+ bar.appendChild(refreshBtn);
|
|
|
|
|
+ bar.appendChild(backupBtn);
|
|
|
|
|
+ panel.appendChild(bar);
|
|
|
|
|
+
|
|
|
|
|
+ // 编辑框(Markdown 源码)
|
|
|
|
|
+ const editor = document.createElement('textarea');
|
|
|
|
|
+ editor.className = 'custom-file-editor';
|
|
|
|
|
+ editor.id = `editor-${t.id}`;
|
|
|
|
|
+ editor.spellcheck = false;
|
|
|
|
|
+ editor.placeholder = '在这里编辑内容…(支持 Markdown,可切换预览查看效果)';
|
|
|
|
|
+ panel.appendChild(editor);
|
|
|
|
|
+
|
|
|
|
|
+ // 预览容器(初始隐藏,仅点击「预览」时显示,与编辑按钮默认高亮一致)
|
|
|
|
|
+ const md = document.createElement('div');
|
|
|
|
|
+ md.className = 'markdown-body custom-file-md';
|
|
|
|
|
+ md.id = `md-${t.id}`;
|
|
|
|
|
+ md.style.display = 'none'; // 默认编辑模式:只显示文本框,预览框隐藏
|
|
|
|
|
+ md.textContent = '点击「预览」查看 Markdown 渲染效果。';
|
|
|
|
|
+ panel.appendChild(md);
|
|
|
|
|
+
|
|
|
|
|
+ // 编辑/预览切换
|
|
|
|
|
+ const setFileMode = (mode) => {
|
|
|
|
|
+ const isEdit = mode === 'edit';
|
|
|
|
|
+ editBtn.classList.toggle('is-active', isEdit);
|
|
|
|
|
+ previewBtn.classList.toggle('is-active', !isEdit);
|
|
|
|
|
+ editor.style.display = isEdit ? '' : 'none';
|
|
|
|
|
+ md.style.display = isEdit ? 'none' : '';
|
|
|
|
|
+ if (!isEdit) refreshCustomFilePreview(t); // 进入预览时实时渲染
|
|
|
|
|
+ };
|
|
|
|
|
+ editBtn.addEventListener('click', () => setFileMode('edit'));
|
|
|
|
|
+ previewBtn.addEventListener('click', () => setFileMode('preview'));
|
|
|
|
|
+ // 编辑框为纯文本编辑:输入时不触发 Markdown 渲染(避免编辑态出现 markdown 副作用),
|
|
|
|
|
+ // 预览容器仅在切到「预览」模式时渲染(见 setFileMode)。
|
|
|
|
|
+ }
|
|
|
content.insertBefore(panel, settingsPanel);
|
|
content.insertBefore(panel, settingsPanel);
|
|
|
}
|
|
}
|
|
|
// 刷新按钮与面板引用,供 applyTabOrder / switchTab 使用
|
|
// 刷新按钮与面板引用,供 applyTabOrder / switchTab 使用
|
|
@@ -152,6 +218,71 @@ async function applyCustomTabs(tabs) {
|
|
|
return customTabs.length > 0;
|
|
return customTabs.length > 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 加载文件类自定义 Tab 内容:读取文本填入编辑框,并刷新预览
|
|
|
|
|
+async function loadCustomFileTab(t) {
|
|
|
|
|
+ const editor = document.getElementById(`editor-${t.id}`);
|
|
|
|
|
+ const md = document.getElementById(`md-${t.id}`);
|
|
|
|
|
+ if (!editor || !md) return;
|
|
|
|
|
+ setCustomFileStatus(t, '加载中…', true);
|
|
|
|
|
+ try {
|
|
|
|
|
+ const text = await SettingsService.ReadTextFile(t.url);
|
|
|
|
|
+ editor.value = text;
|
|
|
|
|
+ md.innerHTML = text.trim() ? await SettingsService.RenderMarkdown(text) : '<p style="opacity:.6">(文件为空)</p>';
|
|
|
|
|
+ setCustomFileStatus(t, `已加载 ${new Date().toLocaleTimeString('zh-CN')}`, true);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ md.innerHTML = `<p class="custom-file-error">读取失败:${escapeHtml(String(err && err.message || err))}</p>`;
|
|
|
|
|
+ setCustomFileStatus(t, `读取失败:${err && err.message || err}`, false);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 用编辑框当前内容刷新 Markdown 预览
|
|
|
|
|
+async function refreshCustomFilePreview(t) {
|
|
|
|
|
+ const editor = document.getElementById(`editor-${t.id}`);
|
|
|
|
|
+ const md = document.getElementById(`md-${t.id}`);
|
|
|
|
|
+ if (!editor || !md) return;
|
|
|
|
|
+ const text = editor.value;
|
|
|
|
|
+ try {
|
|
|
|
|
+ md.innerHTML = text.trim() ? await SettingsService.RenderMarkdown(text) : '<p style="opacity:.6">(文件为空)</p>';
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ md.innerHTML = `<p class="custom-file-error">预览失败:${escapeHtml(String(err && err.message || err))}</p>`;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 在文件类自定义 Tab 工具条的状态区显示信息(编辑模式下也可见)
|
|
|
|
|
+function setCustomFileStatus(t, msg, ok = true) {
|
|
|
|
|
+ const status = document.getElementById(`status-${t.id}`);
|
|
|
|
|
+ if (!status) return;
|
|
|
|
|
+ status.textContent = msg;
|
|
|
|
|
+ status.classList.toggle('is-ok', ok);
|
|
|
|
|
+ status.classList.toggle('is-err', !ok);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 保存文件类自定义 Tab 的编辑框内容到本地文件
|
|
|
|
|
+async function saveCustomFileTab(t) {
|
|
|
|
|
+ const editor = document.getElementById(`editor-${t.id}`);
|
|
|
|
|
+ if (!editor) return;
|
|
|
|
|
+ setCustomFileStatus(t, '保存中…', true);
|
|
|
|
|
+ try {
|
|
|
|
|
+ await SettingsService.WriteTextFile(t.url, editor.value);
|
|
|
|
|
+ setCustomFileStatus(t, `已保存 ${new Date().toLocaleTimeString('zh-CN')}`, true);
|
|
|
|
|
+ const md = document.getElementById(`md-${t.id}`);
|
|
|
|
|
+ if (md) md.innerHTML = editor.value.trim() ? await SettingsService.RenderMarkdown(editor.value) : '<p style="opacity:.6">(文件为空)</p>';
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ setCustomFileStatus(t, `保存失败:${err && err.message || err}`, false);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 备份文件类自定义 Tab 的源文件:按当前时间备份到「设置 → 文本备份目录」,文件名带时间戳
|
|
|
|
|
+async function backupCustomFileTab(t) {
|
|
|
|
|
+ setCustomFileStatus(t, '备份中…', true);
|
|
|
|
|
+ try {
|
|
|
|
|
+ const dst = await SettingsService.BackupFile(t.url);
|
|
|
|
|
+ setCustomFileStatus(t, `已备份:${dst}`, true);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ setCustomFileStatus(t, `备份失败:${err && err.message || err}`, false);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// 应用 Tab 显示顺序:按 settings.tabOrder 重排按钮与面板
|
|
// 应用 Tab 显示顺序:按 settings.tabOrder 重排按钮与面板
|
|
|
function applyTabOrder(order) {
|
|
function applyTabOrder(order) {
|
|
|
const valid = new Set([...Object.keys(TAB_NAMES), ...customTabs.map(t => t.id)]); // 含内置与自定义 Tab
|
|
const valid = new Set([...Object.keys(TAB_NAMES), ...customTabs.map(t => t.id)]); // 含内置与自定义 Tab
|
|
@@ -225,11 +356,13 @@ function switchTab(tabId) {
|
|
|
// 部分面板首次打开时才加载数据
|
|
// 部分面板首次打开时才加载数据
|
|
|
if (!loadedOnce.has(tabId)) {
|
|
if (!loadedOnce.has(tabId)) {
|
|
|
loadedOnce.add(tabId);
|
|
loadedOnce.add(tabId);
|
|
|
- if (tabId === 'record') loadRecord();
|
|
|
|
|
if (tabId === 'todo') loadTodo();
|
|
if (tabId === 'todo') loadTodo();
|
|
|
if (tabId === 'host') loadHostPanel();
|
|
if (tabId === 'host') loadHostPanel();
|
|
|
if (tabId === 'tools') loadToolsPanel();
|
|
if (tabId === 'tools') loadToolsPanel();
|
|
|
}
|
|
}
|
|
|
|
|
+ // 文件类自定义 Tab:首次打开时自动读取并渲染 Markdown
|
|
|
|
|
+ const custom = customTabs.find(t => t.id === tabId);
|
|
|
|
|
+ if (custom && !custom.isWeb) loadCustomFileTab(custom);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
tabButtons.forEach(btn => {
|
|
tabButtons.forEach(btn => {
|
|
@@ -269,21 +402,16 @@ document.querySelectorAll('#theme-seg .seg-btn').forEach(btn => {
|
|
|
const autostartToggle = document.getElementById('autostart'); // 开机启动开关
|
|
const autostartToggle = document.getElementById('autostart'); // 开机启动开关
|
|
|
const topToggle = document.getElementById('always-on-top'); // 窗口置顶开关
|
|
const topToggle = document.getElementById('always-on-top'); // 窗口置顶开关
|
|
|
const hotkeyInput = document.getElementById('hotkey'); // 热键输入框
|
|
const hotkeyInput = document.getElementById('hotkey'); // 热键输入框
|
|
|
-const notesUrlInput = document.getElementById('notes-url'); // 笔记页地址
|
|
|
|
|
-const controlUrlInput = document.getElementById('control-url'); // 控制页地址
|
|
|
|
|
-const blogUrlInput = document.getElementById('blog-url'); // 博客页地址
|
|
|
|
|
-const chatUrlInput = document.getElementById('chat-url'); // 聊天页地址
|
|
|
|
|
const tabOrderText = document.getElementById('tab-order'); // Tab 显示顺序(每行一个 id)
|
|
const tabOrderText = document.getElementById('tab-order'); // Tab 显示顺序(每行一个 id)
|
|
|
-const hardwareUrlInput = document.getElementById('hardware-url'); // 硬件页地址
|
|
|
|
|
const stockCodesInput = document.getElementById('stock-codes'); // 股票代码
|
|
const stockCodesInput = document.getElementById('stock-codes'); // 股票代码
|
|
|
const linksText = document.getElementById('links-text'); // 常用网址(每行 名称|网址)
|
|
const linksText = document.getElementById('links-text'); // 常用网址(每行 名称|网址)
|
|
|
const newsKeywordsInput = document.getElementById('news-keywords'); // 新闻关键词
|
|
const newsKeywordsInput = document.getElementById('news-keywords'); // 新闻关键词
|
|
|
const biliCookieInput = document.getElementById('bili-cookie'); // B站登录 cookie
|
|
const biliCookieInput = document.getElementById('bili-cookie'); // B站登录 cookie
|
|
|
const githubReposText = document.getElementById('github-repos'); // Github 项目(每行 地址|说明)
|
|
const githubReposText = document.getElementById('github-repos'); // Github 项目(每行 地址|说明)
|
|
|
const apiAddrInput = document.getElementById('api-addr'); // API 监听地址
|
|
const apiAddrInput = document.getElementById('api-addr'); // API 监听地址
|
|
|
-const recordPathInput = document.getElementById('record-path'); // 记录文件路径
|
|
|
|
|
const todoPathInput = document.getElementById('todo-path'); // 待办(TODO)文件路径
|
|
const todoPathInput = document.getElementById('todo-path'); // 待办(TODO)文件路径
|
|
|
-const customTabsText = document.getElementById('custom-tabs'); // 自定义 Tab(每行 名称|网址)
|
|
|
|
|
|
|
+const backupDirInput = document.getElementById('backup-dir'); // 文本备份目录(文件类自定义 Tab「备份」按钮使用)
|
|
|
|
|
+const customTabsText = document.getElementById('custom-tabs'); // 自定义 Tab(每行 名称|地址|备注)
|
|
|
const saveButton = document.getElementById('save-settings'); // 保存按钮
|
|
const saveButton = document.getElementById('save-settings'); // 保存按钮
|
|
|
const saveStatus = document.getElementById('save-status'); // 保存状态提示
|
|
const saveStatus = document.getElementById('save-status'); // 保存状态提示
|
|
|
const stockList = document.getElementById('stock-list'); // 股市列表容器
|
|
const stockList = document.getElementById('stock-list'); // 股市列表容器
|
|
@@ -376,21 +504,27 @@ async function syncForm(settings) {
|
|
|
autostartToggle.checked = settings.autostart;
|
|
autostartToggle.checked = settings.autostart;
|
|
|
topToggle.checked = settings.alwaysOnTop;
|
|
topToggle.checked = settings.alwaysOnTop;
|
|
|
hotkeyInput.value = settings.hotkey;
|
|
hotkeyInput.value = settings.hotkey;
|
|
|
- notesUrlInput.value = settings.notesUrl;
|
|
|
|
|
- controlUrlInput.value = settings.controlUrl;
|
|
|
|
|
- blogUrlInput.value = settings.blogUrl;
|
|
|
|
|
- chatUrlInput.value = settings.chatUrl;
|
|
|
|
|
- tabOrderText.value = (settings.tabOrder || []).map(id => getTabName(id)).join(', '); // 顺序编辑显示 Tab 名称(逗号分隔)
|
|
|
|
|
- hardwareUrlInput.value = settings.hardwareUrl;
|
|
|
|
|
|
|
+ // 先重建自定义 Tab(供顺序文本框解析名称与 applyTabOrder 使用)
|
|
|
|
|
+ await applyCustomTabs(settings.customTabs);
|
|
|
|
|
+ // 完整 Tab 顺序文本:内置 + 自定义,按 settings.tabOrder 排序,未列出的追加(设置恒在最后)
|
|
|
|
|
+ const builtinIds = Object.keys(TAB_NAMES).filter(id => id !== 'settings');
|
|
|
|
|
+ const customIds = customTabs.map(t => t.id);
|
|
|
|
|
+ const allTabIds = [...builtinIds, ...customIds];
|
|
|
|
|
+ const tabOrder = settings.tabOrder || [];
|
|
|
|
|
+ const orderedIds = [...new Set([
|
|
|
|
|
+ ...tabOrder.filter(id => allTabIds.includes(id)),
|
|
|
|
|
+ ...allTabIds.filter(id => !tabOrder.includes(id)),
|
|
|
|
|
+ ])];
|
|
|
|
|
+ tabOrderText.value = orderedIds.map(id => getTabName(id)).join(', '); // 顺序编辑显示 Tab 名称(逗号分隔)
|
|
|
stockCodesInput.value = settings.stockCodes.join(', ');
|
|
stockCodesInput.value = settings.stockCodes.join(', ');
|
|
|
linksText.value = (settings.links || []).map(l => `${l.name}|${l.url}`).join('\n');
|
|
linksText.value = (settings.links || []).map(l => `${l.name}|${l.url}`).join('\n');
|
|
|
newsKeywordsInput.value = settings.newsKeywords || '';
|
|
newsKeywordsInput.value = settings.newsKeywords || '';
|
|
|
biliCookieInput.value = settings.biliCookie || '';
|
|
biliCookieInput.value = settings.biliCookie || '';
|
|
|
githubReposText.value = (settings.githubRepos || []).map(r => `${r.url}|${r.desc}|${r.version || ''}`).join('\n');
|
|
githubReposText.value = (settings.githubRepos || []).map(r => `${r.url}|${r.desc}|${r.version || ''}`).join('\n');
|
|
|
- customTabsText.value = (settings.customTabs || []).map(t => `${t.name}|${t.url}`).join('\n');
|
|
|
|
|
|
|
+ customTabsText.value = (settings.customTabs || []).map(t => `${t.name}|${t.url}${t.desc ? '|' + t.desc : ''}`).join('\n');
|
|
|
apiAddrInput.value = settings.apiAddr;
|
|
apiAddrInput.value = settings.apiAddr;
|
|
|
- recordPathInput.value = settings.recordPath;
|
|
|
|
|
todoPathInput.value = settings.todoPath;
|
|
todoPathInput.value = settings.todoPath;
|
|
|
|
|
+ backupDirInput.value = settings.backupDir;
|
|
|
stockUpColorInput.value = settings.stockUpColor || FALLBACK_SETTINGS.stockUpColor;
|
|
stockUpColorInput.value = settings.stockUpColor || FALLBACK_SETTINGS.stockUpColor;
|
|
|
stockDownColorInput.value = settings.stockDownColor || FALLBACK_SETTINGS.stockDownColor;
|
|
stockDownColorInput.value = settings.stockDownColor || FALLBACK_SETTINGS.stockDownColor;
|
|
|
stockRefreshSecsInput.value = settings.stockRefreshSecs || FALLBACK_SETTINGS.stockRefreshSecs;
|
|
stockRefreshSecsInput.value = settings.stockRefreshSecs || FALLBACK_SETTINGS.stockRefreshSecs;
|
|
@@ -403,8 +537,13 @@ async function syncForm(settings) {
|
|
|
syncPoemForm(settings.poem);
|
|
syncPoemForm(settings.poem);
|
|
|
applyTheme(settings.theme, false);
|
|
applyTheme(settings.theme, false);
|
|
|
applyFontDelta(settings.fontDelta ?? FALLBACK_SETTINGS.fontDelta); // 应用全局字号偏移(默认 +2)
|
|
applyFontDelta(settings.fontDelta ?? FALLBACK_SETTINGS.fontDelta); // 应用全局字号偏移(默认 +2)
|
|
|
- await applyCustomTabs(settings.customTabs); // 重建自定义 Tab(按钮 + 面板)
|
|
|
|
|
applyTabOrder(settings.tabOrder); // 按设置重排 Tab 顺序
|
|
applyTabOrder(settings.tabOrder); // 按设置重排 Tab 顺序
|
|
|
|
|
+ // 启动/加载设置后,默认激活 Tab 顺序的第 1 位(若当前无激活 Tab 或激活的是旧的 links)
|
|
|
|
|
+ const firstId = (settings.tabOrder || []).find(id => id !== 'settings');
|
|
|
|
|
+ const activeId = document.querySelector('.tab-btn.active')?.dataset.tab;
|
|
|
|
|
+ if (firstId && (!activeId || activeId === 'links')) {
|
|
|
|
|
+ switchTab(firstId);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 应用全局字号偏移:写入 CSS 变量并同步步进器显示
|
|
// 应用全局字号偏移:写入 CSS 变量并同步步进器显示
|
|
@@ -449,25 +588,6 @@ function syncPoemForm(cfg) {
|
|
|
poemInputs.speed.value = cfg.speed || 18;
|
|
poemInputs.speed.value = cfg.speed || 18;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function applyEmbedUrls(settings) {
|
|
|
|
|
- const frames = [
|
|
|
|
|
- ['frame-notes', settings.notesUrl],
|
|
|
|
|
- ['frame-control', settings.controlUrl],
|
|
|
|
|
- ['frame-blog', settings.blogUrl],
|
|
|
|
|
- ['frame-chat', settings.chatUrl],
|
|
|
|
|
- ['frame-hardware', settings.hardwareUrl],
|
|
|
|
|
- ];
|
|
|
|
|
- await Promise.all(frames.map(async ([id, url]) => {
|
|
|
|
|
- const frame = document.getElementById(id);
|
|
|
|
|
- if (!frame) return;
|
|
|
|
|
- const eff = embedSrc(url); // 直接使用原地址(已取消代理)
|
|
|
|
|
- if (frame.dataset.src !== eff) {
|
|
|
|
|
- frame.dataset.src = eff; // 记录已应用的地址
|
|
|
|
|
- frame.src = eff; // 地址变化才重新加载
|
|
|
|
|
- }
|
|
|
|
|
- }));
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
// ------------------------------------------------------------------------
|
|
|
// 热键录入:点击输入框后按下组合键
|
|
// 热键录入:点击输入框后按下组合键
|
|
|
// ------------------------------------------------------------------------
|
|
// ------------------------------------------------------------------------
|
|
@@ -505,7 +625,6 @@ async function loadSettings() {
|
|
|
currentSettings = {...FALLBACK_SETTINGS};
|
|
currentSettings = {...FALLBACK_SETTINGS};
|
|
|
}
|
|
}
|
|
|
await syncForm(currentSettings);
|
|
await syncForm(currentSettings);
|
|
|
- applyEmbedUrls(currentSettings);
|
|
|
|
|
renderLinks(currentSettings.links || []);
|
|
renderLinks(currentSettings.links || []);
|
|
|
initFooterPoem(currentSettings.poem); // footer 古诗按配置显示并加载
|
|
initFooterPoem(currentSettings.poem); // footer 古诗按配置显示并加载
|
|
|
restartAutoTimers(); // 按真实设置的间隔重建自动刷新定时器
|
|
restartAutoTimers(); // 按真实设置的间隔重建自动刷新定时器
|
|
@@ -554,7 +673,7 @@ function collectForm() {
|
|
|
// LinkItem / RepoItem / CustomTab 对象:转回 "a|b" 格式行
|
|
// LinkItem / RepoItem / CustomTab 对象:转回 "a|b" 格式行
|
|
|
if (key === 'links') return `${item.name}|${item.url}`;
|
|
if (key === 'links') return `${item.name}|${item.url}`;
|
|
|
if (key === 'githubRepos') return `${item.url}|${item.desc}|${item.version || ''}`;
|
|
if (key === 'githubRepos') return `${item.url}|${item.desc}|${item.version || ''}`;
|
|
|
- if (key === 'customTabs') return `${item.name}|${item.url}`;
|
|
|
|
|
|
|
+ if (key === 'customTabs') return `${item.name}|${item.url}${item.desc ? '|' + item.desc : ''}`;
|
|
|
return '';
|
|
return '';
|
|
|
}).join('\n');
|
|
}).join('\n');
|
|
|
return '';
|
|
return '';
|
|
@@ -578,21 +697,16 @@ function collectForm() {
|
|
|
autostart: autostartToggle.checked,
|
|
autostart: autostartToggle.checked,
|
|
|
alwaysOnTop: topToggle.checked,
|
|
alwaysOnTop: topToggle.checked,
|
|
|
hotkey: hotkeyInput.value.trim() || FALLBACK_SETTINGS.hotkey,
|
|
hotkey: hotkeyInput.value.trim() || FALLBACK_SETTINGS.hotkey,
|
|
|
- notesUrl: notesUrlInput.value.trim(),
|
|
|
|
|
- controlUrl: controlUrlInput.value.trim(),
|
|
|
|
|
- blogUrl: blogUrlInput.value.trim(),
|
|
|
|
|
- chatUrl: chatUrlInput.value.trim(),
|
|
|
|
|
tabOrder: collectTabOrder(),
|
|
tabOrder: collectTabOrder(),
|
|
|
- hardwareUrl: hardwareUrlInput.value.trim(),
|
|
|
|
|
stockCodes,
|
|
stockCodes,
|
|
|
links: parsePipeLines(fallbackArr(linksText, 'links'), 'name', 'url').filter(l => l.name && l.url),
|
|
links: parsePipeLines(fallbackArr(linksText, 'links'), 'name', 'url').filter(l => l.name && l.url),
|
|
|
newsKeywords: newsKeywordsInput.value.trim(),
|
|
newsKeywords: newsKeywordsInput.value.trim(),
|
|
|
biliCookie: biliCookieInput.value.trim(),
|
|
biliCookie: biliCookieInput.value.trim(),
|
|
|
githubRepos: parsePipeLines3(fallbackArr(githubReposText, 'githubRepos'), 'url', 'desc', 'version').filter(r => r.url),
|
|
githubRepos: parsePipeLines3(fallbackArr(githubReposText, 'githubRepos'), 'url', 'desc', 'version').filter(r => r.url),
|
|
|
- customTabs: parsePipeLines(fallbackArr(customTabsText, 'customTabs'), 'name', 'url').filter(t => t.name && t.url),
|
|
|
|
|
|
|
+ customTabs: parsePipeLines3(fallbackArr(customTabsText, 'customTabs'), 'name', 'url', 'desc').filter(t => t.name && t.url),
|
|
|
apiAddr: apiAddrInput.value.trim(),
|
|
apiAddr: apiAddrInput.value.trim(),
|
|
|
- recordPath: recordPathInput.value.trim(),
|
|
|
|
|
todoPath: todoPathInput.value.trim(),
|
|
todoPath: todoPathInput.value.trim(),
|
|
|
|
|
+ backupDir: backupDirInput.value.trim(),
|
|
|
stockUpColor: fallbackColor(stockUpColorInput, currentSettings.stockUpColor, FALLBACK_SETTINGS.stockUpColor),
|
|
stockUpColor: fallbackColor(stockUpColorInput, currentSettings.stockUpColor, FALLBACK_SETTINGS.stockUpColor),
|
|
|
stockDownColor: fallbackColor(stockDownColorInput, currentSettings.stockDownColor, FALLBACK_SETTINGS.stockDownColor),
|
|
stockDownColor: fallbackColor(stockDownColorInput, currentSettings.stockDownColor, FALLBACK_SETTINGS.stockDownColor),
|
|
|
stockRefreshSecs: parseInt(stockRefreshSecsInput.value, 10) || FALLBACK_SETTINGS.stockRefreshSecs,
|
|
stockRefreshSecs: parseInt(stockRefreshSecsInput.value, 10) || FALLBACK_SETTINGS.stockRefreshSecs,
|
|
@@ -765,7 +879,7 @@ function showFooterPoem(cfg) {
|
|
|
footerMode = 'poem';
|
|
footerMode = 'poem';
|
|
|
const enabled = !!cfg?.enabled;
|
|
const enabled = !!cfg?.enabled;
|
|
|
if (!enabled) { // 未启用:回退显示当前 Tab 说明
|
|
if (!enabled) { // 未启用:回退显示当前 Tab 说明
|
|
|
- const cur = document.querySelector('.tab-btn.active')?.dataset.tab || 'notes';
|
|
|
|
|
|
|
+ const cur = document.querySelector('.tab-btn.active')?.dataset.tab || 'links';
|
|
|
showTabDesc(cur);
|
|
showTabDesc(cur);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
@@ -795,9 +909,15 @@ function initFooterPoem(cfg) {
|
|
|
|
|
|
|
|
// 从控件收集 Tab 顺序:逗号(中英文)分隔的名称,转回 tabId(含内置与自定义);未知名称忽略
|
|
// 从控件收集 Tab 顺序:逗号(中英文)分隔的名称,转回 tabId(含内置与自定义);未知名称忽略
|
|
|
function collectTabOrder() {
|
|
function collectTabOrder() {
|
|
|
|
|
+ // 基于「自定义 Tab 页面」文本框当前内容构建名称->id 映射(而非全局旧列表),
|
|
|
|
|
+ // 确保本次新增/修改的自定义 Tab 名称也能参与顺序调整。文本框为空时回退生效配置(与 fallbackArr 语义一致)。
|
|
|
|
|
+ const raw = customTabsText.value.trim();
|
|
|
|
|
+ const customNames = raw
|
|
|
|
|
+ ? parsePipeLines3(raw, 'name', 'url', 'desc').filter(t => t.name && t.url).map(t => t.name)
|
|
|
|
|
+ : (currentSettings.customTabs || []).map(t => t.name);
|
|
|
const nameToId = {};
|
|
const nameToId = {};
|
|
|
Object.entries(TAB_NAMES).forEach(([id, name]) => { nameToId[name] = id; });
|
|
Object.entries(TAB_NAMES).forEach(([id, name]) => { nameToId[name] = id; });
|
|
|
- customTabs.forEach(t => { nameToId[t.name] = t.id; });
|
|
|
|
|
|
|
+ customNames.forEach((name, idx) => { nameToId[name] = `custom-${idx}`; });
|
|
|
return tabOrderText.value.split(/[,,\s]+/)
|
|
return tabOrderText.value.split(/[,,\s]+/)
|
|
|
.map(s => s.trim())
|
|
.map(s => s.trim())
|
|
|
.filter(Boolean)
|
|
.filter(Boolean)
|
|
@@ -816,7 +936,6 @@ async function saveSettings() {
|
|
|
const saved = await SettingsService.SaveSettings(new AppSettings(collectForm()));
|
|
const saved = await SettingsService.SaveSettings(new AppSettings(collectForm()));
|
|
|
currentSettings = saved;
|
|
currentSettings = saved;
|
|
|
await syncForm(saved);
|
|
await syncForm(saved);
|
|
|
- applyEmbedUrls(saved);
|
|
|
|
|
renderLinks(saved.links || []);
|
|
renderLinks(saved.links || []);
|
|
|
// 古诗配置变化:重新进入古诗模式(若启用则加载一首,未启用回退 Tab 说明)
|
|
// 古诗配置变化:重新进入古诗模式(若启用则加载一首,未启用回退 Tab 说明)
|
|
|
showFooterPoem(saved.poem);
|
|
showFooterPoem(saved.poem);
|
|
@@ -1263,73 +1382,6 @@ async function loadTrending() {
|
|
|
|
|
|
|
|
trendingRefreshBtn.addEventListener('click', loadTrending); // 手动刷新
|
|
trendingRefreshBtn.addEventListener('click', loadTrending); // 手动刷新
|
|
|
|
|
|
|
|
-// ------------------------------------------------------------------------
|
|
|
|
|
-// 记录:编辑记录文件
|
|
|
|
|
-// ------------------------------------------------------------------------
|
|
|
|
|
-const recordEditor = document.getElementById('record-editor'); // 记录编辑框
|
|
|
|
|
-const recordStatus = document.getElementById('record-status'); // 记录状态提示
|
|
|
|
|
-const recordSaveBtn = document.getElementById('record-save'); // 记录保存按钮
|
|
|
|
|
-const recordPathDesc = document.getElementById('record-path-desc'); // 显示当前记录文件路径
|
|
|
|
|
-const recordPreview = document.getElementById('record-preview'); // Markdown 预览容器
|
|
|
|
|
-const recordModeEdit = document.getElementById('record-mode-edit'); // 编辑模式按钮
|
|
|
|
|
-const recordModePreview = document.getElementById('record-mode-preview'); // 预览模式按钮
|
|
|
|
|
-
|
|
|
|
|
-// 记录页显示模式:'edit' 源码编辑 / 'preview' Markdown 预览
|
|
|
|
|
-let recordMode = 'edit';
|
|
|
|
|
-
|
|
|
|
|
-async function loadRecord() {
|
|
|
|
|
- recordPathDesc.textContent = `记录零碎信息,当前文件:${currentSettings.recordPath || FALLBACK_SETTINGS.recordPath}`;
|
|
|
|
|
- try {
|
|
|
|
|
- recordEditor.value = await SettingsService.ReadRecord();
|
|
|
|
|
- recordStatus.textContent = '';
|
|
|
|
|
- refreshRecordPreview(); // 首次加载后刷新预览
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- recordStatus.textContent = '读取失败:' + (err?.message || err);
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 把当前编辑内容渲染到 Markdown 预览区(调用后端 gomarkdown)
|
|
|
|
|
-async function refreshRecordPreview() {
|
|
|
|
|
- const md = recordEditor.value;
|
|
|
|
|
- try {
|
|
|
|
|
- recordPreview.innerHTML = await SettingsService.RenderMarkdown(md);
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- recordPreview.textContent = '预览失败:' + (err?.message || err);
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 切换记录页显示模式(编辑 / 预览),并给两个按钮标记激活态
|
|
|
|
|
-function setRecordMode(mode) {
|
|
|
|
|
- recordMode = mode;
|
|
|
|
|
- const isEdit = mode === 'edit';
|
|
|
|
|
- recordModeEdit.classList.toggle('is-active', isEdit);
|
|
|
|
|
- recordModePreview.classList.toggle('is-active', !isEdit);
|
|
|
|
|
- recordEditor.style.display = isEdit ? '' : 'none';
|
|
|
|
|
- recordPreview.style.display = isEdit ? 'none' : '';
|
|
|
|
|
- if (!isEdit) refreshRecordPreview(); // 进入预览时渲染一次
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-recordModeEdit.addEventListener('click', () => setRecordMode('edit'));
|
|
|
|
|
-recordModePreview.addEventListener('click', () => setRecordMode('preview'));
|
|
|
|
|
-
|
|
|
|
|
-// 编辑内容变化时,若正处于预览模式则实时刷新(简单去抖)
|
|
|
|
|
-let recordPreviewTimer = null;
|
|
|
|
|
-recordEditor.addEventListener('input', () => {
|
|
|
|
|
- if (recordMode !== 'preview') return;
|
|
|
|
|
- clearTimeout(recordPreviewTimer);
|
|
|
|
|
- recordPreviewTimer = setTimeout(refreshRecordPreview, 250);
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-recordSaveBtn.addEventListener('click', async () => {
|
|
|
|
|
- recordStatus.textContent = '保存中…';
|
|
|
|
|
- try {
|
|
|
|
|
- await SettingsService.WriteRecord(recordEditor.value);
|
|
|
|
|
- recordStatus.textContent = '已保存 ' + new Date().toLocaleTimeString('zh-CN');
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- recordStatus.textContent = '保存失败:' + (err?.message || err);
|
|
|
|
|
- }
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
// ------------------------------------------------------------------------
|
|
|
// 待办:每行记录一个待完成任务,可勾选完成 / 编辑 / 删除,保存到本地文件
|
|
// 待办:每行记录一个待完成任务,可勾选完成 / 编辑 / 删除,保存到本地文件
|
|
|
// ------------------------------------------------------------------------
|
|
// ------------------------------------------------------------------------
|
|
@@ -1445,8 +1497,27 @@ function addTodoRow() {
|
|
|
|
|
|
|
|
// 从后端读取待办并渲染:每行一个任务,行首 [x] 表示已完成,行尾 @ 时间表示提醒
|
|
// 从后端读取待办并渲染:每行一个任务,行首 [x] 表示已完成,行尾 @ 时间表示提醒
|
|
|
async function loadTodo() {
|
|
async function loadTodo() {
|
|
|
- todoPathDesc.textContent = `待办任务,当前文件:${currentSettings.todoPath || FALLBACK_SETTINGS.todoPath}`;
|
|
|
|
|
|
|
+ const path = currentSettings.todoPath || '';
|
|
|
|
|
+ todoPathDesc.textContent = path
|
|
|
|
|
+ ? `待办任务,当前文件:${path}`
|
|
|
|
|
+ : '待办任务:未设置「待办文件路径」,请到设置页配置后再使用';
|
|
|
todoList.innerHTML = '';
|
|
todoList.innerHTML = '';
|
|
|
|
|
+ if (!path) {
|
|
|
|
|
+ // 未配置待办路径:强制提示进入设置,不渲染任务行
|
|
|
|
|
+ const hint = document.createElement('div');
|
|
|
|
|
+ hint.className = 'empty-state';
|
|
|
|
|
+ hint.innerHTML = `<div class="empty-mark">⚙</div><div class="empty-title">请先设置待办文件路径</div><div class="empty-text">前往「设置」页填写「待办文件路径」后保存即可使用</div>`;
|
|
|
|
|
+ const goBtn = document.createElement('button');
|
|
|
|
|
+ goBtn.className = 'btn-ghost';
|
|
|
|
|
+ goBtn.textContent = '前往设置';
|
|
|
|
|
+ goBtn.style.marginTop = '10px';
|
|
|
|
|
+ goBtn.addEventListener('click', () => switchTab('settings'));
|
|
|
|
|
+ hint.appendChild(goBtn);
|
|
|
|
|
+ todoList.appendChild(hint);
|
|
|
|
|
+ todoDirty = false;
|
|
|
|
|
+ todoStatus.textContent = '';
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
try {
|
|
try {
|
|
|
const raw = await SettingsService.ReadTodo();
|
|
const raw = await SettingsService.ReadTodo();
|
|
|
const lines = (raw || '').split(/\r?\n/);
|
|
const lines = (raw || '').split(/\r?\n/);
|