From 1f792ca41895a1f41279b9d7b3f40a89b838b0d5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 20 Jul 2025 20:06:54 +0300 Subject: [PATCH] feat(views/board): add new column --- .../widgets/view_widgets/board_view/api.ts | 15 +++ .../widgets/view_widgets/board_view/index.ts | 118 ++++++++++++++++++ 2 files changed, 133 insertions(+) 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 ffec13960..cd94abce9 100644 --- a/apps/client/src/widgets/view_widgets/board_view/api.ts +++ b/apps/client/src/widgets/view_widgets/board_view/api.ts @@ -87,6 +87,21 @@ export default class BoardApi { this.viewStorage.store(this.persistedData); } + async createColumn(columnValue: string) { + // Add the new column to persisted data if it doesn't exist + if (!this.persistedData.columns) { + this.persistedData.columns = []; + } + + const existingColumn = this.persistedData.columns.find(col => col.value === columnValue); + if (!existingColumn) { + this.persistedData.columns.push({ value: columnValue }); + await this.viewStorage.store(this.persistedData); + } + + return columnValue; + } + static async build(parentNote: FNote, viewStorage: ViewModeStorage) { let persistedData = await viewStorage.restore() ?? {}; const { byColumn, newPersistedData } = await getBoardData(parentNote, "status", persistedData); 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 6505d11cf..ad4a46b9f 100644 --- a/apps/client/src/widgets/view_widgets/board_view/index.ts +++ b/apps/client/src/widgets/view_widgets/board_view/index.ts @@ -160,6 +160,39 @@ const TPL = /*html*/` .board-new-item .icon { margin-right: 0.25em; } + + .board-add-column { + width: 250px; + flex-shrink: 0; + min-height: 200px; + border: 2px dashed var(--main-border-color); + border-radius: 8px; + padding: 0.5em; + background-color: transparent; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: var(--muted-text-color); + } + + .board-add-column:hover { + border-color: var(--main-text-color); + color: var(--main-text-color); + background-color: var(--hover-item-background-color); + } + + .board-add-column.editing { + border-style: solid; + border-color: var(--main-text-color); + background-color: var(--main-background-color); + } + + .board-add-column .icon { + margin-right: 0.5em; + font-size: 1.2em; + }
@@ -282,6 +315,18 @@ export default class BoardView extends ViewMode { $(el).append($columnEl); } + + // Add "Add Column" button at the end + const $addColumnEl = $("
") + .addClass("board-add-column") + .html('Add Column'); + + $addColumnEl.on("click", (e) => { + e.stopPropagation(); + this.startCreatingNewColumn($addColumnEl); + }); + + $(el).append($addColumnEl); } private setupNoteDrag($noteEl: JQuery, note: any, branch: any) { @@ -554,6 +599,79 @@ export default class BoardView extends ViewMode { } } + private startCreatingNewColumn($addColumnEl: JQuery) { + if ($addColumnEl.hasClass("editing")) { + return; // Already editing + } + + $addColumnEl.addClass("editing"); + + const $input = $("") + .attr("type", "text") + .attr("placeholder", "Enter column name...") + .css({ + background: "var(--main-background-color)", + border: "1px solid var(--main-text-color)", + borderRadius: "4px", + padding: "0.5em", + color: "var(--main-text-color)", + fontFamily: "inherit", + fontSize: "inherit", + width: "100%", + textAlign: "center" + }); + + $addColumnEl.empty().append($input); + $input.focus(); + + const finishEdit = async (save: boolean = true) => { + if (!$addColumnEl.hasClass("editing")) { + return; // Already finished + } + + $addColumnEl.removeClass("editing"); + + if (save) { + const columnName = $input.val() as string; + if (columnName.trim()) { + await this.createNewColumn(columnName.trim()); + } + } + + // Restore the add button + $addColumnEl.html('Add Column'); + }; + + $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 createNewColumn(columnName: string) { + try { + // Check if column already exists + if (this.api?.columns.includes(columnName)) { + console.warn("A column with this name already exists."); + return; + } + + // Create the new column + await this.api?.createColumn(columnName); + + // Refresh the board to show the new column + await this.renderList(); + } catch (error) { + console.error("Failed to create new column:", error); + } + } + async onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">) { // React to changes in "status" attribute for notes in this board if (loadResults.getAttributeRows().some(attr => attr.name === "status" && this.noteIds.includes(attr.noteId!))) {