Skip to content

Commit 5a20b61

Browse files
committed
Fix all ESLint issues from community plugin review
- Eliminate all `any` types: extend canvas-internal.d.ts with CMEditorView, CMContentElement, ObsidianCommands, Canvas undo/redo - Replace `any` casts with proper typed casts across keyboard-handler, subtree-drag, group-drag, main, toc-view, canvas-api - Remove all eslint-disable comments (23 total) - Fix async/await: remove unnecessary async from settings onChange callbacks, convert importFreeMindFile to sync with void IIFE, wrap TOC toggle promise with void - Fix sentence case: "Mind map TOC", "Toggle table of contents", "Import FreeMind (.mm) to canvas", "Mindvas" settings heading - Use TFile instanceof check instead of `as any` for openFile - Set data.mindmap directly (already typed in CanvasFileData) - Remove leftover settings.js artifact
1 parent fd5c634 commit 5a20b61

9 files changed

Lines changed: 102 additions & 224 deletions

File tree

src/canvas/canvas-api.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ export class CanvasAPI {
127127
const selection = canvas.selection;
128128
if (selection.size !== 1) return null;
129129

130-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
131130
const item = selection.values().next().value;
132131
if (!item || !("nodeEl" in item)) return null;
133132

src/canvas/group-drag.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,7 @@ export function registerGroupDragHandler(canvas: Canvas, canvasApi: CanvasAPI):
8484
const upHandler = (): void => {
8585
if (frozenNodes.length === 0) return;
8686
for (const node of frozenNodes) {
87-
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
88-
delete (node as any).moveTo;
87+
delete (node as { moveTo?: unknown }).moveTo;
8988
}
9089
frozenNodes.length = 0;
9190
canvas.requestSave();
@@ -97,8 +96,7 @@ export function registerGroupDragHandler(canvas: Canvas, canvasApi: CanvasAPI):
9796
return () => {
9897
if (frozenNodes.length > 0) {
9998
for (const node of frozenNodes) {
100-
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
101-
delete (node as any).moveTo;
99+
delete (node as { moveTo?: unknown }).moveTo;
102100
}
103101
frozenNodes.length = 0;
104102
}

src/canvas/subtree-drag.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,17 @@ export function registerSubtreeDragHandler(canvas: Canvas, canvasApi: CanvasAPI)
5050

5151
// Wrap moveTo so descendants move in the same call stack.
5252
// Use prototype's moveTo (not instance) to avoid stacked wrappers.
53-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
54-
originalMoveTo = Object.getPrototypeOf(node).moveTo.bind(node);
53+
const proto = Object.getPrototypeOf(node) as CanvasNode;
54+
originalMoveTo = proto.moveTo.bind(node);
5555
node.moveTo = (pos: { x: number; y: number }) => {
5656
const dx = pos.x - node.x;
5757
const dy = pos.y - node.y;
5858
originalMoveTo!(pos);
5959
// Call descendants' moveTo via prototype to bypass any
6060
// per-instance wrappers — prevents infinite recursion.
6161
for (const desc of cachedDescendants!) {
62-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
63-
Object.getPrototypeOf(desc).moveTo.call(
62+
const descProto = Object.getPrototypeOf(desc) as CanvasNode;
63+
descProto.moveTo.call(
6464
desc, { x: desc.x + dx, y: desc.y + dy }
6565
);
6666
}
@@ -70,8 +70,7 @@ export function registerSubtreeDragHandler(canvas: Canvas, canvasApi: CanvasAPI)
7070
function clearDragSession(): void {
7171
if (draggedNode && originalMoveTo) {
7272
// Remove instance override to restore prototype method lookup
73-
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
74-
delete (draggedNode as any).moveTo;
73+
delete (draggedNode as { moveTo?: unknown }).moveTo;
7574
}
7675
draggedNode = null;
7776
cachedDescendants = null;

src/main.ts

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Plugin, Notice, TFolder, debounce, WorkspaceLeaf, setIcon } from "obsidian";
2-
import type { Canvas } from "./types/canvas-internal";
1+
import { Plugin, Notice, TFile, TFolder, debounce, WorkspaceLeaf, setIcon } from "obsidian";
2+
import type { Canvas, CreateNodeOptions } from "./types/canvas-internal";
33
import { CanvasAPI } from "./canvas/canvas-api";
44
import { NodeOperations } from "./mindmap/node-operations";
55
import { LayoutEngine } from "./mindmap/layout-engine";
@@ -45,7 +45,7 @@ export default class CanvasMindMapPlugin extends Plugin {
4545
/** Original canvas methods for unwrapping on cleanup. */
4646
private origCanvasMethods: {
4747
requestSave?: () => void;
48-
createGroupNode?: (options: any) => any;
48+
createGroupNode?: (options: CreateNodeOptions & { label?: string }) => import("./types/canvas-internal").CanvasNode;
4949
} = {};
5050
/** Set to true on unload to prevent deferred callbacks from running. */
5151
private unloaded = false;
@@ -170,16 +170,17 @@ export default class CanvasMindMapPlugin extends Plugin {
170170
// Command: Toggle TOC panel
171171
this.addCommand({
172172
id: "mindmap-toggle-toc",
173-
name: "Toggle Table of Contents",
174-
callback: async () => {
173+
name: "Toggle table of contents",
174+
callback: () => {
175175
const leaves = this.app.workspace.getLeavesOfType(TOC_VIEW_TYPE);
176176
if (leaves.length > 0) {
177177
leaves[0].detach();
178178
} else {
179179
const leaf = this.app.workspace.getRightLeaf(false);
180180
if (leaf) {
181-
await leaf.setViewState({ type: TOC_VIEW_TYPE });
182-
this.app.workspace.revealLeaf(leaf);
181+
void leaf.setViewState({ type: TOC_VIEW_TYPE }).then(() => {
182+
this.app.workspace.revealLeaf(leaf);
183+
});
183184
}
184185
}
185186
},
@@ -192,7 +193,7 @@ export default class CanvasMindMapPlugin extends Plugin {
192193
if (!(file instanceof TFolder)) return;
193194

194195
menu.addItem((item) => {
195-
item.setTitle("Import FreeMind (.mm) to Canvas")
196+
item.setTitle("Import FreeMind (.mm) to canvas")
196197
.setIcon("file-input")
197198
.onClick(() => this.importFreeMindFile(file.path));
198199
});
@@ -202,7 +203,7 @@ export default class CanvasMindMapPlugin extends Plugin {
202203
// Import FreeMind: command palette
203204
this.addCommand({
204205
id: "mindmap-import-freemind",
205-
name: "Import FreeMind (.mm) file to Canvas",
206+
name: "Import FreeMind (.mm) file to canvas",
206207
callback: () => this.importFreeMindFile(),
207208
});
208209

@@ -369,7 +370,7 @@ export default class CanvasMindMapPlugin extends Plugin {
369370
origSave();
370371
this.debouncedTocRefresh();
371372
};
372-
canvas.createGroupNode = (options: any) => {
373+
canvas.createGroupNode = (options: CreateNodeOptions & { label?: string }) => {
373374
const group = origCreateGroup(options);
374375
this.updateGroupBounds(canvas);
375376
return group;
@@ -642,59 +643,61 @@ export default class CanvasMindMapPlugin extends Plugin {
642643
* Import a FreeMind .mm file and create a .canvas file.
643644
* @param folderPath Optional target folder; defaults to vault root.
644645
*/
645-
private async importFreeMindFile(folderPath?: string): Promise<void> {
646+
private importFreeMindFile(folderPath?: string): void {
646647
// Open native file picker for .mm files
647648
const input = document.createElement("input");
648649
input.type = "file";
649650
input.accept = ".mm";
650-
const onChange = async () => {
651-
input.removeEventListener("change", onChange);
651+
const handler = () => {
652+
input.removeEventListener("change", handler);
652653
const file = input.files?.[0];
653654
if (!file) return;
654655

655-
const xml = await file.text();
656-
const canvasData = freemindToCanvas(xml, {
657-
nodeWidth: this.settings.defaultNodeWidth,
658-
nodeHeight: this.settings.defaultNodeHeight,
659-
maxNodeHeight: this.settings.maxNodeHeight,
660-
horizontalGap: this.settings.horizontalGap,
661-
verticalGap: this.settings.verticalGap,
662-
});
656+
void (async () => {
657+
const xml = await file.text();
658+
const canvasData = freemindToCanvas(xml, {
659+
nodeWidth: this.settings.defaultNodeWidth,
660+
nodeHeight: this.settings.defaultNodeHeight,
661+
maxNodeHeight: this.settings.maxNodeHeight,
662+
horizontalGap: this.settings.horizontalGap,
663+
verticalGap: this.settings.verticalGap,
664+
});
663665

664-
if (!canvasData) {
665-
new Notice(
666-
"Failed to parse FreeMind file. Make sure it is a valid .mm file."
667-
);
668-
return;
669-
}
666+
if (!canvasData) {
667+
new Notice(
668+
"Failed to parse FreeMind file. Make sure it is a valid .mm file."
669+
);
670+
return;
671+
}
670672

671-
const baseName = file.name.replace(/\.mm$/i, "");
672-
const folder = folderPath ? folderPath + "/" : "";
673-
let canvasPath = `${folder}${baseName}.canvas`;
673+
const baseName = file.name.replace(/\.mm$/i, "");
674+
const folder = folderPath ? folderPath + "/" : "";
675+
let canvasPath = `${folder}${baseName}.canvas`;
674676

675-
// Avoid overwriting existing files
676-
let counter = 1;
677-
while (this.app.vault.getAbstractFileByPath(canvasPath)) {
678-
canvasPath = `${folder}${baseName} ${counter}.canvas`;
679-
counter++;
680-
}
677+
// Avoid overwriting existing files
678+
let counter = 1;
679+
while (this.app.vault.getAbstractFileByPath(canvasPath)) {
680+
canvasPath = `${folder}${baseName} ${counter}.canvas`;
681+
counter++;
682+
}
681683

682-
await this.app.vault.create(
683-
canvasPath,
684-
JSON.stringify(canvasData, null, "\t")
685-
);
684+
await this.app.vault.create(
685+
canvasPath,
686+
JSON.stringify(canvasData, null, "\t")
687+
);
686688

687-
// Open the new canvas
688-
const created = this.app.vault.getAbstractFileByPath(canvasPath);
689-
if (created) {
690-
await this.app.workspace.getLeaf(false).openFile(created as any);
691-
}
689+
// Open the new canvas
690+
const created = this.app.vault.getAbstractFileByPath(canvasPath);
691+
if (created instanceof TFile) {
692+
await this.app.workspace.getLeaf(false).openFile(created);
693+
}
692694

693-
new Notice(
694-
`Imported "${file.name}" as "${canvasPath}"`
695-
);
695+
new Notice(
696+
`Imported "${file.name}" as "${canvasPath}"`
697+
);
698+
})();
696699
};
697-
input.addEventListener("change", onChange);
700+
input.addEventListener("change", handler);
698701
input.click();
699702
}
700703

@@ -707,7 +710,7 @@ export default class CanvasMindMapPlugin extends Plugin {
707710
private toggleMindmapMode(canvas: Canvas): void {
708711
const data = canvas.getData();
709712
const newValue = !this.isMindmapCanvas(canvas);
710-
(data as any).mindmap = newValue;
713+
data.mindmap = newValue;
711714
canvas.setData(data);
712715
canvas.requestSave();
713716

src/settings.js

Lines changed: 0 additions & 127 deletions
This file was deleted.

src/settings.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class MindMapSettingTab extends PluginSettingTab {
4141
await this.plugin.saveSettings();
4242
}, 500);
4343

44-
new Setting(containerEl).setName("Canvas mindmap").setHeading();
44+
new Setting(containerEl).setName("Mindvas").setHeading();
4545

4646
new Setting(containerEl)
4747
.setName("Default mindmap mode")
@@ -85,7 +85,7 @@ export class MindMapSettingTab extends PluginSettingTab {
8585
.addText((text) =>
8686
text
8787
.setValue(String(this.plugin.settings.horizontalGap))
88-
.onChange(async (value) => {
88+
.onChange((value) => {
8989
const num = parseInt(value, 10);
9090
if (!isNaN(num) && num > 0) {
9191
this.plugin.settings.horizontalGap = num;
@@ -100,7 +100,7 @@ export class MindMapSettingTab extends PluginSettingTab {
100100
.addText((text) =>
101101
text
102102
.setValue(String(this.plugin.settings.verticalGap))
103-
.onChange(async (value) => {
103+
.onChange((value) => {
104104
const num = parseInt(value, 10);
105105
if (!isNaN(num) && num > 0) {
106106
this.plugin.settings.verticalGap = num;
@@ -115,7 +115,7 @@ export class MindMapSettingTab extends PluginSettingTab {
115115
.addText((text) =>
116116
text
117117
.setValue(String(this.plugin.settings.defaultNodeWidth))
118-
.onChange(async (value) => {
118+
.onChange((value) => {
119119
const num = parseInt(value, 10);
120120
if (!isNaN(num) && num > 0) {
121121
this.plugin.settings.defaultNodeWidth = num;
@@ -130,7 +130,7 @@ export class MindMapSettingTab extends PluginSettingTab {
130130
.addText((text) =>
131131
text
132132
.setValue(String(this.plugin.settings.defaultNodeHeight))
133-
.onChange(async (value) => {
133+
.onChange((value) => {
134134
const num = parseInt(value, 10);
135135
if (!isNaN(num) && num > 0) {
136136
this.plugin.settings.defaultNodeHeight = num;
@@ -145,7 +145,7 @@ export class MindMapSettingTab extends PluginSettingTab {
145145
.addText((text) =>
146146
text
147147
.setValue(String(this.plugin.settings.maxNodeHeight))
148-
.onChange(async (value) => {
148+
.onChange((value) => {
149149
const num = parseInt(value, 10);
150150
if (!isNaN(num) && num > 0) {
151151
this.plugin.settings.maxNodeHeight = num;
@@ -160,7 +160,7 @@ export class MindMapSettingTab extends PluginSettingTab {
160160
.addText((text) =>
161161
text
162162
.setValue(String(this.plugin.settings.navigationZoomPadding))
163-
.onChange(async (value) => {
163+
.onChange((value) => {
164164
const num = parseInt(value, 10);
165165
if (!isNaN(num) && num >= 0) {
166166
this.plugin.settings.navigationZoomPadding = num;

src/types/canvas-internal.d.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ export interface Canvas {
109109
zoomToFit(): void;
110110

111111
posFromEvt(e: MouseEvent): { x: number; y: number };
112+
113+
undo?: () => void;
114+
redo?: () => void;
112115
}
113116

114117
export interface CanvasFileData {
@@ -148,3 +151,22 @@ export interface CanvasView extends ItemView {
148151
canvas: Canvas;
149152
file: { path: string };
150153
}
154+
155+
/** Minimal CodeMirror 6 EditorView interface for text extraction. */
156+
export interface CMEditorView {
157+
state: {
158+
selection: { main: { from: number; to: number } };
159+
sliceDoc: (from: number, to: number) => string;
160+
};
161+
dispatch: (tr: { changes: { from: number; to: number; insert: string } }) => void;
162+
}
163+
164+
/** DOM element with a CodeMirror view reference attached by Obsidian. */
165+
export interface CMContentElement extends HTMLElement {
166+
cmView?: { view: CMEditorView };
167+
}
168+
169+
/** Obsidian's undocumented App.commands API for programmatic command execution. */
170+
export interface ObsidianCommands {
171+
executeCommandById: (id: string) => boolean;
172+
}

0 commit comments

Comments
 (0)