mirror of
https://github.com/nebulousnessie/Obsidian-MOCBlocks.git
synced 2026-07-22 06:44:20 +00:00
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:
parent
6d0e477677
commit
01723cc8ac
7 changed files with 1184 additions and 820 deletions
1802
package-lock.json
generated
1802
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
21
src/main.ts
21
src/main.ts
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
106
src/modals.ts
106
src/modals.ts
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
24
tsconfig.json
Normal 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"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in a new issue