From 3e5c91415db9de3cdf97b87f254d8b2a79d55941 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 20 Jul 2025 18:17:53 +0300 Subject: [PATCH] feat(views/board): rename columns --- .../widgets/view_widgets/board_view/api.ts | 7 + .../widgets/view_widgets/board_view/index.ts | 146 +++++++++++++++++- 2 files changed, 151 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/view_widgets/board_view/api.ts b/apps/client/src/widgets/view_widgets/board_view/api.ts index 436e88152..bccf0201b 100644 --- a/apps/client/src/widgets/view_widgets/board_view/api.ts +++ b/apps/client/src/widgets/view_widgets/board_view/api.ts @@ -42,4 +42,11 @@ export default class BoardApi { } } + async renameColumn(oldValue: string, newValue: string, noteIds: string[]) { + // Update all notes that have the old status value to the new value + for (const noteId of noteIds) { + await attributes.setLabel(noteId, "status", newValue); + } + } + } diff --git a/apps/client/src/widgets/view_widgets/board_view/index.ts b/apps/client/src/widgets/view_widgets/board_view/index.ts index 7ca6d9738..b772e2082 100644 --- a/apps/client/src/widgets/view_widgets/board_view/index.ts +++ b/apps/client/src/widgets/view_widgets/board_view/index.ts @@ -49,6 +49,54 @@ const TPL = /*html*/` margin-bottom: 0.75em; padding-bottom: 0.5em; border-bottom: 1px solid var(--main-border-color); + cursor: pointer; + position: relative; + transition: background-color 0.2s ease; + display: flex; + align-items: center; + justify-content: space-between; + } + + .board-view-container .board-column h3:hover { + background-color: var(--hover-item-background-color); + border-radius: 4px; + padding: 0.25em 0.5em; + margin: -0.25em -0.5em 0.75em -0.5em; + } + + .board-view-container .board-column h3.editing { + background-color: var(--main-background-color); + border: 1px solid var(--main-text-color); + border-radius: 4px; + padding: 0.25em 0.5em; + margin: -0.25em -0.5em 0.75em -0.5em; + } + + .board-view-container .board-column h3 input { + background: transparent; + border: none; + outline: none; + font-size: inherit; + font-weight: inherit; + color: inherit; + width: 100%; + font-family: inherit; + } + + .board-view-container .board-column h3 .edit-icon { + opacity: 0; + font-size: 0.8em; + margin-left: 0.5em; + transition: opacity 0.2s ease; + color: var(--muted-text-color); + } + + .board-view-container .board-column h3:hover .edit-icon { + opacity: 1; + } + + .board-view-container .board-column h3.editing .edit-icon { + display: none; } .board-view-container .board-note { @@ -177,10 +225,23 @@ export default class BoardView extends ViewMode { continue; } + // Find the column data to get custom title + const columnTitle = column; + const $columnEl = $("
") .addClass("board-column") - .attr("data-column", column) - .append($("

").text(column)); + .attr("data-column", column); + + const $titleEl = $("

") + .attr("data-column-value", column); + + const { $titleText, $editIcon } = this.createTitleStructure(columnTitle); + $titleEl.append($titleText, $editIcon); + + // Make column title editable + this.setupColumnTitleEdit($titleEl, column, columnItems); + + $columnEl.append($titleEl); // Allow vertical scrolling in the column, bypassing the horizontal scroll of the container. $columnEl.on("wheel", (event) => { @@ -401,6 +462,87 @@ export default class BoardView extends ViewMode { $dropIndicator.addClass("show"); } + private createTitleStructure(title: string): { $titleText: JQuery; $editIcon: JQuery } { + const $titleText = $("").text(title); + const $editIcon = $("") + .addClass("edit-icon icon bx bx-edit-alt") + .attr("title", "Click to edit column title"); + + return { $titleText, $editIcon }; + } + + private setupColumnTitleEdit($titleEl: JQuery, columnValue: string, columnItems: { branch: any; note: any; }[]) { + $titleEl.on("click", (e) => { + e.stopPropagation(); + this.startEditingColumnTitle($titleEl, columnValue, columnItems); + }); + } + + private startEditingColumnTitle($titleEl: JQuery, columnValue: string, columnItems: { branch: any; note: any; }[]) { + if ($titleEl.hasClass("editing")) { + return; // Already editing + } + + const $titleText = $titleEl.find("span").first(); + const currentTitle = $titleText.text(); + $titleEl.addClass("editing"); + + const $input = $("") + .attr("type", "text") + .val(currentTitle) + .attr("placeholder", "Column title"); + + $titleEl.empty().append($input); + $input.focus().select(); + + const finishEdit = async (save: boolean = true) => { + if (!$titleEl.hasClass("editing")) { + return; // Already finished + } + + $titleEl.removeClass("editing"); + + let finalTitle = currentTitle; + if (save) { + const newTitle = $input.val() as string; + if (newTitle.trim() && newTitle !== currentTitle) { + await this.renameColumn(columnValue, newTitle.trim(), columnItems); + finalTitle = newTitle.trim(); + } + } + + // Recreate the title structure + const { $titleText, $editIcon } = this.createTitleStructure(finalTitle); + $titleEl.empty().append($titleText, $editIcon); + }; + + $input.on("blur", () => finishEdit(true)); + $input.on("keydown", (e) => { + if (e.key === "Enter") { + e.preventDefault(); + finishEdit(true); + } else if (e.key === "Escape") { + e.preventDefault(); + finishEdit(false); + } + }); + } + + private async renameColumn(oldValue: string, newValue: string, columnItems: { branch: any; note: any; }[]) { + try { + // Get all note IDs in this column + const noteIds = columnItems.map(item => item.note.noteId); + + // Use the API to rename the column (update all notes) + await this.api?.renameColumn(oldValue, newValue, noteIds); + + // Refresh the board to reflect the changes + await this.renderList(); + } catch (error) { + console.error("Failed to rename column:", error); + } + } + private async createNewItem(column: string) { try { // Get the parent note path