mirror of
https://github.com/zadam/trilium.git
synced 2025-10-20 07:08:55 +02:00
feat(views/board): basic column drag support
This commit is contained in:
parent
43229f0b99
commit
cb37724879
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) => {
|
||||||
e.preventDefault();
|
// Only handle note drops when a note is being dragged
|
||||||
const originalEvent = e.originalEvent as DragEvent;
|
if (this.context.draggedNote && !this.context.draggedColumn) {
|
||||||
if (originalEvent.dataTransfer) {
|
e.preventDefault();
|
||||||
originalEvent.dataTransfer.dropEffect = "move";
|
const originalEvent = e.originalEvent as DragEvent;
|
||||||
}
|
if (originalEvent.dataTransfer) {
|
||||||
|
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,16 +259,59 @@ 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) => {
|
||||||
e.preventDefault();
|
if (this.context.draggedNote && !this.context.draggedColumn) {
|
||||||
$columnEl.removeClass("drag-over");
|
e.preventDefault();
|
||||||
|
$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,10 +379,10 @@ 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]', (e) => {
|
this.$container.on('click', 'h3[data-column-value] .column-title-content span:not(.column-drag-handle)', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const $titleEl = $(e.currentTarget);
|
const $titleEl = $(e.currentTarget).closest('h3[data-column-value]');
|
||||||
const columnValue = $titleEl.attr('data-column-value');
|
const columnValue = $titleEl.attr('data-column-value');
|
||||||
if (columnValue) {
|
if (columnValue) {
|
||||||
const columnItems = this.api?.getColumn(columnValue) || [];
|
const columnItems = this.api?.getColumn(columnValue) || [];
|
||||||
@ -331,6 +390,24 @@ export default class BoardView extends ViewMode<BoardData> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Also handle clicks on the h3 element itself (but not on the drag handle)
|
||||||
|
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();
|
||||||
|
const $titleEl = $(e.currentTarget);
|
||||||
|
const columnValue = $titleEl.attr('data-column-value');
|
||||||
|
if (columnValue) {
|
||||||
|
const columnItems = this.api?.getColumn(columnValue) || [];
|
||||||
|
this.startEditingColumnTitle($titleEl, columnValue, columnItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Handle add column button
|
// Handle add column button
|
||||||
this.$container.on('click', '.board-add-column', (e) => {
|
this.$container.on('click', '.board-add-column', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -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>")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user