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:
wujunchen 2026-04-26 08:58:41 +08:00
parent 89139c37dc
commit 31e57c8b19
12 changed files with 166 additions and 135 deletions

54
main.js

File diff suppressed because one or more lines are too long

63
main.ts
View file

@ -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');

View file

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

View file

@ -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 */
}

View file

@ -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');

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,7 +7,7 @@
"lib": ["ES2022", "DOM"],
"types": ["node"],
"strict": false,
"noImplicitAny": false,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,