fix: week-number middle-click crash and 'No icon' never clearing
- Middle-clicking a calendar week number threw ReferenceError: monday is not defined (shipped in v1.5.1) — the variable was declared inside the click closure and read from the sibling mousedown handler. Hoist it to weekStart. - promptIcon guarded on if (iconName), so the picker's 'No icon' link submitting '' was silently dropped and the old icon stayed. '' is a valid value; always apply it. - renderCalendar scanned vault.getFiles() per day cell (up to 42 full vault scans per render); use the O(1) plugin.hasNoteForDate(). - Collapse ~180 lines of triplicated drag listeners into one attachBookmarkDragHandlers(); replace (el as any).__dropAbove/__dropInto stashing with a typed WeakMap; drop the getLeaf(x as any) casts. - dragManager.dragFile was called twice per dragstart; call it once behind a single narrowly-typed accessor. - Cache the Lucide catalog per session instead of refetching the CDN on every picker open; normalize IconSuggestModal to the file's const/arrow style.
This commit is contained in:
+163
-214
@@ -12,6 +12,7 @@ import {
|
|||||||
Notice,
|
Notice,
|
||||||
TFile,
|
TFile,
|
||||||
moment,
|
moment,
|
||||||
|
type PaneType,
|
||||||
} from 'obsidian';
|
} from 'obsidian';
|
||||||
import type WaypointPlugin from 'src/main';
|
import type WaypointPlugin from 'src/main';
|
||||||
import { getMonthGrid } from 'src/utils/date-utils';
|
import { getMonthGrid } from 'src/utils/date-utils';
|
||||||
@@ -19,6 +20,18 @@ import { BookmarkItem } from 'src/models/bookmark';
|
|||||||
|
|
||||||
export const WAYPOINT_VIEW_TYPE = 'waypoint-view';
|
export const WAYPOINT_VIEW_TYPE = 'waypoint-view';
|
||||||
|
|
||||||
|
/** Obsidian's internal drag manager — undocumented, so it has no public typings. */
|
||||||
|
interface DragManager {
|
||||||
|
dragFile(event: DragEvent, file: TFile): unknown;
|
||||||
|
onDragStart(event: DragEvent, draggable: unknown): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDragManager(app: App): DragManager {
|
||||||
|
// `dragManager` is an internal Obsidian API, absent from the public typings.
|
||||||
|
const internal = app as unknown as { dragManager: DragManager };
|
||||||
|
return internal.dragManager;
|
||||||
|
}
|
||||||
|
|
||||||
export class WaypointView extends ItemView {
|
export class WaypointView extends ItemView {
|
||||||
private plugin: WaypointPlugin;
|
private plugin: WaypointPlugin;
|
||||||
|
|
||||||
@@ -72,6 +85,7 @@ export class WaypointView extends ItemView {
|
|||||||
private currentDisplayMonth: number = moment().month(); // 0-indexed
|
private currentDisplayMonth: number = moment().month(); // 0-indexed
|
||||||
private currentDisplayYear: number = moment().year();
|
private currentDisplayYear: number = moment().year();
|
||||||
private dragId: string | null = null;
|
private dragId: string | null = null;
|
||||||
|
private dropZones = new WeakMap<HTMLElement, { above: boolean; into: boolean }>();
|
||||||
private recentFilesFilter: string | null = null; // null = show all, else filter by type
|
private recentFilesFilter: string | null = null; // null = show all, else filter by type
|
||||||
|
|
||||||
private renderCalendar(): void {
|
private renderCalendar(): void {
|
||||||
@@ -97,7 +111,7 @@ export class WaypointView extends ItemView {
|
|||||||
qEl.addEventListener('mousedown', (event: MouseEvent) => {
|
qEl.addEventListener('mousedown', (event: MouseEvent) => {
|
||||||
if (event.button === 1) {
|
if (event.button === 1) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.plugin.openPeriodNoteInLeaf('quarter', displayDate, this.app.workspace.getLeaf('tab'));
|
this.plugin.openPeriodNote('quarter', displayDate, this.app.workspace.getLeaf('tab'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -109,7 +123,7 @@ export class WaypointView extends ItemView {
|
|||||||
mEl.addEventListener('mousedown', (event: MouseEvent) => {
|
mEl.addEventListener('mousedown', (event: MouseEvent) => {
|
||||||
if (event.button === 1) {
|
if (event.button === 1) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.plugin.openPeriodNoteInLeaf('month', displayDate, this.app.workspace.getLeaf('tab'));
|
this.plugin.openPeriodNote('month', displayDate, this.app.workspace.getLeaf('tab'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -121,7 +135,7 @@ export class WaypointView extends ItemView {
|
|||||||
yEl.addEventListener('mousedown', (event: MouseEvent) => {
|
yEl.addEventListener('mousedown', (event: MouseEvent) => {
|
||||||
if (event.button === 1) {
|
if (event.button === 1) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.plugin.openPeriodNoteInLeaf('year', displayDate, this.app.workspace.getLeaf('tab'));
|
this.plugin.openPeriodNote('year', displayDate, this.app.workspace.getLeaf('tab'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -172,15 +186,14 @@ export class WaypointView extends ItemView {
|
|||||||
// Week number cell
|
// Week number cell
|
||||||
const wnCell = row.createEl('td', { cls: 'waypoint-weeknum' });
|
const wnCell = row.createEl('td', { cls: 'waypoint-weeknum' });
|
||||||
wnCell.setText(String(week.weekNumber));
|
wnCell.setText(String(week.weekNumber));
|
||||||
|
const weekStart = week.days[0].date;
|
||||||
wnCell.addEventListener('click', () => {
|
wnCell.addEventListener('click', () => {
|
||||||
const monday = week.days[0].date;
|
this.plugin.openPeriodNote('week', weekStart);
|
||||||
this.plugin.openPeriodNote('week', monday);
|
|
||||||
});
|
});
|
||||||
wnCell.addEventListener('mousedown', (event: MouseEvent) => {
|
wnCell.addEventListener('mousedown', (event: MouseEvent) => {
|
||||||
if (event.button === 1) {
|
if (event.button === 1) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const file = this.app.workspace.getLeaf('tab');
|
this.plugin.openPeriodNote('week', weekStart, this.app.workspace.getLeaf('tab'));
|
||||||
this.plugin.openPeriodNoteInLeaf('week', monday, file);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -197,10 +210,7 @@ export class WaypointView extends ItemView {
|
|||||||
|
|
||||||
if (this.plugin.settings.calendar.showNoteIndicators) {
|
if (this.plugin.settings.calendar.showNoteIndicators) {
|
||||||
const dateStr = day.date.format('YYYY-MM-DD');
|
const dateStr = day.date.format('YYYY-MM-DD');
|
||||||
const hasNote = this.plugin.app.vault.getFiles().some(
|
if (this.plugin.hasNoteForDate(dateStr)) {
|
||||||
f => f.extension === 'md' && f.basename === dateStr,
|
|
||||||
);
|
|
||||||
if (hasNote) {
|
|
||||||
cell.addClass('has-note');
|
cell.addClass('has-note');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,8 +221,7 @@ export class WaypointView extends ItemView {
|
|||||||
cell.addEventListener('mousedown', (event: MouseEvent) => {
|
cell.addEventListener('mousedown', (event: MouseEvent) => {
|
||||||
if (event.button === 1) {
|
if (event.button === 1) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const file = this.app.workspace.getLeaf('tab');
|
this.plugin.openPeriodNote('day', day.date, this.app.workspace.getLeaf('tab'));
|
||||||
this.plugin.openPeriodNoteInLeaf('day', day.date, file);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -284,7 +293,7 @@ export class WaypointView extends ItemView {
|
|||||||
// Type pills
|
// Type pills
|
||||||
const sortedTypes = configuredTags.length > 0
|
const sortedTypes = configuredTags.length > 0
|
||||||
? Object.entries(typeCounts) // preserve configured order
|
? Object.entries(typeCounts) // preserve configured order
|
||||||
: Object.entries(typeCounts).sort(([,a], [,b]) => b - a); // sort by count
|
: Object.entries(typeCounts).sort((a, b) => b[1] - a[1]); // sort by count
|
||||||
for (const [type, count] of sortedTypes) {
|
for (const [type, count] of sortedTypes) {
|
||||||
const pill = filterBar.createSpan({ cls: `waypoint-recent-pill${this.recentFilesFilter === type ? ' is-active' : ''}` });
|
const pill = filterBar.createSpan({ cls: `waypoint-recent-pill${this.recentFilesFilter === type ? ' is-active' : ''}` });
|
||||||
pill.setText(`${type} ${count}`);
|
pill.setText(`${type} ${count}`);
|
||||||
@@ -339,8 +348,9 @@ export class WaypointView extends ItemView {
|
|||||||
navFileTitle.addEventListener('dragstart', (event: DragEvent) => {
|
navFileTitle.addEventListener('dragstart', (event: DragEvent) => {
|
||||||
const tfile = this.app.metadataCache.getFirstLinkpathDest(file.path, '');
|
const tfile = this.app.metadataCache.getFirstLinkpathDest(file.path, '');
|
||||||
if (tfile) {
|
if (tfile) {
|
||||||
(this.app as any).dragManager.dragFile(event, tfile);
|
const dragManager = getDragManager(this.app);
|
||||||
(this.app as any).dragManager.onDragStart(event, (this.app as any).dragManager.dragFile(event, tfile));
|
const draggable = dragManager.dragFile(event, tfile);
|
||||||
|
dragManager.onDragStart(event, draggable);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -398,10 +408,10 @@ export class WaypointView extends ItemView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private focusFile(file: { path: string; basename: string }, newLeaf: boolean | string | 'split'): void {
|
private focusFile(file: { path: string; basename: string }, newLeaf: PaneType | boolean): void {
|
||||||
const targetFile = this.app.vault.getFiles().find(f => f.path === file.path);
|
const targetFile = this.app.vault.getFiles().find(f => f.path === file.path);
|
||||||
if (targetFile) {
|
if (targetFile) {
|
||||||
const leaf = this.app.workspace.getLeaf(newLeaf as any);
|
const leaf = this.app.workspace.getLeaf(newLeaf);
|
||||||
leaf.openFile(targetFile);
|
leaf.openFile(targetFile);
|
||||||
} else {
|
} else {
|
||||||
new Notice('Cannot find file');
|
new Notice('Cannot find file');
|
||||||
@@ -499,45 +509,7 @@ export class WaypointView extends ItemView {
|
|||||||
rowEl.style.cursor = 'grab';
|
rowEl.style.cursor = 'grab';
|
||||||
|
|
||||||
// Drag events
|
// Drag events
|
||||||
rowEl.addEventListener('dragstart', (e) => {
|
this.attachBookmarkDragHandlers(rowEl, container, item, false);
|
||||||
this.dragId = item.id;
|
|
||||||
e.dataTransfer!.effectAllowed = 'move';
|
|
||||||
e.dataTransfer!.setData('text/plain', item.id);
|
|
||||||
rowEl.addClass('waypoint-bm-dragging');
|
|
||||||
});
|
|
||||||
rowEl.addEventListener('dragend', () => {
|
|
||||||
this.dragId = null;
|
|
||||||
container.querySelectorAll('.waypoint-bm-dragging, .waypoint-bm-drop-line, .waypoint-bm-drop-below, .waypoint-bm-drop-into').forEach(el => {
|
|
||||||
el.removeClass('waypoint-bm-dragging');
|
|
||||||
el.removeClass('waypoint-bm-drop-line');
|
|
||||||
el.removeClass('waypoint-bm-drop-below');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
rowEl.addEventListener('dragenter', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!this.dragId || this.dragId === item.id) return;
|
|
||||||
this.showDropIndicator(rowEl, e, false);
|
|
||||||
});
|
|
||||||
rowEl.addEventListener('dragover', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!this.dragId || this.dragId === item.id) return;
|
|
||||||
this.showDropIndicator(rowEl, e, false);
|
|
||||||
});
|
|
||||||
rowEl.addEventListener('dragleave', () => {
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-line');
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-below');
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-into');
|
|
||||||
});
|
|
||||||
rowEl.addEventListener('drop', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
this.dragId = null;
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-line');
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-below');
|
|
||||||
const draggedId = e.dataTransfer?.getData('text/plain');
|
|
||||||
if (!draggedId || draggedId === item.id) return;
|
|
||||||
const dropAbove = (rowEl as any).__dropAbove;
|
|
||||||
this.moveBookmarkToPosition(draggedId, item.id, dropAbove);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Context menu
|
// Context menu
|
||||||
rowEl.addEventListener('contextmenu', (event: MouseEvent) => {
|
rowEl.addEventListener('contextmenu', (event: MouseEvent) => {
|
||||||
@@ -558,45 +530,7 @@ export class WaypointView extends ItemView {
|
|||||||
rowEl.style.cursor = 'grab';
|
rowEl.style.cursor = 'grab';
|
||||||
|
|
||||||
// Drag events
|
// Drag events
|
||||||
rowEl.addEventListener('dragstart', (e) => {
|
this.attachBookmarkDragHandlers(rowEl, container, item, false);
|
||||||
this.dragId = item.id;
|
|
||||||
e.dataTransfer!.effectAllowed = 'move';
|
|
||||||
e.dataTransfer!.setData('text/plain', item.id);
|
|
||||||
rowEl.addClass('waypoint-bm-dragging');
|
|
||||||
});
|
|
||||||
rowEl.addEventListener('dragend', () => {
|
|
||||||
this.dragId = null;
|
|
||||||
container.querySelectorAll('.waypoint-bm-dragging, .waypoint-bm-drop-line, .waypoint-bm-drop-below, .waypoint-bm-drop-into').forEach(el => {
|
|
||||||
el.removeClass('waypoint-bm-dragging');
|
|
||||||
el.removeClass('waypoint-bm-drop-line');
|
|
||||||
el.removeClass('waypoint-bm-drop-below');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
rowEl.addEventListener('dragenter', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!this.dragId || this.dragId === item.id) return;
|
|
||||||
this.showDropIndicator(rowEl, e, false);
|
|
||||||
});
|
|
||||||
rowEl.addEventListener('dragover', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!this.dragId || this.dragId === item.id) return;
|
|
||||||
this.showDropIndicator(rowEl, e, false);
|
|
||||||
});
|
|
||||||
rowEl.addEventListener('dragleave', () => {
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-line');
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-below');
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-into');
|
|
||||||
});
|
|
||||||
rowEl.addEventListener('drop', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
this.dragId = null;
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-line');
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-below');
|
|
||||||
const draggedId = e.dataTransfer?.getData('text/plain');
|
|
||||||
if (!draggedId || draggedId === item.id) return;
|
|
||||||
const dropAbove = (rowEl as any).__dropAbove;
|
|
||||||
this.moveBookmarkToPosition(draggedId, item.id, dropAbove);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Context menu
|
// Context menu
|
||||||
rowEl.addEventListener('contextmenu', (event: MouseEvent) => {
|
rowEl.addEventListener('contextmenu', (event: MouseEvent) => {
|
||||||
@@ -619,65 +553,7 @@ export class WaypointView extends ItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Drag events ──
|
// ── Drag events ──
|
||||||
rowEl.addEventListener('dragstart', (e) => {
|
this.attachBookmarkDragHandlers(rowEl, container, item, true);
|
||||||
this.dragId = item.id;
|
|
||||||
e.dataTransfer!.effectAllowed = 'move';
|
|
||||||
e.dataTransfer!.setData('text/plain', item.id);
|
|
||||||
rowEl.addClass('waypoint-bm-dragging');
|
|
||||||
});
|
|
||||||
|
|
||||||
const canAcceptChildren = true; // all file/group bookmarks can accept drops
|
|
||||||
|
|
||||||
rowEl.addEventListener('dragend', () => {
|
|
||||||
this.dragId = null;
|
|
||||||
container.querySelectorAll('.waypoint-bm-dragging, .waypoint-bm-drop-line, .waypoint-bm-drop-below, .waypoint-bm-drop-into').forEach(el => {
|
|
||||||
el.removeClass('waypoint-bm-dragging');
|
|
||||||
el.removeClass('waypoint-bm-drop-line');
|
|
||||||
el.removeClass('waypoint-bm-drop-below');
|
|
||||||
el.removeClass('waypoint-bm-drop-into');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
rowEl.addEventListener('dragenter', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!this.dragId || this.dragId === item.id) return;
|
|
||||||
this.showDropIndicator(rowEl, e, canAcceptChildren);
|
|
||||||
});
|
|
||||||
|
|
||||||
rowEl.addEventListener('dragover', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!this.dragId || this.dragId === item.id) return;
|
|
||||||
this.showDropIndicator(rowEl, e, canAcceptChildren);
|
|
||||||
});
|
|
||||||
|
|
||||||
rowEl.addEventListener('dragleave', () => {
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-line');
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-below');
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-into');
|
|
||||||
});
|
|
||||||
|
|
||||||
rowEl.addEventListener('drop', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
this.dragId = null;
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-line');
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-below');
|
|
||||||
rowEl.removeClass('waypoint-bm-drop-into');
|
|
||||||
|
|
||||||
const draggedId = e.dataTransfer?.getData('text/plain');
|
|
||||||
if (!draggedId || draggedId === item.id) return;
|
|
||||||
|
|
||||||
const dropInto = (rowEl as any).__dropInto;
|
|
||||||
if (dropInto && canAcceptChildren) {
|
|
||||||
if (isGroup) {
|
|
||||||
this.moveBookmarkToGroup(draggedId, item.id);
|
|
||||||
} else {
|
|
||||||
this.createParentNoteAndMove(draggedId, item.id);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const dropAbove = (rowEl as any).__dropAbove;
|
|
||||||
this.moveBookmarkToPosition(draggedId, item.id, dropAbove);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Group: chevron + icon + label ──
|
// ── Group: chevron + icon + label ──
|
||||||
if (isGroup) {
|
if (isGroup) {
|
||||||
@@ -699,7 +575,7 @@ export class WaypointView extends ItemView {
|
|||||||
const tfile = this.app.vault.getFileByPath(item.filePath);
|
const tfile = this.app.vault.getFileByPath(item.filePath);
|
||||||
if (tfile) {
|
if (tfile) {
|
||||||
const newLeaf = Keymap.isModEvent(event);
|
const newLeaf = Keymap.isModEvent(event);
|
||||||
this.app.workspace.getLeaf(newLeaf as any).openFile(tfile);
|
this.app.workspace.getLeaf(newLeaf).openFile(tfile);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -751,7 +627,7 @@ export class WaypointView extends ItemView {
|
|||||||
const tfile = this.app.vault.getFileByPath(item.filePath);
|
const tfile = this.app.vault.getFileByPath(item.filePath);
|
||||||
if (tfile) {
|
if (tfile) {
|
||||||
const newLeaf = Keymap.isModEvent(event);
|
const newLeaf = Keymap.isModEvent(event);
|
||||||
this.app.workspace.getLeaf(newLeaf as any).openFile(tfile);
|
this.app.workspace.getLeaf(newLeaf).openFile(tfile);
|
||||||
} else {
|
} else {
|
||||||
new Notice('File not found');
|
new Notice('File not found');
|
||||||
this.plugin.removeBookmark(item.id);
|
this.plugin.removeBookmark(item.id);
|
||||||
@@ -935,6 +811,75 @@ export class WaypointView extends ItemView {
|
|||||||
menu.showAtPosition({ x: event.clientX, y: event.clientY });
|
menu.showAtPosition({ x: event.clientX, y: event.clientY });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach the shared bookmark drag-and-drop listeners to a row element.
|
||||||
|
* `canAcceptChildren` selects 3-zone (above/into/below) drop targeting for
|
||||||
|
* file and group rows; separators and spacers use 2-zone (above/below).
|
||||||
|
*/
|
||||||
|
private attachBookmarkDragHandlers(
|
||||||
|
rowEl: HTMLElement,
|
||||||
|
container: HTMLElement,
|
||||||
|
item: BookmarkItem,
|
||||||
|
canAcceptChildren: boolean,
|
||||||
|
): void {
|
||||||
|
const clearIndicators = (): void => {
|
||||||
|
rowEl.removeClass('waypoint-bm-drop-line');
|
||||||
|
rowEl.removeClass('waypoint-bm-drop-below');
|
||||||
|
rowEl.removeClass('waypoint-bm-drop-into');
|
||||||
|
};
|
||||||
|
|
||||||
|
rowEl.addEventListener('dragstart', (e) => {
|
||||||
|
this.dragId = item.id;
|
||||||
|
e.dataTransfer!.effectAllowed = 'move';
|
||||||
|
e.dataTransfer!.setData('text/plain', item.id);
|
||||||
|
rowEl.addClass('waypoint-bm-dragging');
|
||||||
|
});
|
||||||
|
|
||||||
|
rowEl.addEventListener('dragend', () => {
|
||||||
|
this.dragId = null;
|
||||||
|
container.querySelectorAll('.waypoint-bm-dragging, .waypoint-bm-drop-line, .waypoint-bm-drop-below, .waypoint-bm-drop-into').forEach(el => {
|
||||||
|
el.removeClass('waypoint-bm-dragging');
|
||||||
|
el.removeClass('waypoint-bm-drop-line');
|
||||||
|
el.removeClass('waypoint-bm-drop-below');
|
||||||
|
el.removeClass('waypoint-bm-drop-into');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
rowEl.addEventListener('dragenter', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!this.dragId || this.dragId === item.id) return;
|
||||||
|
this.showDropIndicator(rowEl, e, canAcceptChildren);
|
||||||
|
});
|
||||||
|
|
||||||
|
rowEl.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!this.dragId || this.dragId === item.id) return;
|
||||||
|
this.showDropIndicator(rowEl, e, canAcceptChildren);
|
||||||
|
});
|
||||||
|
|
||||||
|
rowEl.addEventListener('dragleave', clearIndicators);
|
||||||
|
|
||||||
|
rowEl.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.dragId = null;
|
||||||
|
clearIndicators();
|
||||||
|
|
||||||
|
const draggedId = e.dataTransfer?.getData('text/plain');
|
||||||
|
if (!draggedId || draggedId === item.id) return;
|
||||||
|
|
||||||
|
const zone = this.dropZones.get(rowEl);
|
||||||
|
if (canAcceptChildren && zone?.into) {
|
||||||
|
if (item.type === 'group') {
|
||||||
|
this.moveBookmarkToGroup(draggedId, item.id);
|
||||||
|
} else {
|
||||||
|
this.createParentNoteAndMove(draggedId, item.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.moveBookmarkToPosition(draggedId, item.id, zone?.above ?? false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private showDropIndicator(el: HTMLElement, e: MouseEvent, isGroupLike: boolean): void {
|
private showDropIndicator(el: HTMLElement, e: MouseEvent, isGroupLike: boolean): void {
|
||||||
// Clear all indicators
|
// Clear all indicators
|
||||||
const parent = el.parentElement;
|
const parent = el.parentElement;
|
||||||
@@ -956,25 +901,21 @@ export class WaypointView extends ItemView {
|
|||||||
|
|
||||||
if (y < topThreshold) {
|
if (y < topThreshold) {
|
||||||
el.addClass('waypoint-bm-drop-line');
|
el.addClass('waypoint-bm-drop-line');
|
||||||
(el as any).__dropAbove = true;
|
this.dropZones.set(el, { above: true, into: false });
|
||||||
(el as any).__dropInto = false;
|
|
||||||
} else if (y > bottomThreshold) {
|
} else if (y > bottomThreshold) {
|
||||||
el.addClass('waypoint-bm-drop-line');
|
el.addClass('waypoint-bm-drop-line');
|
||||||
el.addClass('waypoint-bm-drop-below');
|
el.addClass('waypoint-bm-drop-below');
|
||||||
(el as any).__dropAbove = false;
|
this.dropZones.set(el, { above: false, into: false });
|
||||||
(el as any).__dropInto = false;
|
|
||||||
} else {
|
} else {
|
||||||
el.addClass('waypoint-bm-drop-into');
|
el.addClass('waypoint-bm-drop-into');
|
||||||
(el as any).__dropAbove = false;
|
this.dropZones.set(el, { above: false, into: true });
|
||||||
(el as any).__dropInto = true;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 2-zone: top half = above, bottom half = below
|
// 2-zone: top half = above, bottom half = below
|
||||||
const above = y < rect.top + rect.height / 2;
|
const above = y < rect.top + rect.height / 2;
|
||||||
el.addClass('waypoint-bm-drop-line');
|
el.addClass('waypoint-bm-drop-line');
|
||||||
if (!above) el.addClass('waypoint-bm-drop-below');
|
if (!above) el.addClass('waypoint-bm-drop-below');
|
||||||
(el as any).__dropAbove = above;
|
this.dropZones.set(el, { above, into: false });
|
||||||
(el as any).__dropInto = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1130,9 +1071,8 @@ export class WaypointView extends ItemView {
|
|||||||
|
|
||||||
private promptIcon(item: BookmarkItem): void {
|
private promptIcon(item: BookmarkItem): void {
|
||||||
new IconSuggestModal(this.app, item.icon, (iconName) => {
|
new IconSuggestModal(this.app, item.icon, (iconName) => {
|
||||||
if (iconName) {
|
// An empty string is a valid value meaning "no icon", so always apply.
|
||||||
this.plugin.updateBookmark(item.id, { icon: iconName });
|
this.plugin.updateBookmark(item.id, { icon: iconName });
|
||||||
}
|
|
||||||
}).open();
|
}).open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1188,6 +1128,31 @@ class RenameModal extends Modal {
|
|||||||
|
|
||||||
// ── Icon picker modal (full Lucide icon set, grid layout) ──
|
// ── Icon picker modal (full Lucide icon set, grid layout) ──
|
||||||
|
|
||||||
|
// Lucide icon metadata, fetched at most once per session and shared by every
|
||||||
|
// picker. Concurrent opens await the same in-flight promise; a failed fetch is
|
||||||
|
// not cached, so a later open retries once the network is back.
|
||||||
|
let iconCatalog: Promise<Record<string, string[]>> | null = null;
|
||||||
|
|
||||||
|
function loadIconCatalog(): Promise<Record<string, string[]>> {
|
||||||
|
if (!iconCatalog) {
|
||||||
|
iconCatalog = (async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('https://cdn.jsdelivr.net/npm/lucide-static@0.517.0/tags.json');
|
||||||
|
return (await res.json()) as Record<string, string[]>;
|
||||||
|
} catch {
|
||||||
|
try {
|
||||||
|
const res = await fetch('https://lucide.dev/api/tags');
|
||||||
|
return (await res.json()) as Record<string, string[]>;
|
||||||
|
} catch {
|
||||||
|
iconCatalog = null;
|
||||||
|
return FALLBACK_ICONS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
return iconCatalog;
|
||||||
|
}
|
||||||
|
|
||||||
class IconSuggestModal extends Modal {
|
class IconSuggestModal extends Modal {
|
||||||
private onSubmit: (icon: string) => void;
|
private onSubmit: (icon: string) => void;
|
||||||
private selected: string;
|
private selected: string;
|
||||||
@@ -1280,7 +1245,7 @@ class IconSuggestModal extends Modal {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── Render grid ──
|
// ── Render grid ──
|
||||||
let debounce: any = null;
|
let debounce: number | undefined;
|
||||||
|
|
||||||
const renderGrid = (query: string) => {
|
const renderGrid = (query: string) => {
|
||||||
grid.empty();
|
grid.empty();
|
||||||
@@ -1306,9 +1271,8 @@ class IconSuggestModal extends Modal {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < matches.length; i++) {
|
for (const name of matches) {
|
||||||
var name = matches[i];
|
const tile = grid.createDiv();
|
||||||
var tile = grid.createDiv();
|
|
||||||
tile.setAttr('data-icon', name);
|
tile.setAttr('data-icon', name);
|
||||||
tile.style.display = 'flex';
|
tile.style.display = 'flex';
|
||||||
tile.style.alignItems = 'center';
|
tile.style.alignItems = 'center';
|
||||||
@@ -1326,27 +1290,26 @@ class IconSuggestModal extends Modal {
|
|||||||
tile.style.color = 'var(--text-muted)';
|
tile.style.color = 'var(--text-muted)';
|
||||||
}
|
}
|
||||||
|
|
||||||
var svg = tile.createSpan();
|
const svg = tile.createSpan();
|
||||||
svg.style.display = 'flex';
|
svg.style.display = 'flex';
|
||||||
setIcon(svg, name);
|
setIcon(svg, name);
|
||||||
|
|
||||||
;(function(_self, _tile, _name, _query, _grid, _previewIcon, _previewLabel) {
|
tile.addEventListener('mouseenter', () => {
|
||||||
_tile.addEventListener('mouseenter', function() {
|
if (name !== this.selected) tile.style.background = 'var(--background-modifier-hover)';
|
||||||
if (_name !== _self.selected) _tile.style.background = 'var(--background-modifier-hover)';
|
|
||||||
});
|
});
|
||||||
_tile.addEventListener('mouseleave', function() {
|
tile.addEventListener('mouseleave', () => {
|
||||||
if (_name !== _self.selected) _tile.style.background = '';
|
if (name !== this.selected) tile.style.background = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
_tile.addEventListener('click', function() {
|
tile.addEventListener('click', () => {
|
||||||
_self.selected = _name;
|
this.selected = name;
|
||||||
renderGrid(_query);
|
renderGrid(query);
|
||||||
_previewIcon.empty();
|
previewIcon.empty();
|
||||||
setIcon(_previewIcon, _name);
|
setIcon(previewIcon, name);
|
||||||
_previewLabel.setText(_name);
|
previewLabel.setText(name);
|
||||||
_grid.querySelectorAll('div[data-icon]').forEach(function(_el) {
|
grid.querySelectorAll('div[data-icon]').forEach((node) => {
|
||||||
var el = _el as HTMLElement;
|
const el = node as HTMLElement;
|
||||||
if (el.getAttr('data-icon') === _name) {
|
if (el.getAttr('data-icon') === name) {
|
||||||
el.style.background = 'var(--interactive-accent)';
|
el.style.background = 'var(--interactive-accent)';
|
||||||
el.style.color = 'var(--text-on-accent)';
|
el.style.color = 'var(--text-on-accent)';
|
||||||
} else {
|
} else {
|
||||||
@@ -1355,31 +1318,30 @@ class IconSuggestModal extends Modal {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})(this, tile, name, query, grid, previewIcon, previewLabel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
statusEl.setText(matches.length + ' of ' + this.allIcons.length + ' icons');
|
statusEl.setText(matches.length + ' of ' + this.allIcons.length + ' icons');
|
||||||
};
|
};
|
||||||
|
|
||||||
input.addEventListener('input', function() {
|
input.addEventListener('input', () => {
|
||||||
if (debounce !== null) clearTimeout(debounce);
|
window.clearTimeout(debounce);
|
||||||
debounce = setTimeout(function() { renderGrid(input.value); }, 60);
|
debounce = window.setTimeout(() => renderGrid(input.value), 60);
|
||||||
});
|
});
|
||||||
|
|
||||||
input.addEventListener('keydown', function(e: KeyboardEvent) {
|
input.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||||
if (e.key === 'Escape') this.close();
|
if (e.key === 'Escape') this.close();
|
||||||
}.bind(this));
|
});
|
||||||
|
|
||||||
// ── Buttons ──
|
// ── Buttons ──
|
||||||
var btns = modal.createDiv({ cls: 'modal-button-container' });
|
const btns = modal.createDiv({ cls: 'modal-button-container' });
|
||||||
var cancel = btns.createEl('button', { text: 'Cancel' });
|
const cancel = btns.createEl('button', { text: 'Cancel' });
|
||||||
cancel.addEventListener('click', function() { this.close(); }.bind(this));
|
cancel.addEventListener('click', () => this.close());
|
||||||
var saveBtn = btns.createEl('button', { text: 'Save', cls: 'mod-cta' });
|
const saveBtn = btns.createEl('button', { text: 'Save', cls: 'mod-cta' });
|
||||||
saveBtn.style.marginLeft = '8px';
|
saveBtn.style.marginLeft = '8px';
|
||||||
saveBtn.addEventListener('click', function() {
|
saveBtn.addEventListener('click', () => {
|
||||||
this.onSubmit(this.selected);
|
this.onSubmit(this.selected);
|
||||||
this.close();
|
this.close();
|
||||||
}.bind(this));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose(): void {
|
onClose(): void {
|
||||||
@@ -1387,22 +1349,9 @@ class IconSuggestModal extends Modal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async loadIcons(): Promise<void> {
|
private async loadIcons(): Promise<void> {
|
||||||
try {
|
const catalog = await loadIconCatalog();
|
||||||
var r = await fetch('https://cdn.jsdelivr.net/npm/lucide-static@0.517.0/tags.json');
|
this.tagsMap = catalog;
|
||||||
var d = await r.json();
|
this.allIcons = Object.keys(catalog).sort();
|
||||||
this.tagsMap = d as Record<string, string[]>;
|
|
||||||
this.allIcons = Object.keys(d).sort();
|
|
||||||
} catch (_e) {
|
|
||||||
try {
|
|
||||||
var r2 = await fetch('https://lucide.dev/api/tags');
|
|
||||||
var d2 = await r2.json();
|
|
||||||
this.tagsMap = d2 as Record<string, string[]>;
|
|
||||||
this.allIcons = Object.keys(d2).sort();
|
|
||||||
} catch (_e2) {
|
|
||||||
this.tagsMap = {};
|
|
||||||
this.allIcons = Object.keys(FALLBACK_ICONS).sort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user