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:
+58
-15
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user