feat: drag-and-drop into groups/parent notes + fix ⋯ button styling

Drag-and-drop:
- 3-zone drop indicator for groups and parent notes (files with children):
  top 25% = drop above, middle 50% = drop into, bottom 25% = drop below
- 2-zone for regular files/separators/spacers (unchanged)
- 'Drop into' shows background highlight instead of border line
- Circular reference guard: can't drop a group into itself or descendants

⋯ button:
- No visible button chrome when not hovered (no border, no background)
- Subtle opacity (0.6) at rest, full opacity on hover
- Hover shows text color change only, no background highlight
This commit is contained in:
2026-06-10 21:53:22 -04:00
parent 2e8ac7047f
commit eeb6059fe5
3 changed files with 81 additions and 30 deletions
+8 -8
View File
File diff suppressed because one or more lines are too long
+58 -15
View File
@@ -390,7 +390,7 @@ export class WaypointView extends ItemView {
});
rowEl.addEventListener('dragend', () => {
this.dragId = null;
container.querySelectorAll('.waypoint-bm-dragging, .waypoint-bm-drop-line, .waypoint-bm-drop-below').forEach(el => {
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');
@@ -399,16 +399,17 @@ export class WaypointView extends ItemView {
rowEl.addEventListener('dragenter', (e) => {
e.preventDefault();
if (!this.dragId || this.dragId === item.id) return;
this.showDropIndicator(rowEl, e);
this.showDropIndicator(rowEl, e, false);
});
rowEl.addEventListener('dragover', (e) => {
e.preventDefault();
if (!this.dragId || this.dragId === item.id) return;
this.showDropIndicator(rowEl, e);
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();
@@ -448,7 +449,7 @@ export class WaypointView extends ItemView {
});
rowEl.addEventListener('dragend', () => {
this.dragId = null;
container.querySelectorAll('.waypoint-bm-dragging, .waypoint-bm-drop-line, .waypoint-bm-drop-below').forEach(el => {
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');
@@ -457,16 +458,17 @@ export class WaypointView extends ItemView {
rowEl.addEventListener('dragenter', (e) => {
e.preventDefault();
if (!this.dragId || this.dragId === item.id) return;
this.showDropIndicator(rowEl, e);
this.showDropIndicator(rowEl, e, false);
});
rowEl.addEventListener('dragover', (e) => {
e.preventDefault();
if (!this.dragId || this.dragId === item.id) return;
this.showDropIndicator(rowEl, e);
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();
@@ -507,30 +509,34 @@ export class WaypointView extends ItemView {
rowEl.addClass('waypoint-bm-dragging');
});
const canAcceptChildren = isGroup || (item.children && item.children.length > 0);
rowEl.addEventListener('dragend', () => {
this.dragId = null;
container.querySelectorAll('.waypoint-bm-dragging, .waypoint-bm-drop-line, .waypoint-bm-drop-below').forEach(el => {
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);
this.showDropIndicator(rowEl, e, canAcceptChildren);
});
rowEl.addEventListener('dragover', (e) => {
e.preventDefault();
if (!this.dragId || this.dragId === item.id) return;
this.showDropIndicator(rowEl, e);
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) => {
@@ -538,12 +544,18 @@ export class WaypointView extends ItemView {
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) {
this.moveBookmarkToGroup(draggedId, item.id);
} else {
const dropAbove = (rowEl as any).__dropAbove;
this.moveBookmarkToPosition(draggedId, item.id, dropAbove);
}
});
// ── Group: chevron + icon + label ──
@@ -808,25 +820,47 @@ export class WaypointView extends ItemView {
menu.showAtPosition({ x: event.clientX, y: event.clientY });
}
private showDropIndicator(el: HTMLElement, e: MouseEvent): void {
// Clear indicator from all siblings first
private showDropIndicator(el: HTMLElement, e: MouseEvent, isGroupLike: boolean): void {
// Clear all indicators
const parent = el.parentElement;
if (parent) {
parent.querySelectorAll('.waypoint-bm-drop-line, .waypoint-bm-drop-below').forEach(el2 => {
parent.querySelectorAll('.waypoint-bm-drop-line, .waypoint-bm-drop-below, .waypoint-bm-drop-into').forEach(el2 => {
el2.removeClass('waypoint-bm-drop-line');
el2.removeClass('waypoint-bm-drop-below');
el2.removeClass('waypoint-bm-drop-into');
});
}
const rect = el.getBoundingClientRect();
const midY = rect.top + rect.height / 2;
const above = e.clientY < midY;
const y = e.clientY;
if (isGroupLike) {
// 3-zone: top 25% = above, middle 50% = into, bottom 25% = below
const topThreshold = rect.top + rect.height * 0.25;
const bottomThreshold = rect.top + rect.height * 0.75;
if (y < topThreshold) {
el.addClass('waypoint-bm-drop-line');
(el as any).__dropAbove = true;
(el as any).__dropInto = false;
} else if (y > bottomThreshold) {
el.addClass('waypoint-bm-drop-line');
if (!above) {
el.addClass('waypoint-bm-drop-below');
(el as any).__dropAbove = false;
(el as any).__dropInto = false;
} else {
el.addClass('waypoint-bm-drop-into');
(el as any).__dropAbove = false;
(el as any).__dropInto = true;
}
} else {
// 2-zone: top half = above, bottom half = below
const above = y < rect.top + rect.height / 2;
el.addClass('waypoint-bm-drop-line');
if (!above) el.addClass('waypoint-bm-drop-below');
(el as any).__dropAbove = above;
(el as any).__dropInto = false;
}
}
private moveBookmarkToGroup(itemId: string, targetGroupId: string | null): void {
@@ -846,6 +880,15 @@ export class WaypointView extends ItemView {
const item = removeRecursive(this.plugin.waypointData.bookmarks);
if (!item) return;
// Prevent dropping into self or own descendant
if (targetGroupId) {
const isDescendant = (parent: BookmarkItem, targetId: string): boolean => {
if (parent.id === targetId) return true;
return parent.children.some(c => isDescendant(c, targetId));
};
if (item.id === targetGroupId || isDescendant(item, targetGroupId)) return;
}
if (targetGroupId) {
const findGroup = (items: BookmarkItem[]): BookmarkItem | null => {
for (const i of items) {
+10 -2
View File
@@ -44,11 +44,14 @@
align-items: center;
justify-content: center;
transition: color 80ms, background 80ms;
box-shadow: none;
opacity: 0.6;
}
.waypoint-header-more:hover {
color: var(--text-accent);
background-color: var(--background-modifier-active-hover);
color: var(--text-normal);
background: none;
opacity: 1;
}
/* ── Calendar ── */
@@ -287,6 +290,11 @@
border-bottom: 3px solid var(--text-accent) !important;
}
.waypoint-favorites .waypoint-bm-drop-into {
background-color: var(--background-modifier-active-hover) !important;
border-radius: 4px;
}
/* ── Separator & Spacer ── */
.waypoint-favorites .waypoint-bookmark-item.waypoint-bookmark-separator {