refactor: split UI helpers module

Change-Id: I1abcd20b75608261b2d004550c011eeba76d35bf
This commit is contained in:
wujunchen 2026-04-25 21:32:32 +08:00
parent 5f6f18677b
commit d8880d41b2
4 changed files with 87 additions and 74 deletions

40
main.js

File diff suppressed because one or more lines are too long

59
main.ts
View file

@ -1,5 +1,5 @@
'use strict';
import { Plugin, ItemView, PluginSettingTab, Setting, Notice, MarkdownView, TFile, Menu, Modal, MarkdownRenderer, requestUrl, setIcon } from 'obsidian';
import { Plugin, ItemView, PluginSettingTab, Setting, Notice, MarkdownView, TFile, Menu, Modal, MarkdownRenderer, requestUrl } from 'obsidian';
import { findLineForAnchor } from './src/anchor';
import { serializeCacheFile, shouldConfirmRegenerate, touchCacheEntry } from './src/cache';
import { activeIndexAfterCardDelete, removeCardAt, updateCardAt } from './src/cards';
@ -29,6 +29,7 @@ import {
GenerationJobManager,
classifyGenerationError,
} from './src/generation-job-manager';
import { addIconButton, addTextButton, copyToClipboard } from './src/ui-helpers';
import {
API_AUTH_TYPES,
API_FORMATS,
@ -106,59 +107,6 @@ async function summarizeDocument(content, settings, job) {
return resolved;
}
function addIconButton(parent, icon, title, onClick) {
const button = parent.createEl('button', {
cls: 'parallel-reader-icon-button',
attr: { type: 'button', 'aria-label': title },
});
button.title = title;
if (typeof setIcon === 'function') {
setIcon(button, icon);
} else {
button.textContent = title;
}
button.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
try {
await onClick();
} catch (err) {
console.error(err);
new Notice(`${title} failed: ` + (err.message || err));
}
});
return button;
}
function addTextButton(parent, icon, label, onClick, cls) {
const button = parent.createEl('button', {
cls: cls || 'parallel-reader-text-button',
attr: { type: 'button' },
});
if (icon && typeof setIcon === 'function') setIcon(button, icon);
button.createSpan({ text: label });
button.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
try {
await onClick();
} catch (err) {
console.error(err);
new Notice(`${label} failed: ` + (err.message || err));
}
});
return button;
}
async function copyToClipboard(text, successMsg) {
try {
await navigator.clipboard.writeText(text);
new Notice(successMsg);
} catch (e) {
new Notice('Copy failed: ' + (e.message || e));
}
}
function cancellationNoticeKey(settings, job) {
if (job?.phase === 'generating' && isApiBackend(settings?.backend)) {
return 'cancelRequestedApiInFlight';
@ -1628,6 +1576,8 @@ export const __test = {
GenerationJobAlreadyRunningError,
GenerationJobCancelledError,
GenerationJobManager,
addIconButton,
addTextButton,
activeIndexAfterCardDelete,
activeSectionLine,
buildAnthropicMessagesBody,
@ -1639,6 +1589,7 @@ export const __test = {
cacheEntryMatches,
cancellationNoticeKey,
classifyGenerationError,
copyToClipboard,
createRafThrottledHandler,
extractJson,
findLineForAnchor,

56
src/ui-helpers.ts Normal file
View file

@ -0,0 +1,56 @@
'use strict';
import { Notice, setIcon } from 'obsidian';
export function addIconButton(parent, icon, title, onClick) {
const button = parent.createEl('button', {
cls: 'parallel-reader-icon-button',
attr: { type: 'button', 'aria-label': title },
});
button.title = title;
if (typeof setIcon === 'function') {
setIcon(button, icon);
} else {
button.textContent = title;
}
button.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
try {
await onClick();
} catch (err) {
console.error(err);
new Notice(`${title} failed: ` + (err.message || err));
}
});
return button;
}
export function addTextButton(parent, icon, label, onClick, cls?) {
const button = parent.createEl('button', {
cls: cls || 'parallel-reader-text-button',
attr: { type: 'button' },
});
if (icon && typeof setIcon === 'function') setIcon(button, icon);
button.createSpan({ text: label });
button.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
try {
await onClick();
} catch (err) {
console.error(err);
new Notice(`${label} failed: ` + (err.message || err));
}
});
return button;
}
export async function copyToClipboard(text, successMsg) {
try {
await navigator.clipboard.writeText(text);
new Notice(successMsg);
} catch (e) {
new Notice('Copy failed: ' + (e.message || e));
}
}

View file

@ -54,8 +54,14 @@ assert.ok(/flushCacheSave\s*\(/.test(mainSource), 'pending cache touches should
assert.ok(/onunload[\s\S]*flushCacheSave/.test(mainSource), 'plugin unload should flush pending cache touches');
assert.ok(/cacheTouch[\s\S]*scheduleCacheSave/.test(mainSource), 'cacheTouch should schedule a cache save');
assert.ok(!/cacheTouch[\s\S]{0,220}await this\.saveCache/.test(mainSource), 'cacheTouch should not synchronously write cache.json');
assert.ok(!/function addIconButton/.test(mainSource), 'UI icon helper should live outside main.ts');
assert.ok(!/function addTextButton/.test(mainSource), 'UI text-button helper should live outside main.ts');
assert.ok(!/function copyToClipboard/.test(mainSource), 'clipboard helper should live outside main.ts');
assert.strictEqual(typeof t.cardsToMarkdown, 'function');
assert.strictEqual(typeof t.cancellationNoticeKey, 'function');
assert.strictEqual(typeof t.addIconButton, 'function');
assert.strictEqual(typeof t.addTextButton, 'function');
assert.strictEqual(typeof t.copyToClipboard, 'function');
assert.strictEqual(typeof t.resolveCliPath, 'function');
assert.strictEqual(typeof t.buildPrompts, 'function');
assert.strictEqual(typeof t.buildOpenAiChatBody, 'function');