Using mouse events rather than event listeners; fixed workspace leaf refresh issue; more robust modal closure refreshing; window.setTimeout in settings.

This commit is contained in:
NebulousNessie 2025-11-11 11:50:45 +00:00
parent 6d0e477677
commit 01723cc8ac
7 changed files with 1184 additions and 820 deletions

1802
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -21,7 +21,7 @@
"obsidian": "latest",
"obsidian-typings": "^3.12.0",
"tslib": "2.4.0",
"typescript": "^4.9.5",
"typescript": "^5.9.3",
"uuid": "^9.0.0"
},
"dependencies": {

View file

@ -1,4 +1,4 @@
import { TFile } from "obsidian";
import { TFile, Component } from "obsidian";
import { PinMarker, PolylineMarker, getCodeBlockContainer } from "./helpers";
import { MOCBlockSettings } from "./settings";
@ -20,7 +20,8 @@ export function renderPinMarker(
el: HTMLElement,
refreshMOCBlock: Function,
saveUpdatedMarker: Function,
deleteMarkerFromFile: Function) {
deleteMarkerFromFile: Function,
parentComponent?: Component) {
// Position marker on MOC Block image
@ -134,7 +135,7 @@ export function renderPinMarker(
wasDragged = false;
});
document.addEventListener("mouseup", async () => {
const onDocMouseUp = async () => {
if (isDragging) {
isDragging = false;
@ -147,9 +148,9 @@ export function renderPinMarker(
);
}
}
});
};
document.addEventListener("mousemove", (evt) => {
const onDocMouseMove = (evt: MouseEvent) => {
if (!isDragging) return;
wasDragged = true;
@ -173,7 +174,15 @@ export function renderPinMarker(
// 🔹 Only update UI live
markerEl.style.left = `${percentX}%`;
markerEl.style.top = `${percentY}%`;
});
};
if (parentComponent && typeof parentComponent.registerDomEvent === "function") {
parentComponent.registerDomEvent(document, "mouseup", onDocMouseUp);
parentComponent.registerDomEvent(document, "mousemove", onDocMouseMove);
} else {
document.addEventListener("mouseup", onDocMouseUp);
document.addEventListener("mousemove", onDocMouseMove);
}
//--------------------------------
} else {
console.warn("SVG element not found");
@ -196,7 +205,8 @@ export function renderPolylineMarker(
el: HTMLElement,
refreshMOCBlock: Function,
saveUpdatedMarker: Function,
deleteMarkerFromFile: Function) {
deleteMarkerFromFile: Function,
parentComponent?: Component) {
// Get style config for this polyline
const styleName = poly.styleName ?? "Default";
@ -292,7 +302,8 @@ export function addResizeHandle(
el: HTMLElement,
refreshMOCBlock: any,
saveUpdatedMarker: any,
deleteMarkerFromFile: any
deleteMarkerFromFile: any,
parentComponent?: Component
) {
if (!isEditMode) return;
@ -345,12 +356,12 @@ export function addResizeHandle(
for (const marker of markerData.markers) {
if (marker.type === "pin") {
renderPinMarker(
marker, container, img, settings, isEditMode, ctx, app, moc_id, markerFile, source, el, refreshMOCBlock, saveUpdatedMarker, deleteMarkerFromFile
marker, container, img, settings, isEditMode, ctx, app, moc_id, markerFile, source, el, refreshMOCBlock, saveUpdatedMarker, deleteMarkerFromFile, parentComponent
);
}
if (marker.type === "polyline") {
renderPolylineMarker(
marker, container, img, settings, isEditMode, ctx, app, moc_id, markerFile, source, el, refreshMOCBlock, saveUpdatedMarker, deleteMarkerFromFile
marker, container, img, settings, isEditMode, ctx, app, moc_id, markerFile, source, el, refreshMOCBlock, saveUpdatedMarker, deleteMarkerFromFile, parentComponent
);
}
}
@ -358,16 +369,16 @@ export function addResizeHandle(
pendingAnimation = false;
};
window.addEventListener("mousemove", (e) => {
const onMouseMove = (e: MouseEvent) => {
if (!isResizing) return;
latestEvent = e;
if (!pendingAnimation) {
pendingAnimation = true;
requestAnimationFrame(handleResizeFrame);
}
});
};
window.addEventListener("mouseup", async () => {
const onMouseUp = async () => {
if (isResizing) {
isResizing = false;
document.body.classList.remove("mocblockRenderer-userselect-none");
@ -404,5 +415,13 @@ export function addResizeHandle(
await app.vault.process(file, () => newContent);
}
});
};
if (parentComponent && typeof parentComponent.registerDomEvent === "function") {
parentComponent.registerDomEvent(window, "mousemove", onMouseMove);
parentComponent.registerDomEvent(window, "mouseup", onMouseUp);
} else {
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", onMouseUp);
}
}

View file

@ -54,8 +54,16 @@ export default class MOCBlockPlugin extends Plugin {
this.addChild(mocBlockComponent);
//console.log("Processing MOC block:", source);
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
const isEditMode: boolean = !!(view && view.getMode && view.getMode() === "source");
let view: MarkdownView | undefined = undefined;
for (const leaf of this.app.workspace.getLeavesOfType("markdown")) {
const candidate = (leaf as any).view as MarkdownView | undefined;
if (candidate && candidate.file && candidate.file.path === ctx.sourcePath) {
view = candidate;
break;
}
} // Fix for getting correct view in multi-pane setup, and if page refreshes without being selected (i.e. file explorer clicked)
const isEditMode: boolean = !!(view && typeof view.getMode === "function" && view.getMode() === "source");
// Parse YAML MOC Block Data
let config: any;
@ -202,7 +210,8 @@ export default class MOCBlockPlugin extends Plugin {
el,
(source: string, el: HTMLElement, ctx: any) => refreshMOCBlock(this.app, source, el, ctx, mocBlockComponent),
saveUpdatedMarker,
deleteMarkerFromFile
deleteMarkerFromFile,
mocBlockComponent
);
// --------------------------------
@ -271,7 +280,8 @@ export default class MOCBlockPlugin extends Plugin {
el,
(source: string, el: HTMLElement, ctx: any) => refreshMOCBlock(this.app, source, el, ctx, mocBlockComponent),
saveUpdatedMarker,
deleteMarkerFromFile
deleteMarkerFromFile,
mocBlockComponent
);
}
@ -291,7 +301,8 @@ export default class MOCBlockPlugin extends Plugin {
el,
(source: string, el: HTMLElement, ctx: any) => refreshMOCBlock(this.app, source, el, ctx, mocBlockComponent),
saveUpdatedMarker,
deleteMarkerFromFile
deleteMarkerFromFile,
mocBlockComponent
);
}

View file

@ -4,81 +4,6 @@ import { styleNamesSetting } from "./settings";
import { v4 as uuidv4 } from "uuid";
// class LinkSuggestModal extends SuggestModal<TFile> {
// onChoose: (file: TFile) => void;
// constructor(app: App, onChoose: (file: TFile) => void) {
// super(app);
// this.onChoose = onChoose;
// this.setPlaceholder("Type to search for a note...");
// }
// getSuggestions(query: string): TFile[] {
// return this.app.vault.getMarkdownFiles().filter(file =>
// file.basename.toLowerCase().includes(query.toLowerCase())
// );
// }
// renderSuggestion(file: TFile, el: HTMLElement) {
// el.setText(file.basename);
// }
// onChooseSuggestion(file: TFile) {
// this.onChoose(file);
// }
// }
// type LinkSuggestion = { file: TFile; heading?: string };
// class LinkSuggestModal extends SuggestModal<LinkSuggestion> {
// onChoose: (file: TFile, heading?: string) => void;
// constructor(app: App, onChoose: (file: TFile, heading?: string) => void) {
// super(app);
// this.onChoose = onChoose;
// this.setPlaceholder("Type to search for a note or subheading...");
// }
// async getSuggestions(query: string): Promise<LinkSuggestion[]> {
// const files = this.app.vault.getMarkdownFiles();
// const lowerQuery = query.toLowerCase();
// const suggestions: LinkSuggestion[] = [];
// for (const file of files) {
// if (file.basename.toLowerCase().includes(lowerQuery)) {
// suggestions.push({ file });
// }
// // Read file for headings
// try {
// const content = await this.app.vault.read(file);
// const lines = content.split(/\r?\n/);
// for (const line of lines) {
// const headingMatch = line.match(/^(#+)\s+(.*)/);
// if (headingMatch) {
// const headingText = headingMatch[2].trim();
// if (headingText && headingText.toLowerCase().includes(lowerQuery)) {
// suggestions.push({ file, heading: headingText });
// }
// }
// }
// } catch (e) {
// // ignore file read errors
// }
// }
// return suggestions;
// }
// renderSuggestion(suggestion: LinkSuggestion, el: HTMLElement) {
// if (suggestion.heading) {
// el.createEl("div", { text: `${suggestion.file.basename} > ${suggestion.heading}` });
// } else {
// el.createEl("div", { text: suggestion.file.basename });
// }
// }
// onChooseSuggestion(suggestion: LinkSuggestion) {
// this.onChoose(suggestion.file, suggestion.heading);
// }
// }
type LinkSuggestion = { file: TFile; heading?: string };
class LinkSuggestModal extends SuggestModal<LinkSuggestion> {
@ -132,7 +57,6 @@ class LinkSuggestModal extends SuggestModal<LinkSuggestion> {
}
}
class ImageSuggestModal extends SuggestModal<TFile> {
onChoose: (file: TFile) => void;
@ -169,6 +93,7 @@ export class NewPinModal extends Modal {
percentY: number;
onSubmit: (marker: PinMarker) => Promise<void>;
onCancel?: () => void;
onCloseRefresh?: () => void;
constructor(
app: App,
@ -176,7 +101,8 @@ export class NewPinModal extends Modal {
x: number,
y: number,
onSubmit: (marker: PinMarker) => Promise<void>,
onCancel?: () => void
onCancel?: () => void,
onCloseRefresh?: () => void
) {
super(app);
this.styleNames = styleNames;
@ -184,6 +110,7 @@ export class NewPinModal extends Modal {
this.percentY = y;
this.onSubmit = onSubmit;
this.onCancel = onCancel;
this.onCloseRefresh = onCloseRefresh;
}
onOpen() {
@ -246,6 +173,7 @@ export class NewPinModal extends Modal {
onClose() {
this.contentEl.empty();
if (this.onCancel) this.onCancel();
if (this.onCloseRefresh) this.onCloseRefresh();
}
}
@ -254,6 +182,7 @@ export class PinEditModal extends Modal {
onSave: (updated: PinMarker) => void;
onDelete?: (marker: PinMarker) => void;
styleNames: Record<string, styleNamesSetting>;
onCloseRefresh?: () => void;
constructor(
app: App,
@ -261,12 +190,14 @@ export class PinEditModal extends Modal {
styleNames: Record<string, styleNamesSetting>,
onSave: (updated: PinMarker) => void,
onDelete?: (marker: PinMarker) => void
, onCloseRefresh?: () => void
) {
super(app);
this.marker = { ...marker }; // clone to avoid mutating original until save
this.styleNames = styleNames;
this.onSave = onSave;
this.onDelete = onDelete;
this.onCloseRefresh = onCloseRefresh;
}
onOpen() {
@ -331,9 +262,9 @@ export class PinEditModal extends Modal {
setIcon(btn.extraSettingsEl, "trash-2"); // Obsidian's trash icon
btn.extraSettingsEl.setAttr("aria-label", "Delete marker");
btn.extraSettingsEl.classList.add("mocblock-trash-hover");
btn.extraSettingsEl.addEventListener("click", () => {
btn.extraSettingsEl.addEventListener("click", async () => {
if (this.onDelete) {
this.onDelete(this.marker);
await this.onDelete(this.marker);
}
this.close();
});
@ -342,6 +273,7 @@ export class PinEditModal extends Modal {
onClose() {
this.contentEl.empty();
if (this.onCloseRefresh) this.onCloseRefresh();
}
}
@ -350,6 +282,7 @@ export class NewPolylineModal extends Modal {
styleNames: Record<string, styleNamesSetting>;
onSubmit: (marker: PolylineMarker) => Promise<void>;
onCancel: () => void;
onCloseRefresh?: () => void;
constructor(
@ -357,13 +290,15 @@ export class NewPolylineModal extends Modal {
points: [number, number][],
styleNames: Record<string, styleNamesSetting>,
onSubmit: (marker: PolylineMarker) => Promise<void>,
onCancel: () => void
onCancel: () => void,
onCloseRefresh?: () => void
) {
super(app);
this.points = points;
this.styleNames = styleNames;
this.onSubmit = onSubmit;
this.onCancel = onCancel;
this.onCloseRefresh = onCloseRefresh;
}
onOpen() {
@ -426,6 +361,7 @@ export class NewPolylineModal extends Modal {
onClose() {
this.contentEl.empty();
if (this.onCancel) this.onCancel();
if (this.onCloseRefresh) this.onCloseRefresh();
}
}
@ -434,6 +370,7 @@ export class PolylineEditModal extends Modal {
onSave: (updated: PolylineMarker) => void;
onDelete?: (marker: PolylineMarker) => void;
styleNames: Record<string, styleNamesSetting>;
onCloseRefresh?: () => void;
constructor(
app: App,
@ -441,12 +378,14 @@ export class PolylineEditModal extends Modal {
styleNames: Record<string, styleNamesSetting>,
onSave: (updated: PolylineMarker) => void,
onDelete?: (marker: PolylineMarker) => void
, onCloseRefresh?: () => void
) {
super(app);
this.marker = { ...marker }; // clone so we don't mutate original until save
this.styleNames = styleNames;
this.onSave = onSave;
this.onDelete = onDelete;
this.onCloseRefresh = onCloseRefresh;
}
onOpen() {
@ -511,9 +450,9 @@ export class PolylineEditModal extends Modal {
setIcon(btn.extraSettingsEl, "trash-2");
btn.extraSettingsEl.setAttr("aria-label", "Delete polyline");
btn.extraSettingsEl.classList.add("mocblock-trash-hover");
btn.extraSettingsEl.addEventListener("click", () => {
btn.extraSettingsEl.addEventListener("click", async () => {
if (this.onDelete) {
this.onDelete(this.marker);
await this.onDelete(this.marker);
}
this.close();
});
@ -522,11 +461,13 @@ export class PolylineEditModal extends Modal {
onClose() {
this.contentEl.empty();
if (this.onCloseRefresh) this.onCloseRefresh();
}
}
export class NewMocBlockModal extends Modal {
onSubmit: (result: string) => void;
onCloseRefresh?: () => void;
constructor(app: App, onSubmit: (result: string) => void) {
super(app);
@ -571,5 +512,6 @@ export class NewMocBlockModal extends Modal {
onClose() {
this.contentEl.empty();
if (this.onCloseRefresh) this.onCloseRefresh();
}
}

View file

@ -254,7 +254,7 @@ export class MOCBlockSettingTab extends PluginSettingTab {
});
// Prevent auto focus on data input folder
setTimeout(() => {
window.setTimeout(() => {
(document.activeElement as HTMLElement)?.blur();
}, 0);

24
tsconfig.json Normal file
View file

@ -0,0 +1,24 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7"
]
},
"include": [
"**/*.ts"
]
}