mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	feat(views/board): basic column drag support
This commit is contained in:
		
							
								
								
									
										2
									
								
								.github/instructions/nx.instructions.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/instructions/nx.instructions.md
									
									
									
									
										vendored
									
									
								
							| @@ -4,7 +4,7 @@ applyTo: '**' | |||||||
|  |  | ||||||
| // This file is automatically generated by Nx Console | // This file is automatically generated by Nx Console | ||||||
|  |  | ||||||
| You are in an nx workspace using Nx 21.3.1 and pnpm as the package manager. | You are in an nx workspace using Nx 21.3.2 and pnpm as the package manager. | ||||||
|  |  | ||||||
| You have access to the Nx MCP server and the tools it provides. Use them. Follow these guidelines in order to best help the user: | You have access to the Nx MCP server and the tools it provides. Use them. Follow these guidelines in order to best help the user: | ||||||
|  |  | ||||||
|   | |||||||
| @@ -110,12 +110,45 @@ export default class BoardApi { | |||||||
|         return columnValue; |         return columnValue; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     async reorderColumns(newColumnOrder: string[]) { | ||||||
|  |         console.log("API: Reordering columns to:", newColumnOrder); | ||||||
|  |          | ||||||
|  |         // Update the column order in persisted data | ||||||
|  |         if (!this.persistedData.columns) { | ||||||
|  |             this.persistedData.columns = []; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Create a map of existing column data | ||||||
|  |         const columnDataMap = new Map(); | ||||||
|  |         this.persistedData.columns.forEach(col => { | ||||||
|  |             columnDataMap.set(col.value, col); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // Reorder columns based on new order | ||||||
|  |         this.persistedData.columns = newColumnOrder.map(columnValue => { | ||||||
|  |             return columnDataMap.get(columnValue) || { value: columnValue }; | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // Update internal columns array | ||||||
|  |         this._columns = newColumnOrder; | ||||||
|  |  | ||||||
|  |         console.log("API: Updated internal columns to:", this._columns); | ||||||
|  |         console.log("API: Updated persisted data:", this.persistedData.columns); | ||||||
|  |  | ||||||
|  |         await this.viewStorage.store(this.persistedData); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     static async build(parentNote: FNote, viewStorage: ViewModeStorage<BoardData>) { |     static async build(parentNote: FNote, viewStorage: ViewModeStorage<BoardData>) { | ||||||
|         const statusAttribute = parentNote.getLabelValue("board:groupBy") ?? "status"; |         const statusAttribute = parentNote.getLabelValue("board:groupBy") ?? "status"; | ||||||
|  |  | ||||||
|         let persistedData = await viewStorage.restore() ?? {}; |         let persistedData = await viewStorage.restore() ?? {}; | ||||||
|         const { byColumn, newPersistedData } = await getBoardData(parentNote, statusAttribute, persistedData); |         const { byColumn, newPersistedData } = await getBoardData(parentNote, statusAttribute, persistedData); | ||||||
|         const columns = Array.from(byColumn.keys()) || []; |          | ||||||
|  |         // Use the order from persistedData.columns, then add any new columns found | ||||||
|  |         const orderedColumns = persistedData.columns?.map(col => col.value) || []; | ||||||
|  |         const allColumns = Array.from(byColumn.keys()); | ||||||
|  |         const newColumns = allColumns.filter(col => !orderedColumns.includes(col)); | ||||||
|  |         const columns = [...orderedColumns, ...newColumns]; | ||||||
|  |  | ||||||
|         if (newPersistedData) { |         if (newPersistedData) { | ||||||
|             persistedData = newPersistedData; |             persistedData = newPersistedData; | ||||||
|   | |||||||
| @@ -241,15 +241,33 @@ export class DifferentialBoardRenderer { | |||||||
|             .addClass("board-column") |             .addClass("board-column") | ||||||
|             .attr("data-column", column); |             .attr("data-column", column); | ||||||
|  |  | ||||||
|         // Create header |         // Create header with drag handle | ||||||
|         const $titleEl = $("<h3>").attr("data-column-value", column); |         const $titleEl = $("<h3>").attr("data-column-value", column); | ||||||
|  |          | ||||||
|  |         // Create drag handle | ||||||
|  |         const $dragHandle = $("<span>") | ||||||
|  |             .addClass("column-drag-handle icon bx bx-menu") | ||||||
|  |             .attr("title", "Drag to reorder column"); | ||||||
|  |  | ||||||
|  |         // Create title text | ||||||
|         const $titleText = $("<span>").text(column); |         const $titleText = $("<span>").text(column); | ||||||
|  |          | ||||||
|  |         // Create title content container | ||||||
|  |         const $titleContent = $("<div>") | ||||||
|  |             .addClass("column-title-content") | ||||||
|  |             .append($dragHandle, $titleText); | ||||||
|  |  | ||||||
|  |         // Create edit icon | ||||||
|         const $editIcon = $("<span>") |         const $editIcon = $("<span>") | ||||||
|             .addClass("edit-icon icon bx bx-edit-alt") |             .addClass("edit-icon icon bx bx-edit-alt") | ||||||
|             .attr("title", "Click to edit column title"); |             .attr("title", "Click to edit column title"); | ||||||
|         $titleEl.append($titleText, $editIcon); |  | ||||||
|  |         $titleEl.append($titleContent, $editIcon); | ||||||
|         $columnEl.append($titleEl); |         $columnEl.append($titleEl); | ||||||
|  |  | ||||||
|  |         // Setup column dragging | ||||||
|  |         this.dragHandler.setupColumnDrag($columnEl, column); | ||||||
|  |  | ||||||
|         // Handle wheel events for scrolling |         // Handle wheel events for scrolling | ||||||
|         $columnEl.on("wheel", (event) => { |         $columnEl.on("wheel", (event) => { | ||||||
|             const el = $columnEl[0]; |             const el = $columnEl[0]; | ||||||
| @@ -259,7 +277,8 @@ export class DifferentialBoardRenderer { | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         // Setup drop zone |         // Setup drop zones for both notes and columns | ||||||
|  |         this.dragHandler.setupNoteDropZone($columnEl, column); | ||||||
|         this.dragHandler.setupColumnDropZone($columnEl, column); |         this.dragHandler.setupColumnDropZone($columnEl, column); | ||||||
|  |  | ||||||
|         // Add cards |         // Add cards | ||||||
|   | |||||||
| @@ -5,6 +5,8 @@ export interface DragContext { | |||||||
|     draggedNote: any; |     draggedNote: any; | ||||||
|     draggedBranch: any; |     draggedBranch: any; | ||||||
|     draggedNoteElement: JQuery<HTMLElement> | null; |     draggedNoteElement: JQuery<HTMLElement> | null; | ||||||
|  |     draggedColumn: string | null; | ||||||
|  |     draggedColumnElement: JQuery<HTMLElement> | null; | ||||||
| } | } | ||||||
|  |  | ||||||
| export class BoardDragHandler { | export class BoardDragHandler { | ||||||
| @@ -35,6 +37,54 @@ export class BoardDragHandler { | |||||||
|         this.setupTouchDrag($noteEl, note, branch); |         this.setupTouchDrag($noteEl, note, branch); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     setupColumnDrag($columnEl: JQuery<HTMLElement>, columnValue: string) { | ||||||
|  |         const $dragHandle = $columnEl.find('.column-drag-handle'); | ||||||
|  |          | ||||||
|  |         $dragHandle.attr("draggable", "true"); | ||||||
|  |  | ||||||
|  |         $dragHandle.on("dragstart", (e) => { | ||||||
|  |             this.context.draggedColumn = columnValue; | ||||||
|  |             this.context.draggedColumnElement = $columnEl; | ||||||
|  |             $columnEl.addClass("column-dragging"); | ||||||
|  |  | ||||||
|  |             const originalEvent = e.originalEvent as DragEvent; | ||||||
|  |             if (originalEvent.dataTransfer) { | ||||||
|  |                 originalEvent.dataTransfer.effectAllowed = "move"; | ||||||
|  |                 originalEvent.dataTransfer.setData("text/plain", columnValue); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Prevent note dragging when column is being dragged | ||||||
|  |             e.stopPropagation(); | ||||||
|  |  | ||||||
|  |             // Setup global drag tracking for better drop indicator positioning | ||||||
|  |             this.setupGlobalColumnDragTracking(); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         $dragHandle.on("dragend", () => { | ||||||
|  |             $columnEl.removeClass("column-dragging"); | ||||||
|  |             this.$container.find('.board-column').removeClass('column-drag-over'); | ||||||
|  |             this.context.draggedColumn = null; | ||||||
|  |             this.context.draggedColumnElement = null; | ||||||
|  |             this.cleanupColumnDropIndicators(); | ||||||
|  |             this.cleanupGlobalColumnDragTracking(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private setupGlobalColumnDragTracking() { | ||||||
|  |         // Add container-level drag tracking for better indicator positioning | ||||||
|  |         this.$container.on("dragover.columnDrag", (e) => { | ||||||
|  |             if (this.context.draggedColumn) { | ||||||
|  |                 e.preventDefault(); | ||||||
|  |                 const originalEvent = e.originalEvent as DragEvent; | ||||||
|  |                 this.showColumnDropIndicator(originalEvent.clientX); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private cleanupGlobalColumnDragTracking() { | ||||||
|  |         this.$container.off("dragover.columnDrag"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     updateApi(newApi: BoardApi) { |     updateApi(newApi: BoardApi) { | ||||||
|         this.api = newApi; |         this.api = newApi; | ||||||
|     } |     } | ||||||
| @@ -42,10 +92,16 @@ export class BoardDragHandler { | |||||||
|     private cleanupAllDropIndicators() { |     private cleanupAllDropIndicators() { | ||||||
|         // Remove all drop indicators from the DOM to prevent layout issues |         // Remove all drop indicators from the DOM to prevent layout issues | ||||||
|         this.$container.find(".board-drop-indicator").remove(); |         this.$container.find(".board-drop-indicator").remove(); | ||||||
|  |         this.$container.find(".column-drop-indicator").remove(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private cleanupColumnDropIndicators($columnEl: JQuery<HTMLElement>) { |     private cleanupColumnDropIndicators() { | ||||||
|         // Remove drop indicators from a specific column |         // Remove column drop indicators | ||||||
|  |         this.$container.find(".column-drop-indicator").remove(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private cleanupNoteDropIndicators($columnEl: JQuery<HTMLElement>) { | ||||||
|  |         // Remove note drop indicators from a specific column | ||||||
|         $columnEl.find(".board-drop-indicator").remove(); |         $columnEl.find(".board-drop-indicator").remove(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -53,6 +109,10 @@ export class BoardDragHandler { | |||||||
|     cleanup() { |     cleanup() { | ||||||
|         this.cleanupAllDropIndicators(); |         this.cleanupAllDropIndicators(); | ||||||
|         this.$container.find('.board-column').removeClass('drag-over'); |         this.$container.find('.board-column').removeClass('drag-over'); | ||||||
|  |         this.$container.find('.board-column').removeClass('column-drag-over'); | ||||||
|  |         this.context.draggedColumn = null; | ||||||
|  |         this.context.draggedColumnElement = null; | ||||||
|  |         this.cleanupGlobalColumnDragTracking(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private setupMouseDrag($noteEl: JQuery<HTMLElement>, note: any, branch: any) { |     private setupMouseDrag($noteEl: JQuery<HTMLElement>, note: any, branch: any) { | ||||||
| @@ -175,15 +235,16 @@ export class BoardDragHandler { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     setupColumnDropZone($columnEl: JQuery<HTMLElement>, column: string) { |     setupNoteDropZone($columnEl: JQuery<HTMLElement>, column: string) { | ||||||
|         $columnEl.on("dragover", (e) => { |         $columnEl.on("dragover", (e) => { | ||||||
|  |             // Only handle note drops when a note is being dragged | ||||||
|  |             if (this.context.draggedNote && !this.context.draggedColumn) { | ||||||
|                 e.preventDefault(); |                 e.preventDefault(); | ||||||
|                 const originalEvent = e.originalEvent as DragEvent; |                 const originalEvent = e.originalEvent as DragEvent; | ||||||
|                 if (originalEvent.dataTransfer) { |                 if (originalEvent.dataTransfer) { | ||||||
|                     originalEvent.dataTransfer.dropEffect = "move"; |                     originalEvent.dataTransfer.dropEffect = "move"; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|             if (this.context.draggedNote) { |  | ||||||
|                 $columnEl.addClass("drag-over"); |                 $columnEl.addClass("drag-over"); | ||||||
|                 this.showDropIndicator($columnEl, e); |                 this.showDropIndicator($columnEl, e); | ||||||
|             } |             } | ||||||
| @@ -198,17 +259,60 @@ export class BoardDragHandler { | |||||||
|  |  | ||||||
|             if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) { |             if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) { | ||||||
|                 $columnEl.removeClass("drag-over"); |                 $columnEl.removeClass("drag-over"); | ||||||
|                 this.cleanupColumnDropIndicators($columnEl); |                 this.cleanupNoteDropIndicators($columnEl); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         $columnEl.on("drop", async (e) => { |         $columnEl.on("drop", async (e) => { | ||||||
|  |             if (this.context.draggedNote && !this.context.draggedColumn) { | ||||||
|                 e.preventDefault(); |                 e.preventDefault(); | ||||||
|                 $columnEl.removeClass("drag-over"); |                 $columnEl.removeClass("drag-over"); | ||||||
|  |  | ||||||
|                 if (this.context.draggedNote && this.context.draggedNoteElement && this.context.draggedBranch) { |                 if (this.context.draggedNote && this.context.draggedNoteElement && this.context.draggedBranch) { | ||||||
|                     await this.handleNoteDrop($columnEl, column); |                     await this.handleNoteDrop($columnEl, column); | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     setupColumnDropZone($columnEl: JQuery<HTMLElement>, columnValue: string) { | ||||||
|  |         $columnEl.on("dragover", (e) => { | ||||||
|  |             // Only handle column drops when a column is being dragged | ||||||
|  |             if (this.context.draggedColumn && !this.context.draggedNote) { | ||||||
|  |                 e.preventDefault(); | ||||||
|  |                 const originalEvent = e.originalEvent as DragEvent; | ||||||
|  |                 if (originalEvent.dataTransfer) { | ||||||
|  |                     originalEvent.dataTransfer.dropEffect = "move"; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (this.context.draggedColumn !== columnValue) { | ||||||
|  |                     $columnEl.addClass("column-drag-over"); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         $columnEl.on("dragleave", (e) => { | ||||||
|  |             if (this.context.draggedColumn && !this.context.draggedNote) { | ||||||
|  |                 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("column-drag-over"); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         $columnEl.on("drop", async (e) => { | ||||||
|  |             if (this.context.draggedColumn && !this.context.draggedNote) { | ||||||
|  |                 e.preventDefault(); | ||||||
|  |                 $columnEl.removeClass("column-drag-over"); | ||||||
|  |  | ||||||
|  |                 if (this.context.draggedColumn !== columnValue) { | ||||||
|  |                     await this.handleColumnDrop($columnEl, columnValue); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -245,7 +349,7 @@ export class BoardDragHandler { | |||||||
|         const relativeY = y - columnRect.top; |         const relativeY = y - columnRect.top; | ||||||
|  |  | ||||||
|         // Clean up any existing drop indicators in this column first |         // Clean up any existing drop indicators in this column first | ||||||
|         this.cleanupColumnDropIndicators($columnEl); |         this.cleanupNoteDropIndicators($columnEl); | ||||||
|  |  | ||||||
|         // Create a new drop indicator |         // Create a new drop indicator | ||||||
|         const $dropIndicator = $("<div>").addClass("board-drop-indicator"); |         const $dropIndicator = $("<div>").addClass("board-drop-indicator"); | ||||||
| @@ -277,6 +381,63 @@ export class BoardDragHandler { | |||||||
|         $dropIndicator.addClass("show"); |         $dropIndicator.addClass("show"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private showColumnDropIndicator(mouseX: number) { | ||||||
|  |         // Clean up existing indicators | ||||||
|  |         this.cleanupColumnDropIndicators(); | ||||||
|  |  | ||||||
|  |         // Get all columns (excluding the dragged one if it exists) | ||||||
|  |         let $allColumns = this.$container.find('.board-column'); | ||||||
|  |         if (this.context.draggedColumnElement) { | ||||||
|  |             $allColumns = $allColumns.not(this.context.draggedColumnElement); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         let $targetColumn: JQuery<HTMLElement> = $(); | ||||||
|  |         let insertBefore = false; | ||||||
|  |  | ||||||
|  |         // Find which column the mouse is closest to | ||||||
|  |         $allColumns.each((_, columnEl) => { | ||||||
|  |             const $column = $(columnEl); | ||||||
|  |             const rect = columnEl.getBoundingClientRect(); | ||||||
|  |             const columnMiddle = rect.left + rect.width / 2; | ||||||
|  |  | ||||||
|  |             if (mouseX >= rect.left && mouseX <= rect.right) { | ||||||
|  |                 // Mouse is over this column | ||||||
|  |                 $targetColumn = $column; | ||||||
|  |                 insertBefore = mouseX < columnMiddle; | ||||||
|  |                 return false; // Break the loop | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // If no column found under mouse, find the closest one | ||||||
|  |         if ($targetColumn.length === 0) { | ||||||
|  |             let closestDistance = Infinity; | ||||||
|  |             $allColumns.each((_, columnEl) => { | ||||||
|  |                 const $column = $(columnEl); | ||||||
|  |                 const rect = columnEl.getBoundingClientRect(); | ||||||
|  |                 const columnCenter = rect.left + rect.width / 2; | ||||||
|  |                 const distance = Math.abs(mouseX - columnCenter); | ||||||
|  |  | ||||||
|  |                 if (distance < closestDistance) { | ||||||
|  |                     closestDistance = distance; | ||||||
|  |                     $targetColumn = $column; | ||||||
|  |                     insertBefore = mouseX < columnCenter; | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if ($targetColumn.length > 0) { | ||||||
|  |             const $dropIndicator = $("<div>").addClass("column-drop-indicator"); | ||||||
|  |              | ||||||
|  |             if (insertBefore) { | ||||||
|  |                 $targetColumn.before($dropIndicator); | ||||||
|  |             } else { | ||||||
|  |                 $targetColumn.after($dropIndicator); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             $dropIndicator.addClass("show"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private async handleNoteDrop($columnEl: JQuery<HTMLElement>, column: string) { |     private async handleNoteDrop($columnEl: JQuery<HTMLElement>, column: string) { | ||||||
|         const draggedNoteElement = this.context.draggedNoteElement; |         const draggedNoteElement = this.context.draggedNoteElement; | ||||||
|         const draggedNote = this.context.draggedNote; |         const draggedNote = this.context.draggedNote; | ||||||
| @@ -337,4 +498,74 @@ export class BoardDragHandler { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private async handleColumnDrop($targetColumnEl: JQuery<HTMLElement>, targetColumnValue: string) { | ||||||
|  |         if (!this.context.draggedColumn || !this.context.draggedColumnElement) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             // Get current column order from the DOM | ||||||
|  |             const currentOrder = Array.from(this.$container.find('.board-column')).map(el =>  | ||||||
|  |                 $(el).attr('data-column') | ||||||
|  |             ).filter(col => col) as string[]; | ||||||
|  |  | ||||||
|  |             console.log("Current order:", currentOrder); | ||||||
|  |             console.log("Dragged column:", this.context.draggedColumn); | ||||||
|  |             console.log("Target column:", targetColumnValue); | ||||||
|  |  | ||||||
|  |             // Find the drop indicator to determine insert position | ||||||
|  |             const $dropIndicator = this.$container.find(".column-drop-indicator.show"); | ||||||
|  |              | ||||||
|  |             if ($dropIndicator.length > 0) { | ||||||
|  |                 let newOrder = [...currentOrder]; | ||||||
|  |                  | ||||||
|  |                 // Remove dragged column from current position | ||||||
|  |                 newOrder = newOrder.filter(col => col !== this.context.draggedColumn); | ||||||
|  |  | ||||||
|  |                 // Determine insertion position based on drop indicator | ||||||
|  |                 const $nextColumn = $dropIndicator.next('.board-column'); | ||||||
|  |                 const $prevColumn = $dropIndicator.prev('.board-column'); | ||||||
|  |                  | ||||||
|  |                 let insertIndex = -1; | ||||||
|  |                  | ||||||
|  |                 if ($nextColumn.length > 0) { | ||||||
|  |                     // Insert before the next column | ||||||
|  |                     const nextColumnValue = $nextColumn.attr('data-column'); | ||||||
|  |                     insertIndex = newOrder.indexOf(nextColumnValue!); | ||||||
|  |                 } else if ($prevColumn.length > 0) { | ||||||
|  |                     // Insert after the previous column | ||||||
|  |                     const prevColumnValue = $prevColumn.attr('data-column'); | ||||||
|  |                     insertIndex = newOrder.indexOf(prevColumnValue!) + 1; | ||||||
|  |                 } else { | ||||||
|  |                     // Insert at the beginning | ||||||
|  |                     insertIndex = 0; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Insert the dragged column at the determined position | ||||||
|  |                 if (insertIndex >= 0) { | ||||||
|  |                     newOrder.splice(insertIndex, 0, this.context.draggedColumn); | ||||||
|  |                 } else { | ||||||
|  |                     // Fallback: insert at the end | ||||||
|  |                     newOrder.push(this.context.draggedColumn); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 console.log("New order:", newOrder); | ||||||
|  |  | ||||||
|  |                 // Update column order in API | ||||||
|  |                 await this.api.reorderColumns(newOrder); | ||||||
|  |  | ||||||
|  |                 console.log(`Moved column "${this.context.draggedColumn}" to new position`); | ||||||
|  |  | ||||||
|  |                 // Refresh the board to reflect the changes | ||||||
|  |                 await this.onBoardRefresh(); | ||||||
|  |             } else { | ||||||
|  |                 console.warn("No drop indicator found for column drop"); | ||||||
|  |             } | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error("Failed to reorder columns:", error); | ||||||
|  |         } finally { | ||||||
|  |             this.cleanupColumnDropIndicators(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -70,6 +70,47 @@ const TPL = /*html*/` | |||||||
|             border-radius: 4px; |             border-radius: 4px; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         .board-view-container .board-column h3 .column-title-content { | ||||||
|  |             display: flex; | ||||||
|  |             align-items: center; | ||||||
|  |             flex: 1; | ||||||
|  |             min-width: 0; /* Allow text to truncate */ | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .board-view-container .board-column h3 .column-drag-handle { | ||||||
|  |             margin-right: 0.5em; | ||||||
|  |             color: var(--muted-text-color); | ||||||
|  |             cursor: grab; | ||||||
|  |             opacity: 0; | ||||||
|  |             transition: opacity 0.2s ease; | ||||||
|  |             padding: 0.25em; | ||||||
|  |             border-radius: 3px; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .board-view-container .board-column h3:hover .column-drag-handle { | ||||||
|  |             opacity: 1; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .board-view-container .board-column h3 .column-drag-handle:hover { | ||||||
|  |             background-color: var(--main-background-color); | ||||||
|  |             color: var(--main-text-color); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .board-view-container .board-column h3 .column-drag-handle:active { | ||||||
|  |             cursor: grabbing; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .board-view-container .board-column.column-dragging { | ||||||
|  |             opacity: 0.6; | ||||||
|  |             transform: scale(0.98); | ||||||
|  |             transition: opacity 0.2s ease, transform 0.2s ease; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .board-view-container .board-column.column-drag-over { | ||||||
|  |             border-color: var(--main-text-color); | ||||||
|  |             background-color: var(--hover-item-background-color); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         .board-view-container .board-column h3 input { |         .board-view-container .board-column h3 input { | ||||||
|             background: transparent; |             background: transparent; | ||||||
|             border: none; |             border: none; | ||||||
| @@ -172,6 +213,22 @@ const TPL = /*html*/` | |||||||
|             opacity: 1; |             opacity: 1; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         .column-drop-indicator { | ||||||
|  |             width: 4px; | ||||||
|  |             background-color: var(--main-text-color); | ||||||
|  |             border-radius: 2px; | ||||||
|  |             opacity: 0; | ||||||
|  |             transition: opacity 0.2s ease; | ||||||
|  |             height: 100%; | ||||||
|  |             z-index: 1000; | ||||||
|  |             box-shadow: 0 0 8px rgba(0, 0, 0, 0.3); | ||||||
|  |             flex-shrink: 0; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .column-drop-indicator.show { | ||||||
|  |             opacity: 1; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         .board-new-item { |         .board-new-item { | ||||||
|             margin-top: 0.5em; |             margin-top: 0.5em; | ||||||
|             padding: 0.5em; |             padding: 0.5em; | ||||||
| @@ -274,7 +331,9 @@ export default class BoardView extends ViewMode<BoardData> { | |||||||
|         this.dragContext = { |         this.dragContext = { | ||||||
|             draggedNote: null, |             draggedNote: null, | ||||||
|             draggedBranch: null, |             draggedBranch: null, | ||||||
|             draggedNoteElement: null |             draggedNoteElement: null, | ||||||
|  |             draggedColumn: null, | ||||||
|  |             draggedColumnElement: null | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         args.$parent.append(this.$root); |         args.$parent.append(this.$root); | ||||||
| @@ -320,8 +379,25 @@ export default class BoardView extends ViewMode<BoardData> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private setupBoardInteractions() { |     private setupBoardInteractions() { | ||||||
|         // Handle column title editing |         // Handle column title editing - listen for clicks on the title content, not the drag handle | ||||||
|  |         this.$container.on('click', 'h3[data-column-value] .column-title-content span:not(.column-drag-handle)', (e) => { | ||||||
|  |             e.stopPropagation(); | ||||||
|  |             const $titleEl = $(e.currentTarget).closest('h3[data-column-value]'); | ||||||
|  |             const columnValue = $titleEl.attr('data-column-value'); | ||||||
|  |             if (columnValue) { | ||||||
|  |                 const columnItems = this.api?.getColumn(columnValue) || []; | ||||||
|  |                 this.startEditingColumnTitle($titleEl, columnValue, columnItems); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // Also handle clicks on the h3 element itself (but not on the drag handle) | ||||||
|         this.$container.on('click', 'h3[data-column-value]', (e) => { |         this.$container.on('click', 'h3[data-column-value]', (e) => { | ||||||
|  |             // Only proceed if the click wasn't on the drag handle or edit icon | ||||||
|  |             if (!$(e.target).hasClass('column-drag-handle') &&  | ||||||
|  |                 !$(e.target).hasClass('edit-icon') &&  | ||||||
|  |                 !$(e.target).hasClass('bx-menu') && | ||||||
|  |                 !$(e.target).hasClass('bx-edit-alt')) { | ||||||
|  |                  | ||||||
|                 e.stopPropagation(); |                 e.stopPropagation(); | ||||||
|                 const $titleEl = $(e.currentTarget); |                 const $titleEl = $(e.currentTarget); | ||||||
|                 const columnValue = $titleEl.attr('data-column-value'); |                 const columnValue = $titleEl.attr('data-column-value'); | ||||||
| @@ -329,6 +405,7 @@ export default class BoardView extends ViewMode<BoardData> { | |||||||
|                     const columnItems = this.api?.getColumn(columnValue) || []; |                     const columnItems = this.api?.getColumn(columnValue) || []; | ||||||
|                     this.startEditingColumnTitle($titleEl, columnValue, columnItems); |                     this.startEditingColumnTitle($titleEl, columnValue, columnItems); | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         // Handle add column button |         // Handle add column button | ||||||
| @@ -339,12 +416,21 @@ export default class BoardView extends ViewMode<BoardData> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private createTitleStructure(title: string): { $titleText: JQuery<HTMLElement>; $editIcon: JQuery<HTMLElement> } { |     private createTitleStructure(title: string): { $titleText: JQuery<HTMLElement>; $editIcon: JQuery<HTMLElement> } { | ||||||
|  |         const $dragHandle = $("<span>") | ||||||
|  |             .addClass("column-drag-handle icon bx bx-menu") | ||||||
|  |             .attr("title", "Drag to reorder column"); | ||||||
|  |  | ||||||
|         const $titleText = $("<span>").text(title); |         const $titleText = $("<span>").text(title); | ||||||
|  |          | ||||||
|  |         const $titleContent = $("<div>") | ||||||
|  |             .addClass("column-title-content") | ||||||
|  |             .append($dragHandle, $titleText); | ||||||
|  |  | ||||||
|         const $editIcon = $("<span>") |         const $editIcon = $("<span>") | ||||||
|             .addClass("edit-icon icon bx bx-edit-alt") |             .addClass("edit-icon icon bx bx-edit-alt") | ||||||
|             .attr("title", "Click to edit column title"); |             .attr("title", "Click to edit column title"); | ||||||
|  |  | ||||||
|         return { $titleText, $editIcon }; |         return { $titleText: $titleContent, $editIcon }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private startEditingColumnTitle($titleEl: JQuery<HTMLElement>, columnValue: string, columnItems: { branch: any; note: any; }[]) { |     private startEditingColumnTitle($titleEl: JQuery<HTMLElement>, columnValue: string, columnItems: { branch: any; note: any; }[]) { | ||||||
| @@ -352,8 +438,9 @@ export default class BoardView extends ViewMode<BoardData> { | |||||||
|             return; // Already editing |             return; // Already editing | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const $titleText = $titleEl.find("span").first(); |         const $titleContent = $titleEl.find(".column-title-content"); | ||||||
|         const currentTitle = $titleText.text(); |         const $titleSpan = $titleContent.find("span").last(); // Get the text span, not the drag handle | ||||||
|  |         const currentTitle = $titleSpan.text(); | ||||||
|         $titleEl.addClass("editing"); |         $titleEl.addClass("editing"); | ||||||
|  |  | ||||||
|         const $input = $("<input>") |         const $input = $("<input>") | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user