mirror of
https://github.com/fancive/obsidian-parallel-reader.git
synced 2026-07-22 06:53:43 +00:00
refactor: enable noImplicitAny and add type annotations
Turn on noImplicitAny in tsconfig.json. Add explicit type annotations to all 137 previously untyped parameters across main.ts, view.ts, modal.ts, settings-tab.ts, providers.ts, cli.ts, anchor.ts, scroll.ts, ui-helpers.ts, vault.ts, and settings.ts. Change-Id: Icc5a69ab57113e9f6dc08dd97e3a8fa5e7e24937
This commit is contained in:
parent
89139c37dc
commit
31e57c8b19
12 changed files with 166 additions and 135 deletions
54
main.js
54
main.js
File diff suppressed because one or more lines are too long
63
main.ts
63
main.ts
|
|
@ -6,6 +6,7 @@ import { activeIndexAfterCardDelete, removeCardAt, updateCardAt } from './src/ca
|
|||
import { resolveCliPath, summarizeViaClaudeCode, summarizeViaCodex } from './src/cli';
|
||||
import {
|
||||
classifyGenerationError,
|
||||
type GenerationJob,
|
||||
GenerationJobAlreadyRunningError,
|
||||
GenerationJobCancelledError,
|
||||
GenerationJobManager,
|
||||
|
|
@ -45,7 +46,7 @@ import { ParallelReaderView, VIEW_TYPE_PARALLEL } from './src/view';
|
|||
|
||||
/* ---------- Helpers ---------- */
|
||||
|
||||
async function summarizeDocument(content, settings, job) {
|
||||
async function summarizeDocument(content: string, settings: PluginSettings, job: GenerationJob) {
|
||||
const { system, user } = buildPrompts(content, settings);
|
||||
let cards: RawCard[];
|
||||
switch (settings.backend) {
|
||||
|
|
@ -79,8 +80,8 @@ async function summarizeDocument(content, settings, job) {
|
|||
return resolved;
|
||||
}
|
||||
|
||||
function cancellationNoticeKey(settings, job) {
|
||||
if (job?.phase === 'generating' && isApiBackend(settings?.backend)) {
|
||||
function cancellationNoticeKey(settings: PluginSettings | null, job: GenerationJob | null) {
|
||||
if (job?.phase === 'generating' && settings && isApiBackend(settings.backend)) {
|
||||
return 'cancelRequestedApiInFlight';
|
||||
}
|
||||
return 'cancelRequested';
|
||||
|
|
@ -97,7 +98,7 @@ class ParallelReaderPlugin extends Plugin {
|
|||
_cacheSaveTimer: ReturnType<typeof setTimeout> | null;
|
||||
_cacheDirty: boolean;
|
||||
|
||||
t(key, vars?) {
|
||||
t(key: string, vars?: Record<string, string | number>) {
|
||||
return translate(this.settings || DEFAULT_SETTINGS, key, vars);
|
||||
}
|
||||
|
||||
|
|
@ -195,8 +196,8 @@ class ParallelReaderPlugin extends Plugin {
|
|||
}),
|
||||
);
|
||||
this.registerEvent(this.app.workspace.on('file-menu', (menu, file) => this.addFileMenuItems(menu, file)));
|
||||
this.registerEvent(this.app.vault.on('rename', (file, oldPath) => this.handleFileRename(file, oldPath)));
|
||||
this.registerEvent(this.app.vault.on('delete', (file) => this.handleFileDelete(file)));
|
||||
this.registerEvent(this.app.vault.on('rename', (file, oldPath) => this.handleFileRename(file as TFile, oldPath)));
|
||||
this.registerEvent(this.app.vault.on('delete', (file) => this.handleFileDelete(file as TFile)));
|
||||
this.bindScrollSync();
|
||||
}
|
||||
|
||||
|
|
@ -322,18 +323,18 @@ class ParallelReaderPlugin extends Plugin {
|
|||
if (removed.length > 0) await this.saveCache();
|
||||
return removed;
|
||||
}
|
||||
cacheGet(filePath) {
|
||||
cacheGet(filePath: string) {
|
||||
return this.cache[filePath] || null;
|
||||
}
|
||||
|
||||
async cacheTouch(filePath) {
|
||||
async cacheTouch(filePath: string) {
|
||||
const entry = touchCacheEntry(this.cache[filePath] || null);
|
||||
if (!entry) return null;
|
||||
this.scheduleCacheSave();
|
||||
return entry;
|
||||
}
|
||||
|
||||
async cachePut(filePath, content, cards, settings) {
|
||||
async cachePut(filePath: string, content: string, cards: RawCard[], settings: PluginSettings) {
|
||||
const now = new Date().toISOString();
|
||||
this.cache[filePath] = {
|
||||
schemaVersion: CACHE_SCHEMA_VERSION,
|
||||
|
|
@ -346,11 +347,11 @@ class ParallelReaderPlugin extends Plugin {
|
|||
await this.saveCache();
|
||||
}
|
||||
|
||||
async cacheReplaceCards(filePath, cards) {
|
||||
async cacheReplaceCards(filePath: string, cards: ResolvedCard[]) {
|
||||
const entry = this.cache[filePath];
|
||||
if (!entry) return false;
|
||||
const now = new Date().toISOString();
|
||||
entry.cards = (cards || []).map((card) => ({
|
||||
entry.cards = (cards || []).map((card: ResolvedCard) => ({
|
||||
title: card.title,
|
||||
anchor: card.anchor,
|
||||
gist: card.gist,
|
||||
|
|
@ -362,7 +363,7 @@ class ParallelReaderPlugin extends Plugin {
|
|||
return true;
|
||||
}
|
||||
|
||||
async cacheDelete(filePath) {
|
||||
async cacheDelete(filePath: string) {
|
||||
if (this.cache[filePath]) {
|
||||
delete this.cache[filePath];
|
||||
await this.saveCache();
|
||||
|
|
@ -393,7 +394,7 @@ class ParallelReaderPlugin extends Plugin {
|
|||
return this.app.workspace.getLeavesOfType(VIEW_TYPE_PARALLEL)[0]?.view as ParallelReaderView | undefined;
|
||||
}
|
||||
|
||||
moveActiveCard(delta) {
|
||||
moveActiveCard(delta: number) {
|
||||
const view = this.getParallelView();
|
||||
if (!view?.sections.length) {
|
||||
new Notice(this.t('noActiveCard'));
|
||||
|
|
@ -411,11 +412,11 @@ class ParallelReaderPlugin extends Plugin {
|
|||
return true;
|
||||
}
|
||||
|
||||
isGeneratingFile(file) {
|
||||
isGeneratingFile(file: TFile | null) {
|
||||
return !!file && !!file.path && this.jobs.isRunning(file.path);
|
||||
}
|
||||
|
||||
cancelGenerationForFile(file) {
|
||||
cancelGenerationForFile(file: TFile | null) {
|
||||
if (!file?.path) {
|
||||
new Notice(this.t('noCancelableJob'));
|
||||
return false;
|
||||
|
|
@ -427,10 +428,10 @@ class ParallelReaderPlugin extends Plugin {
|
|||
return cancelled;
|
||||
}
|
||||
|
||||
viewIsShowingFile(view, file) {
|
||||
viewIsShowingFile(view: ParallelReaderView | null, file: TFile) {
|
||||
return !!view && !!file && view.sourceFile?.path === file.path;
|
||||
}
|
||||
activeFileStillMatches(file) {
|
||||
activeFileStillMatches(file: TFile) {
|
||||
const active = this.getActiveView();
|
||||
return !active?.file || active.file.path === file.path;
|
||||
}
|
||||
|
|
@ -449,23 +450,23 @@ class ParallelReaderPlugin extends Plugin {
|
|||
return true;
|
||||
}
|
||||
|
||||
addFileMenuItems(menu, file) {
|
||||
addFileMenuItems(menu: any, file: any) {
|
||||
if (!(file instanceof TFile) || !file.path.endsWith('.md')) return;
|
||||
menu.addSeparator();
|
||||
menu.addItem((it) =>
|
||||
menu.addItem((it: any) =>
|
||||
it
|
||||
.setTitle(this.t('fileMenuGenerate'))
|
||||
.setIcon('book-open')
|
||||
.onClick(() => this.runForFile(file, false)),
|
||||
);
|
||||
menu.addItem((it) =>
|
||||
menu.addItem((it: any) =>
|
||||
it
|
||||
.setTitle(this.t('fileMenuRegen'))
|
||||
.setIcon('refresh-cw')
|
||||
.onClick(() => this.runForFile(file, true)),
|
||||
);
|
||||
if (this.cacheGet(file.path)) {
|
||||
menu.addItem((it) =>
|
||||
menu.addItem((it: any) =>
|
||||
it
|
||||
.setTitle(this.t('fileMenuClear'))
|
||||
.setIcon('trash')
|
||||
|
|
@ -477,7 +478,7 @@ class ParallelReaderPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
async handleFileRename(file, oldPath) {
|
||||
async handleFileRename(file: TFile, oldPath: string) {
|
||||
if (!(file instanceof TFile) || !oldPath) return;
|
||||
const wasMarkdown = oldPath.endsWith('.md');
|
||||
const isMarkdown = file.path.endsWith('.md');
|
||||
|
|
@ -504,7 +505,7 @@ class ParallelReaderPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
async handleFileDelete(file) {
|
||||
async handleFileDelete(file: TFile) {
|
||||
if (!(file instanceof TFile)) return;
|
||||
if (this.cache[file.path]) {
|
||||
delete this.cache[file.path];
|
||||
|
|
@ -545,7 +546,7 @@ class ParallelReaderPlugin extends Plugin {
|
|||
|
||||
/* ---------- Generation ---------- */
|
||||
|
||||
async runForActiveFile(force) {
|
||||
async runForActiveFile(force: boolean) {
|
||||
const mdView = this.getActiveView();
|
||||
if (!mdView?.file) {
|
||||
new Notice(this.t('openNoteFirst'));
|
||||
|
|
@ -554,7 +555,7 @@ class ParallelReaderPlugin extends Plugin {
|
|||
return this.runForFile(mdView.file, force);
|
||||
}
|
||||
|
||||
async runForFile(file, force) {
|
||||
async runForFile(file: TFile | null, force: boolean) {
|
||||
if (!file) {
|
||||
new Notice(this.t('openNoteFirst'));
|
||||
return;
|
||||
|
|
@ -635,8 +636,8 @@ class ParallelReaderPlugin extends Plugin {
|
|||
});
|
||||
}
|
||||
|
||||
resolveCardAnchors(content, rawCards) {
|
||||
const resolved: ResolvedCard[] = (rawCards || []).map((c) => ({
|
||||
resolveCardAnchors(content: string, rawCards: RawCard[]) {
|
||||
const resolved: ResolvedCard[] = (rawCards || []).map((c: RawCard) => ({
|
||||
title: c.title,
|
||||
level: 2,
|
||||
anchor: c.anchor,
|
||||
|
|
@ -655,7 +656,7 @@ class ParallelReaderPlugin extends Plugin {
|
|||
|
||||
/* ---------- Scroll sync ---------- */
|
||||
|
||||
async syncViewToFile(file) {
|
||||
async syncViewToFile(file: TFile) {
|
||||
if (!file?.path?.endsWith('.md')) return;
|
||||
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_PARALLEL);
|
||||
if (leaves.length === 0) return;
|
||||
|
|
@ -693,7 +694,7 @@ class ParallelReaderPlugin extends Plugin {
|
|||
};
|
||||
}
|
||||
|
||||
handleEditorScroll(mdView) {
|
||||
handleEditorScroll(mdView: MarkdownView) {
|
||||
const view = this.getParallelView();
|
||||
if (!view || !mdView.file || view.sourceFile?.path !== mdView.file.path) return;
|
||||
const editor = mdView.editor;
|
||||
|
|
@ -718,7 +719,7 @@ class ParallelReaderPlugin extends Plugin {
|
|||
view.setActiveSection(activeIdx);
|
||||
}
|
||||
|
||||
findLeafForFile(file) {
|
||||
findLeafForFile(file: TFile | null) {
|
||||
if (!file) return null;
|
||||
for (const leaf of this.app.workspace.getLeavesOfType('markdown')) {
|
||||
const v = leaf.view as any;
|
||||
|
|
@ -727,7 +728,7 @@ class ParallelReaderPlugin extends Plugin {
|
|||
return null;
|
||||
}
|
||||
|
||||
async scrollEditorToLine(line, file) {
|
||||
async scrollEditorToLine(line: number, file: TFile | null) {
|
||||
let leaf = file ? this.findLeafForFile(file) : null;
|
||||
if (!leaf && file) {
|
||||
leaf = this.app.workspace.getLeaf('tab');
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
export function findLineForAnchor(content: string, anchor: string): number {
|
||||
if (!anchor) return -1;
|
||||
const normalize = (s) => s.replace(/\s+/g, ' ').trim();
|
||||
const normalizeWithMap = (s) => {
|
||||
const normalize = (s: string) => s.replace(/\s+/g, ' ').trim();
|
||||
const normalizeWithMap = (s: string) => {
|
||||
const chars: string[] = [];
|
||||
const map: number[] = [];
|
||||
let pendingWhitespace = false;
|
||||
|
|
@ -23,7 +23,7 @@ export function findLineForAnchor(content: string, anchor: string): number {
|
|||
}
|
||||
return { text: chars.join(''), map };
|
||||
};
|
||||
const tryAt = (needle) => {
|
||||
const tryAt = (needle: string) => {
|
||||
if (!needle) return -1;
|
||||
const idx = content.indexOf(needle);
|
||||
if (idx === -1) return -1;
|
||||
|
|
|
|||
18
src/cli.ts
18
src/cli.ts
|
|
@ -41,16 +41,16 @@ export function runCli(
|
|||
job?: GenerationJob,
|
||||
): Promise<{ stdout: string; stderr: string }> {
|
||||
return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
|
||||
let child;
|
||||
let child: ReturnType<typeof spawn>;
|
||||
let settled = false;
|
||||
let timer;
|
||||
const fail = (err) => {
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
const fail = (err: Error) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
if (timer) clearTimeout(timer);
|
||||
reject(err);
|
||||
};
|
||||
const succeed = (value) => {
|
||||
const succeed = (value: { stdout: string; stderr: string }) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
if (timer) clearTimeout(timer);
|
||||
|
|
@ -97,10 +97,10 @@ export function runCli(
|
|||
});
|
||||
}
|
||||
|
||||
child.stdout.on('data', (d) => {
|
||||
child.stdout!.on('data', (d) => {
|
||||
stdout += d.toString('utf8');
|
||||
});
|
||||
child.stderr.on('data', (d) => {
|
||||
child.stderr!.on('data', (d) => {
|
||||
stderr += d.toString('utf8');
|
||||
});
|
||||
child.on('error', (e) => {
|
||||
|
|
@ -116,14 +116,14 @@ export function runCli(
|
|||
|
||||
if (stdinText) {
|
||||
try {
|
||||
child.stdin.write(stdinText);
|
||||
child.stdin.end();
|
||||
child.stdin!.write(stdinText);
|
||||
child.stdin!.end();
|
||||
} catch (_e) {
|
||||
// Child may have exited before stdin was written.
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
child.stdin.end();
|
||||
child.stdin!.end();
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export class CardEditModal extends Modal {
|
|||
gist: gistInput.value.trim(),
|
||||
bullets: bulletsInput.value
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.map((line: string) => line.trim())
|
||||
.filter(Boolean),
|
||||
});
|
||||
this.close();
|
||||
|
|
@ -56,7 +56,7 @@ export class CardEditModal extends Modal {
|
|||
);
|
||||
}
|
||||
|
||||
createLabeledInput(parent, label: string, value: string) {
|
||||
createLabeledInput(parent: HTMLElement, label: string, value: string) {
|
||||
const wrapper = parent.createDiv({ cls: 'parallel-reader-modal-field' });
|
||||
wrapper.createEl('label', { text: label });
|
||||
const input = wrapper.createEl('input', { attr: { type: 'text' } });
|
||||
|
|
@ -64,7 +64,7 @@ export class CardEditModal extends Modal {
|
|||
return input;
|
||||
}
|
||||
|
||||
createLabeledTextarea(parent, label: string, value: string, rows: number) {
|
||||
createLabeledTextarea(parent: HTMLElement, label: string, value: string, rows: number) {
|
||||
const wrapper = parent.createDiv({ cls: 'parallel-reader-modal-field' });
|
||||
wrapper.createEl('label', { text: label });
|
||||
const textarea = wrapper.createEl('textarea');
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
} from './settings';
|
||||
import type { PluginSettings, RawCard } from './types';
|
||||
|
||||
function endpointUrl(baseUrl, suffixes) {
|
||||
function endpointUrl(baseUrl: string, suffixes: string[]) {
|
||||
const base = baseUrl.replace(/\/+$/, '');
|
||||
for (const suffix of suffixes) {
|
||||
if (base.endsWith(suffix)) return base;
|
||||
|
|
@ -29,33 +29,33 @@ function endpointUrl(baseUrl, suffixes) {
|
|||
return base + suffixes[0];
|
||||
}
|
||||
|
||||
function parseApiHeaders(raw, settings?) {
|
||||
function parseApiHeaders(raw: string, settings?: PluginSettings | null): Record<string, string> {
|
||||
const text = (raw || '').trim();
|
||||
if (!text) return {};
|
||||
if (text.startsWith('{')) {
|
||||
let parsed;
|
||||
let parsed: Record<string, unknown>;
|
||||
try {
|
||||
parsed = JSON.parse(text);
|
||||
} catch (e) {
|
||||
throw new Error(translate(settings, 'errorCustomHeadersJsonParse', { error: e.message }));
|
||||
} catch (e: any) {
|
||||
throw new Error(translate(settings || null, 'errorCustomHeadersJsonParse', { error: e.message }));
|
||||
}
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
throw new Error(translate(settings, 'errorCustomHeadersJsonObject'));
|
||||
throw new Error(translate(settings || null, 'errorCustomHeadersJsonObject'));
|
||||
}
|
||||
const headers = {};
|
||||
const headers: Record<string, string> = {};
|
||||
for (const [k, v] of Object.entries(parsed)) {
|
||||
if (typeof v === 'string' && k.trim()) headers[k.trim()] = v;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
const headers = {};
|
||||
const headers: Record<string, string> = {};
|
||||
for (const line of text.split(/\r?\n/)) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
const idx = trimmed.indexOf(':');
|
||||
if (idx <= 0) {
|
||||
throw new Error(translate(settings, 'errorCustomHeadersLineFormat'));
|
||||
throw new Error(translate(settings || null, 'errorCustomHeadersLineFormat'));
|
||||
}
|
||||
const key = trimmed.slice(0, idx).trim();
|
||||
const value = trimmed.slice(idx + 1).trim();
|
||||
|
|
@ -64,7 +64,7 @@ function parseApiHeaders(raw, settings?) {
|
|||
return headers;
|
||||
}
|
||||
|
||||
function authHeaders(settings) {
|
||||
function authHeaders(settings: PluginSettings): Record<string, string> {
|
||||
const authType = getApiAuthType(settings);
|
||||
if (authType === 'none') return {};
|
||||
const key = getApiKey(settings);
|
||||
|
|
@ -80,7 +80,7 @@ function authHeaders(settings) {
|
|||
return { authorization: `Bearer ${key}` };
|
||||
}
|
||||
|
||||
function buildApiHeaders(settings, extra?) {
|
||||
function buildApiHeaders(settings: PluginSettings, extra?: Record<string, string>): Record<string, string> {
|
||||
return {
|
||||
'content-type': 'application/json',
|
||||
...authHeaders(settings),
|
||||
|
|
@ -89,13 +89,13 @@ function buildApiHeaders(settings, extra?) {
|
|||
};
|
||||
}
|
||||
|
||||
function responseJson(resp, label, settings?) {
|
||||
function responseJson(resp: any, label: string, settings?: PluginSettings | null) {
|
||||
if (resp.json && typeof resp.json === 'object') return resp.json;
|
||||
try {
|
||||
return JSON.parse(resp.text || '{}');
|
||||
} catch (_) {
|
||||
throw new Error(
|
||||
translate(settings, 'errorProviderNonJson', {
|
||||
translate(settings || null, 'errorProviderNonJson', {
|
||||
label,
|
||||
excerpt: (resp.text || '').slice(0, 500),
|
||||
}),
|
||||
|
|
@ -103,8 +103,15 @@ function responseJson(resp, label, settings?) {
|
|||
}
|
||||
}
|
||||
|
||||
async function requestJsonBody(requestUrlImpl, label, url, headers, body, settings?) {
|
||||
let resp;
|
||||
async function requestJsonBody(
|
||||
requestUrlImpl: any,
|
||||
label: string,
|
||||
url: string,
|
||||
headers: Record<string, string>,
|
||||
body: any,
|
||||
settings?: PluginSettings | null,
|
||||
) {
|
||||
let resp: any;
|
||||
try {
|
||||
resp = await requestUrlImpl({
|
||||
url,
|
||||
|
|
@ -113,9 +120,9 @@ async function requestJsonBody(requestUrlImpl, label, url, headers, body, settin
|
|||
body: JSON.stringify(body),
|
||||
throw: false,
|
||||
});
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
throw new Error(
|
||||
translate(settings, 'errorProviderRequestFailed', {
|
||||
translate(settings || null, 'errorProviderRequestFailed', {
|
||||
label,
|
||||
error: e.message || e,
|
||||
}),
|
||||
|
|
@ -124,7 +131,7 @@ async function requestJsonBody(requestUrlImpl, label, url, headers, body, settin
|
|||
|
||||
if (resp.status >= 400) {
|
||||
throw new Error(
|
||||
translate(settings, 'errorProviderApiStatus', {
|
||||
translate(settings || null, 'errorProviderApiStatus', {
|
||||
label,
|
||||
status: resp.status,
|
||||
excerpt: (resp.text || '').slice(0, 500),
|
||||
|
|
@ -134,7 +141,7 @@ async function requestJsonBody(requestUrlImpl, label, url, headers, body, settin
|
|||
return responseJson(resp, label, settings);
|
||||
}
|
||||
|
||||
function shouldRetryWithoutStructuredOutput(error) {
|
||||
function shouldRetryWithoutStructuredOutput(error: any) {
|
||||
const message = String(error?.message ? error.message : error);
|
||||
if (!/(?:API (?:400|404|422):|API returned HTTP (?:400|404|422)|API 返回 HTTP (?:400|404|422))/.test(message))
|
||||
return false;
|
||||
|
|
@ -144,13 +151,13 @@ function shouldRetryWithoutStructuredOutput(error) {
|
|||
}
|
||||
|
||||
async function requestJsonBodyWithStructuredFallback(
|
||||
requestUrlImpl,
|
||||
label,
|
||||
url,
|
||||
headers,
|
||||
structuredBody,
|
||||
fallbackBody,
|
||||
settings?,
|
||||
requestUrlImpl: any,
|
||||
label: string,
|
||||
url: string,
|
||||
headers: Record<string, string>,
|
||||
structuredBody: any,
|
||||
fallbackBody: any,
|
||||
settings?: PluginSettings | null,
|
||||
) {
|
||||
try {
|
||||
return await requestJsonBody(requestUrlImpl, label, url, headers, structuredBody, settings);
|
||||
|
|
@ -161,7 +168,7 @@ async function requestJsonBodyWithStructuredFallback(
|
|||
}
|
||||
}
|
||||
|
||||
function textFromContent(content) {
|
||||
function textFromContent(content: any): string {
|
||||
if (typeof content === 'string') return content;
|
||||
if (Array.isArray(content)) {
|
||||
return content
|
||||
|
|
@ -178,7 +185,7 @@ function textFromContent(content) {
|
|||
return '';
|
||||
}
|
||||
|
||||
function textFromOpenAiResponses(json) {
|
||||
function textFromOpenAiResponses(json: any): string {
|
||||
if (typeof json.output_text === 'string') return json.output_text;
|
||||
const parts: string[] = [];
|
||||
const walk = (value: any) => {
|
||||
|
|
@ -288,16 +295,21 @@ export function buildGeminiBody(
|
|||
};
|
||||
}
|
||||
|
||||
function cardsFromAnthropicToolUse(json, settings?) {
|
||||
function cardsFromAnthropicToolUse(json: any, settings?: PluginSettings | null) {
|
||||
const content = Array.isArray(json?.content) ? json.content : [];
|
||||
const block = content.find((c) => c && c.type === 'tool_use' && c.name === ANTHROPIC_CARD_TOOL_NAME);
|
||||
const block = content.find((c: any) => c && c.type === 'tool_use' && c.name === ANTHROPIC_CARD_TOOL_NAME);
|
||||
if (!block) return null;
|
||||
if (typeof block.input === 'string') return parseCardsJson(block.input, settings);
|
||||
if (block.input && typeof block.input === 'object') return normalizeCardsPayload(block.input);
|
||||
return [];
|
||||
}
|
||||
|
||||
async function summarizeViaAnthropicMessages(requestUrlImpl, system, user, settings) {
|
||||
async function summarizeViaAnthropicMessages(
|
||||
requestUrlImpl: any,
|
||||
system: string,
|
||||
user: string,
|
||||
settings: PluginSettings,
|
||||
) {
|
||||
const url = endpointUrl(getApiBaseUrl(settings), ['/messages']);
|
||||
const json = await requestJsonBodyWithStructuredFallback(
|
||||
requestUrlImpl,
|
||||
|
|
@ -313,13 +325,13 @@ async function summarizeViaAnthropicMessages(requestUrlImpl, system, user, setti
|
|||
if (toolCards) return toolCards;
|
||||
|
||||
const text = (json.content || [])
|
||||
.map((c) => textFromContent(c))
|
||||
.map((c: any) => textFromContent(c))
|
||||
.join('')
|
||||
.trim();
|
||||
return parseCardsJson(text, settings);
|
||||
}
|
||||
|
||||
async function summarizeViaOpenAiChat(requestUrlImpl, system, user, settings) {
|
||||
async function summarizeViaOpenAiChat(requestUrlImpl: any, system: string, user: string, settings: PluginSettings) {
|
||||
const url = endpointUrl(getApiBaseUrl(settings), ['/chat/completions']);
|
||||
const json = await requestJsonBodyWithStructuredFallback(
|
||||
requestUrlImpl,
|
||||
|
|
@ -335,7 +347,12 @@ async function summarizeViaOpenAiChat(requestUrlImpl, system, user, settings) {
|
|||
return parseCardsJson(text, settings);
|
||||
}
|
||||
|
||||
async function summarizeViaOpenAiResponses(requestUrlImpl, system, user, settings) {
|
||||
async function summarizeViaOpenAiResponses(
|
||||
requestUrlImpl: any,
|
||||
system: string,
|
||||
user: string,
|
||||
settings: PluginSettings,
|
||||
) {
|
||||
const url = endpointUrl(getApiBaseUrl(settings), ['/responses']);
|
||||
const json = await requestJsonBodyWithStructuredFallback(
|
||||
requestUrlImpl,
|
||||
|
|
@ -349,7 +366,12 @@ async function summarizeViaOpenAiResponses(requestUrlImpl, system, user, setting
|
|||
return parseCardsJson(textFromOpenAiResponses(json).trim(), settings);
|
||||
}
|
||||
|
||||
async function summarizeViaGoogleGenerativeAi(requestUrlImpl, system, user, settings) {
|
||||
async function summarizeViaGoogleGenerativeAi(
|
||||
requestUrlImpl: any,
|
||||
system: string,
|
||||
user: string,
|
||||
settings: PluginSettings,
|
||||
) {
|
||||
const model = encodeURIComponent(modelForApi(settings));
|
||||
let url = getApiBaseUrl(settings);
|
||||
if (!/:generateContent(?:\?|$)/.test(url)) {
|
||||
|
|
@ -368,7 +390,7 @@ async function summarizeViaGoogleGenerativeAi(requestUrlImpl, system, user, sett
|
|||
const candidate = (json.candidates || [])[0] || {};
|
||||
const parts = candidate.content?.parts || [];
|
||||
const text = parts
|
||||
.map((p) => textFromContent(p))
|
||||
.map((p: any) => textFromContent(p))
|
||||
.join('')
|
||||
.trim();
|
||||
return parseCardsJson(text, settings);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
export type RafThrottledHandler = (() => void) & { cancel: () => void };
|
||||
|
||||
export function visibleTopProbeY(rect, maxOffset = 80, ratio = 0.1) {
|
||||
export function visibleTopProbeY(rect: { top?: number; height?: number } | null, maxOffset = 80, ratio = 0.1) {
|
||||
const top = Number(rect?.top) || 0;
|
||||
const height = Math.max(0, Number(rect?.height) || 0);
|
||||
const cappedOffset = Math.min(Number(maxOffset) || 0, height * ratio);
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ import {
|
|||
PROMPT_LANGUAGES,
|
||||
UI_LANGUAGES,
|
||||
} from './settings';
|
||||
import type { PluginHost } from './types';
|
||||
import type { PluginHost, PluginSettings } from './types';
|
||||
|
||||
async function testBackend(settings) {
|
||||
async function testBackend(settings: PluginSettings) {
|
||||
if (settings.backend === 'codex') {
|
||||
const cmd = resolveCliPath('codex', settings.cliPath);
|
||||
const { stdout } = await runCli(cmd, ['--version'], '', 10000);
|
||||
|
|
@ -45,7 +45,7 @@ export class ParallelReaderSettingTab extends PluginSettingTab {
|
|||
|
||||
display() {
|
||||
const { containerEl } = this;
|
||||
const tr = (key, vars?) => this.plugin.t(key, vars);
|
||||
const tr = (key: string, vars?: Record<string, string | number>) => this.plugin.t(key, vars);
|
||||
containerEl.empty();
|
||||
containerEl.createEl('h2', { text: tr('settingsTitle') });
|
||||
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ export function stableStringify(value: unknown): string {
|
|||
'{' +
|
||||
Object.keys(value)
|
||||
.sort()
|
||||
.map((k) => JSON.stringify(k) + ':' + stableStringify(value[k]))
|
||||
.map((k) => JSON.stringify(k) + ':' + stableStringify((value as Record<string, unknown>)[k]))
|
||||
.join(',') +
|
||||
'}'
|
||||
);
|
||||
|
|
@ -325,13 +325,14 @@ export function applyApiProviderPreset(settings: PluginSettings, providerId: str
|
|||
}
|
||||
|
||||
export function normalizeSettings(settings: PluginSettings): PluginSettings {
|
||||
if (!UI_LANGUAGES[settings.uiLanguage]) settings.uiLanguage = DEFAULT_SETTINGS.uiLanguage;
|
||||
if (!(UI_LANGUAGES as Record<string, string>)[settings.uiLanguage]) settings.uiLanguage = DEFAULT_SETTINGS.uiLanguage;
|
||||
if (!settings.apiProvider || !API_PROVIDER_PRESETS[settings.apiProvider]) {
|
||||
settings.apiProvider = 'anthropic';
|
||||
}
|
||||
const preset = getApiPreset(settings);
|
||||
if (!settings.apiFormat || !API_FORMATS[settings.apiFormat]) settings.apiFormat = preset.format;
|
||||
if (!settings.apiAuthType || !API_AUTH_TYPES[settings.apiAuthType]) settings.apiAuthType = 'auto';
|
||||
if (!settings.apiAuthType || !(API_AUTH_TYPES as Record<string, string>)[settings.apiAuthType])
|
||||
settings.apiAuthType = 'auto';
|
||||
if (settings.backend === 'anthropic-api') {
|
||||
settings.apiProvider = settings.apiProvider || 'anthropic';
|
||||
settings.apiFormat = settings.apiFormat || 'anthropic-messages';
|
||||
|
|
@ -344,7 +345,8 @@ export function normalizeSettings(settings: PluginSettings): PluginSettings {
|
|||
const maxDocChars = Number(settings.maxDocChars);
|
||||
if (!Number.isFinite(maxDocChars) || maxDocChars < 1000) settings.maxDocChars = DEFAULT_SETTINGS.maxDocChars;
|
||||
settings.maxCacheEntries = normalizeMaxCacheEntries(settings.maxCacheEntries);
|
||||
if (!PROMPT_LANGUAGES[settings.promptLanguage]) settings.promptLanguage = DEFAULT_SETTINGS.promptLanguage;
|
||||
if (!(PROMPT_LANGUAGES as Record<string, string>)[settings.promptLanguage])
|
||||
settings.promptLanguage = DEFAULT_SETTINGS.promptLanguage;
|
||||
settings.minCards = normalizeCardCount(settings.minCards, DEFAULT_SETTINGS.minCards);
|
||||
settings.maxCards = normalizeCardCount(settings.maxCards, DEFAULT_SETTINGS.maxCards);
|
||||
if (settings.maxCards < settings.minCards) settings.maxCards = settings.minCards;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { Notice, setIcon } from 'obsidian';
|
||||
|
||||
export function addIconButton(parent, icon, title, onClick) {
|
||||
export function addIconButton(parent: HTMLElement, icon: string, title: string, onClick: () => void | Promise<void>) {
|
||||
const button = parent.createEl('button', {
|
||||
cls: 'parallel-reader-icon-button',
|
||||
attr: { type: 'button', 'aria-label': title },
|
||||
|
|
@ -26,7 +26,13 @@ export function addIconButton(parent, icon, title, onClick) {
|
|||
return button;
|
||||
}
|
||||
|
||||
export function addTextButton(parent, icon, label, onClick, cls?) {
|
||||
export function addTextButton(
|
||||
parent: HTMLElement,
|
||||
icon: string | null,
|
||||
label: string,
|
||||
onClick: () => void | Promise<void>,
|
||||
cls?: string,
|
||||
) {
|
||||
const button = parent.createEl('button', {
|
||||
cls: cls || 'parallel-reader-text-button',
|
||||
attr: { type: 'button' },
|
||||
|
|
@ -46,7 +52,7 @@ export function addTextButton(parent, icon, label, onClick, cls?) {
|
|||
return button;
|
||||
}
|
||||
|
||||
export async function copyToClipboard(text, successMsg) {
|
||||
export async function copyToClipboard(text: string, successMsg: string) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
new Notice(successMsg);
|
||||
|
|
|
|||
28
src/view.ts
28
src/view.ts
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
import { ItemView, MarkdownRenderer, Menu, Notice, TFile } from 'obsidian';
|
||||
import { ItemView, MarkdownRenderer, Menu, Notice, TFile, type WorkspaceLeaf } from 'obsidian';
|
||||
import { activeIndexAfterCardDelete, removeCardAt, updateCardAt } from './cards';
|
||||
import { cardsToMarkdown, cardToMarkdown, cardToPlain } from './markdown';
|
||||
import { CardEditModal } from './modal';
|
||||
|
|
@ -21,7 +21,7 @@ export class ParallelReaderView extends ItemView {
|
|||
loadingMessage: string;
|
||||
errorMessage: string;
|
||||
|
||||
constructor(leaf, plugin: PluginHost) {
|
||||
constructor(leaf: WorkspaceLeaf, plugin: PluginHost) {
|
||||
super(leaf);
|
||||
this.plugin = plugin;
|
||||
this.sections = [];
|
||||
|
|
@ -78,7 +78,7 @@ export class ParallelReaderView extends ItemView {
|
|||
return true;
|
||||
}
|
||||
|
||||
async loadFor(file, sections, stale) {
|
||||
async loadFor(file: TFile, sections: ResolvedCard[], stale: boolean) {
|
||||
this.sourceFile = file;
|
||||
this.sections = sections;
|
||||
this.stale = !!stale;
|
||||
|
|
@ -87,7 +87,7 @@ export class ParallelReaderView extends ItemView {
|
|||
this.render();
|
||||
}
|
||||
|
||||
async renderLoading(file, message) {
|
||||
async renderLoading(file: TFile, message: string) {
|
||||
this.sourceFile = file;
|
||||
this.sections = [];
|
||||
this.stale = false;
|
||||
|
|
@ -96,7 +96,7 @@ export class ParallelReaderView extends ItemView {
|
|||
this.render();
|
||||
}
|
||||
|
||||
async renderError(file, message) {
|
||||
async renderError(file: TFile, message: string) {
|
||||
this.sourceFile = file;
|
||||
this.sections = [];
|
||||
this.stale = false;
|
||||
|
|
@ -105,7 +105,7 @@ export class ParallelReaderView extends ItemView {
|
|||
this.render();
|
||||
}
|
||||
|
||||
renderEmptyWithHint(file) {
|
||||
renderEmptyWithHint(file: TFile) {
|
||||
this.sourceFile = file;
|
||||
this.sections = [];
|
||||
this.stale = false;
|
||||
|
|
@ -129,9 +129,9 @@ export class ParallelReaderView extends ItemView {
|
|||
const actions = headerRow.createDiv({ cls: 'parallel-reader-actions' });
|
||||
if (this.sourceFile) {
|
||||
if (this.plugin.isGeneratingFile(this.sourceFile)) {
|
||||
addIconButton(actions, 'square', this.plugin.t('actionCancel'), () =>
|
||||
this.plugin.cancelGenerationForFile(this.sourceFile),
|
||||
);
|
||||
addIconButton(actions, 'square', this.plugin.t('actionCancel'), () => {
|
||||
this.plugin.cancelGenerationForFile(this.sourceFile);
|
||||
});
|
||||
} else {
|
||||
addIconButton(actions, 'refresh-cw', this.plugin.t('actionRegenerate'), () =>
|
||||
this.plugin.runForFile(this.sourceFile, true),
|
||||
|
|
@ -270,7 +270,7 @@ export class ParallelReaderView extends ItemView {
|
|||
}
|
||||
}
|
||||
|
||||
setActiveSection(idx) {
|
||||
setActiveSection(idx: number) {
|
||||
if (idx === this.activeIdx) return;
|
||||
if (this.activeIdx >= 0 && this.cards[this.activeIdx]) {
|
||||
this.cards[this.activeIdx].removeClass('is-active');
|
||||
|
|
@ -282,7 +282,7 @@ export class ParallelReaderView extends ItemView {
|
|||
}
|
||||
}
|
||||
|
||||
moveActiveSection(delta) {
|
||||
moveActiveSection(delta: number) {
|
||||
const nextIdx = nextCardIndex(this.activeIdx, this.sections.length, delta);
|
||||
this.setActiveSection(nextIdx);
|
||||
this.focusSummaryPane();
|
||||
|
|
@ -313,7 +313,7 @@ export class ParallelReaderView extends ItemView {
|
|||
}
|
||||
}
|
||||
|
||||
async deleteCard(index) {
|
||||
async deleteCard(index: number) {
|
||||
if (!this.sourceFile) return false;
|
||||
const nextSections = removeCardAt(this.sections, index);
|
||||
if (nextSections.length === this.sections.length) return false;
|
||||
|
|
@ -326,7 +326,7 @@ export class ParallelReaderView extends ItemView {
|
|||
return true;
|
||||
}
|
||||
|
||||
openEditCardModal(index) {
|
||||
openEditCardModal(index: number) {
|
||||
if (!this.sourceFile || !this.sections[index]) return false;
|
||||
new CardEditModal(this.app, this.plugin, this.sections[index], async (patch) => {
|
||||
await this.updateCard(index, patch);
|
||||
|
|
@ -334,7 +334,7 @@ export class ParallelReaderView extends ItemView {
|
|||
return true;
|
||||
}
|
||||
|
||||
async updateCard(index, patch: CardPatch) {
|
||||
async updateCard(index: number, patch: CardPatch) {
|
||||
if (!this.sourceFile) return false;
|
||||
const nextSections = updateCardAt(this.sections, index, patch);
|
||||
if (nextSections.length !== this.sections.length) return false;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"lib": ["ES2022", "DOM"],
|
||||
"types": ["node"],
|
||||
"strict": false,
|
||||
"noImplicitAny": false,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
|
|
|
|||
Loading…
Reference in a new issue