fix: apply lint and typing fixes across source files

This commit is contained in:
Chentao Yang 2026-06-13 19:44:48 +02:00
parent c65d288b67
commit 0697185eba
6 changed files with 117 additions and 70 deletions

View file

@ -1,19 +1,25 @@
import { Editor } from "codemirror";
import { App, MarkdownView, TFile } from "obsidian";
import { App, MarkdownView, TFile, WorkspaceLeaf } from "obsidian";
type MarkdownEditor = Editor & {
replaceRange: Editor["replaceRange"];
};
type MarkdownViewWithEditor = MarkdownView & {
editor?: MarkdownEditor;
sourceMode?: { cmEditor?: MarkdownEditor };
};
function isMarkdownViewWithEditor(view: unknown): view is MarkdownViewWithEditor {
return view instanceof MarkdownView;
}
export function getEditorForFile(app: App, file: TFile): MarkdownEditor | null {
let editor = null;
app.workspace.iterateAllLeaves((leaf) => {
if (leaf.view instanceof MarkdownView && leaf.view.file === file) {
const view = leaf.view as MarkdownView & {
editor?: MarkdownEditor;
sourceMode?: { cmEditor?: MarkdownEditor };
};
editor = view.editor || view.sourceMode?.cmEditor;
let editor: MarkdownEditor | null = null;
app.workspace.iterateAllLeaves((leaf: WorkspaceLeaf) => {
const { view } = leaf;
if (isMarkdownViewWithEditor(view) && view.file === file) {
editor = view.editor ?? view.sourceMode?.cmEditor ?? null;
}
});
return editor;

View file

@ -2,15 +2,15 @@ import { App, Modal } from "obsidian";
interface IConfirmationDialogParams {
cta: string;
onAccept: (...args: unknown[]) => Promise<void>;
onCancel?: (...args: unknown[]) => void;
onAccept: () => Promise<void>;
onCancel?: () => void;
text: string;
title: string;
}
export class ConfirmationModal extends Modal {
private config: IConfirmationDialogParams;
private accepted: boolean;
private accepted = false;
private buttonContainerEl: HTMLElement;
constructor(app: App, config: IConfirmationDialogParams) {
@ -19,28 +19,30 @@ export class ConfirmationModal extends Modal {
const { cta, onAccept, text, title } = config;
this.containerEl.addClass('mod-confirmation');
this.containerEl.addClass("mod-confirmation");
this.titleEl.setText(title);
this.contentEl.setText(text);
this.buttonContainerEl = this.modalEl.createDiv('modal-button-container');
this.buttonContainerEl = this.modalEl.createDiv("modal-button-container");
const acceptBtnEl = this.buttonContainerEl.createEl("button", {
cls: "mod-cta",
text: cta,
});
acceptBtnEl.addEventListener("click", async (e) => {
e.preventDefault();
acceptBtnEl.addEventListener("click", (event: MouseEvent) => {
event.preventDefault();
this.accepted = true;
this.close();
onAccept(e);
void onAccept();
});
const cancelBtnEl = this.buttonContainerEl.createEl("button", { text: "Never mind" });
cancelBtnEl.addEventListener("click", e => {
e.preventDefault();
this.close();
});
const cancelBtnEl = this.buttonContainerEl.createEl("button", {
text: "Never mind",
});
cancelBtnEl.addEventListener("click", (event: MouseEvent) => {
event.preventDefault();
this.close();
});
}
onClose(): void {

35
src/moment.ts Normal file
View file

@ -0,0 +1,35 @@
export interface MomentLike {
unix(): number;
startOf(unit: string): MomentLike;
endOf(unit: string): MomentLike;
format(pattern?: string): string;
diff(input: string | MomentLike, unit?: string): number;
clone(): MomentLike;
subtract(amount: number, unit: string): MomentLike;
add(amount: number, unit: string): MomentLike;
isSameOrBefore(input: MomentLike, unit?: string): boolean;
isBefore(input: MomentLike, unit?: string): boolean;
isAfter(input: MomentLike, unit?: string): boolean;
isSame(input: MomentLike, unit?: string): boolean;
isoWeek(): number;
year(): number;
month(): number;
date(): number;
fromNow(): string;
}
export interface MomentFactory {
(): MomentLike;
(input: string, format?: string, strict?: boolean): MomentLike;
unix(timestamp: number): MomentLike;
}
declare global {
interface Window {
moment: MomentFactory;
}
}
export function getMoment(): MomentFactory {
return window.moment;
}

View file

@ -3,6 +3,15 @@ import { ISettings } from "./settings";
import { ISubTask, ITask } from "./things";
import { getHeadingLevel, getTab, groupBy, toHeading } from "./textUtils";
interface VaultConfigReader {
getConfig(key: "useTab"): boolean;
getConfig(key: "tabSize"): number;
}
function getVaultConfig(app: App): VaultConfigReader {
return app.vault as unknown as VaultConfigReader;
}
export class ToolkitRenderer {
private app: App;
private settings: ISettings;
@ -14,31 +23,33 @@ export class ToolkitRenderer {
}
renderTask(task: ITask): string {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const vault = this.app.vault as any;
const vault = getVaultConfig(this.app);
const tab = getTab(vault.getConfig("useTab"), vault.getConfig("tabSize"));
const prefix = this.settings.tagPrefix;
const tags = Array.from(new Set(task.tags
.filter((tag) => !!tag)
.map((tag) => tag.replace(/\s+/g, "-").toLowerCase())
.sort()
))
const tags = Array.from(
new Set(
task.tags
.filter((tag) => !!tag)
.map((tag) => tag.replace(/\s+/g, "-").toLowerCase())
.sort()
)
)
.map((tag) => `#${prefix}${tag}`)
.join(" ");
const taskTitle = `[${task.title}](things:///show?id=${task.uuid}) ${tags}`.trimEnd()
const taskTitle = `[${task.title}](things:///show?id=${task.uuid}) ${tags}`.trimEnd();
const notes = this.settings.doesSyncNoteBody
? String(task.notes || "")
.trimEnd()
.split("\n")
.filter((line) => !!line)
.map((noteLine) => `${tab}${noteLine}`)
: ""
.trimEnd()
.split("\n")
.filter((line) => !!line)
.map((noteLine) => `${tab}${noteLine}`)
: [];
return [
`- [${task.cancelled ? this.settings.canceledMark : 'x'}] ${taskTitle}`,
`- [${task.cancelled ? this.settings.canceledMark : "x"}] ${taskTitle}`,
...notes,
...task.subtasks.map(
(subtask: ISubTask) =>
@ -51,15 +62,20 @@ export class ToolkitRenderer {
public render(tasks: ITask[]): string {
const { sectionHeading, doesSyncProject, doesAddNewlineBeforeHeadings } = this.settings;
const headings = groupBy<ITask>(tasks, (task) => task.area || (doesSyncProject ? task.project : "") || "");
const headingLevel = getHeadingLevel(sectionHeading);
const headings = groupBy<ITask>(
tasks,
(task) => task.area || (doesSyncProject ? task.project : "") || ""
);
const headingLevel = getHeadingLevel(sectionHeading) ?? 2;
const output = [sectionHeading];
Object.entries(headings).map(([heading, tasks]) => {
Object.entries(headings).forEach(([heading, groupedTasks]) => {
if (heading !== "") {
output.push(toHeading(heading, headingLevel + 1, doesAddNewlineBeforeHeadings));
output.push(
toHeading(heading, headingLevel + 1, doesAddNewlineBeforeHeadings)
);
}
output.push(...tasks.map(this.renderTask));
output.push(...groupedTasks.map(this.renderTask));
});
return output.join("\n");

View file

@ -6,7 +6,7 @@ export const TASK_FETCH_LIMIT = 1000;
interface ISpawnResults {
stdOut: Buffer[];
stdErr: Buffer[];
code: number;
code: number | null;
}
function parseCSV<T>(csv: Buffer[]): T[] {
@ -47,10 +47,10 @@ async function handleSqliteQuery(
});
spawned.on("error", (err: Error) => {
stdErr.push(Buffer.from(String(err.stack), "ascii"));
stdErr.push(Buffer.from(err.stack ?? err.message, "utf-8"));
});
spawned.on("close", (code: number) => finish({ stdErr, stdOut, code }));
spawned.on("exit", (code: number) => finish({ stdErr, stdOut, code }));
spawned.on("close", (code: number | null) => finish({ stdErr, stdOut, code }));
spawned.on("exit", (code: number | null) => finish({ stdErr, stdOut, code }));
});
}
@ -58,10 +58,10 @@ export async function querySqliteDB<T>(
dbPath: string,
query: string
): Promise<T[]> {
const { stdOut, stdErr } = await handleSqliteQuery(dbPath, query);
if (stdErr.length) {
const error = Buffer.concat(stdErr).toString("utf-8");
return Promise.reject(error);
const { stdOut, stdErr, code } = await handleSqliteQuery(dbPath, query);
if (stdErr.length || code !== 0) {
const error = Buffer.concat(stdErr).toString("utf-8") || `sqlite3 exited with code ${String(code)}`;
throw new Error(error);
}
return parseCSV<T>(stdOut);
}

View file

@ -32,7 +32,7 @@ export function groupBy<T>(
}
export function isMacOS(): boolean {
return navigator.appVersion.indexOf("Mac") !== -1;
return navigator.userAgent.includes("Mac");
}
export async function updateSection(
@ -67,9 +67,6 @@ export async function updateSection(
const editor = getEditorForFile(app, file);
if (editor) {
// if the "## Logbook" header exists, we just replace the
// section. If it doesn't, we need to append it to the end
// if the file and add `\n` for separation.
if (logbookSectionLineNum !== -1) {
const currentSection = fileLines
.slice(
@ -89,16 +86,14 @@ export async function updateSection(
: { line: fileLines.length, ch: 0 };
editor.replaceRange(`${sectionContents}\n`, from, to);
return true;
} else {
const pos = { line: fileLines.length, ch: 0 };
editor.replaceRange(`\n\n${sectionContents}`, pos, pos);
return true;
}
const pos = { line: fileLines.length, ch: 0 };
editor.replaceRange(`\n\n${sectionContents}`, pos, pos);
return true;
}
// Editor is not open, modify the file on disk...
if (logbookSectionLineNum !== -1) {
// Section already exists, just replace
const prefix = fileLines.slice(0, logbookSectionLineNum);
const suffix =
nextSectionLineNum !== -1 ? fileLines.slice(nextSectionLineNum) : [];
@ -114,19 +109,12 @@ export async function updateSection(
return false;
}
await vault.process(
file,
() => [...prefix, sectionContents, ...suffix].join("\n")
);
return true;
} else {
// Section does not exist, append to end of file.
await vault.process(
file,
() => [...fileLines, "", sectionContents].join("\n")
);
await vault.process(file, () => [...prefix, sectionContents, ...suffix].join("\n"));
return true;
}
await vault.process(file, () => [...fileLines, "", sectionContents].join("\n"));
return true;
}
export function getSectionContents(