mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 20:06:08 +01:00 
			
		
		
		
	feat(views/board): set up dragging
This commit is contained in:
		@@ -1,6 +1,7 @@
 | 
				
			|||||||
import { setupHorizontalScrollViaWheel } from "../../widget_utils";
 | 
					import { setupHorizontalScrollViaWheel } from "../../widget_utils";
 | 
				
			||||||
import ViewMode, { ViewModeArgs } from "../view_mode";
 | 
					import ViewMode, { ViewModeArgs } from "../view_mode";
 | 
				
			||||||
import { getBoardData } from "./data";
 | 
					import { getBoardData } from "./data";
 | 
				
			||||||
 | 
					import attributeService from "../../../services/attributes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TPL = /*html*/`
 | 
					const TPL = /*html*/`
 | 
				
			||||||
<div class="board-view">
 | 
					<div class="board-view">
 | 
				
			||||||
@@ -22,10 +23,24 @@ const TPL = /*html*/`
 | 
				
			|||||||
        .board-view-container .board-column {
 | 
					        .board-view-container .board-column {
 | 
				
			||||||
            width: 250px;
 | 
					            width: 250px;
 | 
				
			||||||
            flex-shrink: 0;
 | 
					            flex-shrink: 0;
 | 
				
			||||||
 | 
					            min-height: 200px;
 | 
				
			||||||
 | 
					            border: 2px solid transparent;
 | 
				
			||||||
 | 
					            border-radius: 8px;
 | 
				
			||||||
 | 
					            padding: 0.5em;
 | 
				
			||||||
 | 
					            background-color: var(--accented-background-color);
 | 
				
			||||||
 | 
					            transition: border-color 0.2s ease;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .board-view-container .board-column.drag-over {
 | 
				
			||||||
 | 
					            border-color: var(--main-text-color);
 | 
				
			||||||
 | 
					            background-color: var(--hover-item-background-color);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .board-view-container .board-column h3 {
 | 
					        .board-view-container .board-column h3 {
 | 
				
			||||||
            font-size: 1em;
 | 
					            font-size: 1em;
 | 
				
			||||||
 | 
					            margin-bottom: 0.75em;
 | 
				
			||||||
 | 
					            padding-bottom: 0.5em;
 | 
				
			||||||
 | 
					            border-bottom: 1px solid var(--main-border-color);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .board-view-container .board-note {
 | 
					        .board-view-container .board-note {
 | 
				
			||||||
@@ -33,11 +48,41 @@ const TPL = /*html*/`
 | 
				
			|||||||
            margin: 0.65em 0;
 | 
					            margin: 0.65em 0;
 | 
				
			||||||
            padding: 0.5em;
 | 
					            padding: 0.5em;
 | 
				
			||||||
            border-radius: 5px;
 | 
					            border-radius: 5px;
 | 
				
			||||||
 | 
					            cursor: move;
 | 
				
			||||||
 | 
					            position: relative;
 | 
				
			||||||
 | 
					            background-color: var(--main-background-color);
 | 
				
			||||||
 | 
					            border: 1px solid var(--main-border-color);
 | 
				
			||||||
 | 
					            transition: transform 0.2s ease, box-shadow 0.2s ease;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .board-view-container .board-note:hover {
 | 
				
			||||||
 | 
					            transform: translateY(-2px);
 | 
				
			||||||
 | 
					            box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.35);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .board-view-container .board-note.dragging {
 | 
				
			||||||
 | 
					            opacity: 0.8;
 | 
				
			||||||
 | 
					            transform: rotate(5deg);
 | 
				
			||||||
 | 
					            z-index: 1000;
 | 
				
			||||||
 | 
					            box-shadow: 4px 8px 16px rgba(0, 0, 0, 0.5);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .board-view-container .board-note .icon {
 | 
					        .board-view-container .board-note .icon {
 | 
				
			||||||
            margin-right: 0.25em;
 | 
					            margin-right: 0.25em;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .board-drop-indicator {
 | 
				
			||||||
 | 
					            height: 3px;
 | 
				
			||||||
 | 
					            background-color: var(--main-text-color);
 | 
				
			||||||
 | 
					            border-radius: 2px;
 | 
				
			||||||
 | 
					            margin: 0.25em 0;
 | 
				
			||||||
 | 
					            opacity: 0;
 | 
				
			||||||
 | 
					            transition: opacity 0.2s ease;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .board-drop-indicator.show {
 | 
				
			||||||
 | 
					            opacity: 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    </style>
 | 
					    </style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="board-view-container"></div>
 | 
					    <div class="board-view-container"></div>
 | 
				
			||||||
@@ -52,6 +97,8 @@ export default class BoardView extends ViewMode<StateInfo> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private $root: JQuery<HTMLElement>;
 | 
					    private $root: JQuery<HTMLElement>;
 | 
				
			||||||
    private $container: JQuery<HTMLElement>;
 | 
					    private $container: JQuery<HTMLElement>;
 | 
				
			||||||
 | 
					    private draggedNote: any = null;
 | 
				
			||||||
 | 
					    private draggedNoteElement: JQuery<HTMLElement> | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(args: ViewModeArgs) {
 | 
					    constructor(args: ViewModeArgs) {
 | 
				
			||||||
        super(args, "board");
 | 
					        super(args, "board");
 | 
				
			||||||
@@ -65,7 +112,7 @@ export default class BoardView extends ViewMode<StateInfo> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    async renderList(): Promise<JQuery<HTMLElement> | undefined> {
 | 
					    async renderList(): Promise<JQuery<HTMLElement> | undefined> {
 | 
				
			||||||
        this.$container.empty();
 | 
					        this.$container.empty();
 | 
				
			||||||
        this.renderBoard(this.$container[0]);
 | 
					        await this.renderBoard(this.$container[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return this.$root;
 | 
					        return this.$root;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -81,8 +128,12 @@ export default class BoardView extends ViewMode<StateInfo> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            const $columnEl = $("<div>")
 | 
					            const $columnEl = $("<div>")
 | 
				
			||||||
                .addClass("board-column")
 | 
					                .addClass("board-column")
 | 
				
			||||||
 | 
					                .attr("data-column", column)
 | 
				
			||||||
                .append($("<h3>").text(column));
 | 
					                .append($("<h3>").text(column));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Setup drop zone for the column
 | 
				
			||||||
 | 
					            this.setupColumnDropZone($columnEl, column);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for (const note of columnNotes) {
 | 
					            for (const note of columnNotes) {
 | 
				
			||||||
                const $iconEl = $("<span>")
 | 
					                const $iconEl = $("<span>")
 | 
				
			||||||
                    .addClass("icon")
 | 
					                    .addClass("icon")
 | 
				
			||||||
@@ -90,8 +141,15 @@ export default class BoardView extends ViewMode<StateInfo> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                const $noteEl = $("<div>")
 | 
					                const $noteEl = $("<div>")
 | 
				
			||||||
                    .addClass("board-note")
 | 
					                    .addClass("board-note")
 | 
				
			||||||
                    .text(note.title); // Assuming FNote has a title property
 | 
					                    .attr("data-note-id", note.noteId)
 | 
				
			||||||
 | 
					                    .attr("data-current-column", column)
 | 
				
			||||||
 | 
					                    .text(note.title);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                $noteEl.prepend($iconEl);
 | 
					                $noteEl.prepend($iconEl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Setup drag functionality for the note
 | 
				
			||||||
 | 
					                this.setupNoteDrag($noteEl, note);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                $columnEl.append($noteEl);
 | 
					                $columnEl.append($noteEl);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -99,4 +157,132 @@ export default class BoardView extends ViewMode<StateInfo> {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private setupNoteDrag($noteEl: JQuery<HTMLElement>, note: any) {
 | 
				
			||||||
 | 
					        $noteEl.attr("draggable", "true");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $noteEl.on("dragstart", (e) => {
 | 
				
			||||||
 | 
					            this.draggedNote = note;
 | 
				
			||||||
 | 
					            this.draggedNoteElement = $noteEl;
 | 
				
			||||||
 | 
					            $noteEl.addClass("dragging");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Set drag data
 | 
				
			||||||
 | 
					            const originalEvent = e.originalEvent as DragEvent;
 | 
				
			||||||
 | 
					            if (originalEvent.dataTransfer) {
 | 
				
			||||||
 | 
					                originalEvent.dataTransfer.effectAllowed = "move";
 | 
				
			||||||
 | 
					                originalEvent.dataTransfer.setData("text/plain", note.noteId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $noteEl.on("dragend", () => {
 | 
				
			||||||
 | 
					            $noteEl.removeClass("dragging");
 | 
				
			||||||
 | 
					            this.draggedNote = null;
 | 
				
			||||||
 | 
					            this.draggedNoteElement = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Remove all drop indicators
 | 
				
			||||||
 | 
					            this.$container.find(".board-drop-indicator").removeClass("show");
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private setupColumnDropZone($columnEl: JQuery<HTMLElement>, column: string) {
 | 
				
			||||||
 | 
					        $columnEl.on("dragover", (e) => {
 | 
				
			||||||
 | 
					            e.preventDefault();
 | 
				
			||||||
 | 
					            const originalEvent = e.originalEvent as DragEvent;
 | 
				
			||||||
 | 
					            if (originalEvent.dataTransfer) {
 | 
				
			||||||
 | 
					                originalEvent.dataTransfer.dropEffect = "move";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.draggedNote) {
 | 
				
			||||||
 | 
					                $columnEl.addClass("drag-over");
 | 
				
			||||||
 | 
					                this.showDropIndicator($columnEl, e);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $columnEl.on("dragleave", (e) => {
 | 
				
			||||||
 | 
					            // Only remove drag-over if we're leaving the column entirely
 | 
				
			||||||
 | 
					            const rect = $columnEl[0].getBoundingClientRect();
 | 
				
			||||||
 | 
					            const originalEvent = e.originalEvent as DragEvent;
 | 
				
			||||||
 | 
					            const x = originalEvent.clientX;
 | 
				
			||||||
 | 
					            const y = originalEvent.clientY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) {
 | 
				
			||||||
 | 
					                $columnEl.removeClass("drag-over");
 | 
				
			||||||
 | 
					                $columnEl.find(".board-drop-indicator").removeClass("show");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $columnEl.on("drop", async (e) => {
 | 
				
			||||||
 | 
					            e.preventDefault();
 | 
				
			||||||
 | 
					            $columnEl.removeClass("drag-over");
 | 
				
			||||||
 | 
					            $columnEl.find(".board-drop-indicator").removeClass("show");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.draggedNote && this.draggedNoteElement) {
 | 
				
			||||||
 | 
					                const currentColumn = this.draggedNoteElement.attr("data-current-column");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (currentColumn !== column) {
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        // Update the note's status label
 | 
				
			||||||
 | 
					                        await attributeService.setLabel(this.draggedNote.noteId, "status", column);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Move the note element to the new column
 | 
				
			||||||
 | 
					                        const dropIndicator = $columnEl.find(".board-drop-indicator.show");
 | 
				
			||||||
 | 
					                        if (dropIndicator.length > 0) {
 | 
				
			||||||
 | 
					                            dropIndicator.after(this.draggedNoteElement);
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            $columnEl.append(this.draggedNoteElement);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Update the data attribute
 | 
				
			||||||
 | 
					                        this.draggedNoteElement.attr("data-current-column", column);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Show success feedback (optional)
 | 
				
			||||||
 | 
					                        console.log(`Moved note "${this.draggedNote.title}" from "${currentColumn}" to "${column}"`);
 | 
				
			||||||
 | 
					                    } catch (error) {
 | 
				
			||||||
 | 
					                        console.error("Failed to update note status:", error);
 | 
				
			||||||
 | 
					                        // Optionally show user-facing error message
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private showDropIndicator($columnEl: JQuery<HTMLElement>, e: JQuery.DragOverEvent) {
 | 
				
			||||||
 | 
					        const originalEvent = e.originalEvent as DragEvent;
 | 
				
			||||||
 | 
					        const mouseY = originalEvent.clientY;
 | 
				
			||||||
 | 
					        const columnRect = $columnEl[0].getBoundingClientRect();
 | 
				
			||||||
 | 
					        const relativeY = mouseY - columnRect.top;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Find existing drop indicator or create one
 | 
				
			||||||
 | 
					        let $dropIndicator = $columnEl.find(".board-drop-indicator");
 | 
				
			||||||
 | 
					        if ($dropIndicator.length === 0) {
 | 
				
			||||||
 | 
					            $dropIndicator = $("<div>").addClass("board-drop-indicator");
 | 
				
			||||||
 | 
					            $columnEl.append($dropIndicator);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Find the best position to insert the note
 | 
				
			||||||
 | 
					        const $notes = this.draggedNoteElement ?
 | 
				
			||||||
 | 
					            $columnEl.find(".board-note").not(this.draggedNoteElement) :
 | 
				
			||||||
 | 
					            $columnEl.find(".board-note");
 | 
				
			||||||
 | 
					        let insertAfterElement: HTMLElement | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $notes.each((_, noteEl) => {
 | 
				
			||||||
 | 
					            const noteRect = noteEl.getBoundingClientRect();
 | 
				
			||||||
 | 
					            const noteMiddle = noteRect.top + noteRect.height / 2 - columnRect.top;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (relativeY > noteMiddle) {
 | 
				
			||||||
 | 
					                insertAfterElement = noteEl;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Position the drop indicator
 | 
				
			||||||
 | 
					        if (insertAfterElement) {
 | 
				
			||||||
 | 
					            $(insertAfterElement).after($dropIndicator);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Insert at the beginning (after the header)
 | 
				
			||||||
 | 
					            const $header = $columnEl.find("h3");
 | 
				
			||||||
 | 
					            $header.after($dropIndicator);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $dropIndicator.addClass("show");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user