From 4247c8fdc672ad2fec9407e7481ee76af23a74f6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Sep 2025 20:18:17 +0300 Subject: [PATCH 1/5] chore(react/collections/board): render empty columns --- .../src/widgets/collections/NoteList.tsx | 3 + .../board_view => collections/board}/data.ts | 6 +- .../src/widgets/collections/board/index.css | 264 +++++++++++++++++ .../src/widgets/collections/board/index.tsx | 59 ++++ .../widgets/view_widgets/board_view/api.ts | 19 +- .../widgets/view_widgets/board_view/config.ts | 7 - .../board_view/differential_renderer.ts | 18 -- .../widgets/view_widgets/board_view/index.ts | 273 ------------------ 8 files changed, 331 insertions(+), 318 deletions(-) rename apps/client/src/widgets/{view_widgets/board_view => collections/board}/data.ts (95%) create mode 100644 apps/client/src/widgets/collections/board/index.css create mode 100644 apps/client/src/widgets/collections/board/index.tsx delete mode 100644 apps/client/src/widgets/view_widgets/board_view/config.ts diff --git a/apps/client/src/widgets/collections/NoteList.tsx b/apps/client/src/widgets/collections/NoteList.tsx index b0dd94622..8e462e44b 100644 --- a/apps/client/src/widgets/collections/NoteList.tsx +++ b/apps/client/src/widgets/collections/NoteList.tsx @@ -8,6 +8,7 @@ import GeoView from "./geomap"; import ViewModeStorage from "../view_widgets/view_mode_storage"; import CalendarView from "./calendar"; import TableView from "./table"; +import BoardView from "./board"; interface NoteListProps { note?: FNote | null; @@ -88,6 +89,8 @@ function getComponentByViewType(viewType: ViewTypeOptions, props: ViewModeProps< return case "table": return + case "board": + return } } diff --git a/apps/client/src/widgets/view_widgets/board_view/data.ts b/apps/client/src/widgets/collections/board/data.ts similarity index 95% rename from apps/client/src/widgets/view_widgets/board_view/data.ts rename to apps/client/src/widgets/collections/board/data.ts index f468f2292..2a59e82b7 100644 --- a/apps/client/src/widgets/view_widgets/board_view/data.ts +++ b/apps/client/src/widgets/collections/board/data.ts @@ -1,13 +1,13 @@ import FBranch from "../../../entities/fbranch"; import FNote from "../../../entities/fnote"; -import { BoardData } from "./config"; +import { BoardViewData } from "./index"; export type ColumnMap = Map; -export async function getBoardData(parentNote: FNote, groupByColumn: string, persistedData: BoardData) { +export async function getBoardData(parentNote: FNote, groupByColumn: string, persistedData: BoardViewData) { const byColumn: ColumnMap = new Map(); // First, scan all notes to find what columns actually exist @@ -43,7 +43,7 @@ export async function getBoardData(parentNote: FNote, groupByColumn: string, per } // Return updated persisted data only if there were changes - let newPersistedData: BoardData | undefined; + let newPersistedData: BoardViewData | undefined; const hasChanges = newColumnValues.length > 0 || existingPersistedColumns.length !== deduplicatedColumns.length || !existingPersistedColumns.every((col, idx) => deduplicatedColumns[idx]?.value === col.value); diff --git a/apps/client/src/widgets/collections/board/index.css b/apps/client/src/widgets/collections/board/index.css new file mode 100644 index 000000000..bc941b54e --- /dev/null +++ b/apps/client/src/widgets/collections/board/index.css @@ -0,0 +1,264 @@ +.board-view { + overflow-x: auto; + position: relative; + height: 100%; + user-select: none; +} + +.board-view-container { + height: 100%; + display: flex; + gap: 1em; + padding: 1em; + padding-bottom: 0; + align-items: flex-start; +} + +.board-view-container .board-column { + width: 250px; + flex-shrink: 0; + border: 2px solid transparent; + border-radius: 8px; + padding: 0.5em; + background-color: var(--accented-background-color); + transition: border-color 0.2s ease; + overflow-y: auto; + max-height: 100%; +} + +.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 { + font-size: 1em; + margin-bottom: 0.75em; + padding: 0.5em 0.5em 0.5em 0.5em; + border-bottom: 1px solid var(--main-border-color); + cursor: grab; + position: relative; + transition: background-color 0.2s ease, border-radius 0.2s ease; + display: flex; + align-items: center; + justify-content: space-between; + box-sizing: border-box; + background-color: transparent; +} + +.board-view-container .board-column h3:active { + cursor: grabbing; +} + +.board-view-container .board-column h3.editing { + cursor: default; +} + +.board-view-container .board-column h3:hover { + background-color: var(--hover-item-background-color); + border-radius: 4px; +} + +.board-view-container .board-column h3.editing { + background-color: var(--main-background-color); + border: 1px solid var(--main-text-color); + border-radius: 4px; +} + +.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 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; + 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 { + box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.25); + margin: 0.65em 0; + padding: 0.5em; + 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, opacity 0.15s ease; + opacity: 1; +} + +.board-view-container .board-note.fade-in { + animation: fadeIn 0.15s ease-in; +} + +.board-view-container .board-note.fade-out { + animation: fadeOut 0.15s ease-out forwards; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(-10px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes fadeOut { + from { opacity: 1; transform: translateY(0); } + to { opacity: 0; transform: translateY(-10px); } +} + +.board-view-container .board-note.card-updated { + animation: cardUpdate 0.3s ease-in-out; +} + +@keyframes cardUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.02); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); } + 100% { transform: scale(1); } +} + +.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.editing { + box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.35); + border-color: var(--main-text-color); +} + +.board-view-container .board-note.editing input { + background: transparent; + border: none; + outline: none; + font-family: inherit; + font-size: inherit; + color: inherit; + width: 100%; + padding: 0; +} + +.board-view-container .board-note .icon { + 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; +} + +.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 { + margin-top: 0.5em; + padding: 0.5em; + border-radius: 5px; + color: var(--muted-text-color); + cursor: pointer; + transition: all 0.2s ease; + background-color: transparent; +} + +.board-new-item:hover { + border-color: var(--main-text-color); + color: var(--main-text-color); + background-color: var(--hover-item-background-color); +} + +.board-new-item .icon { + margin-right: 0.25em; +} + +.board-add-column { + width: 180px; + flex-shrink: 0; + height: 60px; + border-radius: 8px; + padding: 0.5em; + background-color: var(--accented-background-color); + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: var(--muted-text-color); + font-size: 0.9em; + align-self: flex-start; +} + +.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 .icon { + margin-right: 0.5em; + font-size: 1.2em; +} + +.board-drag-preview { + position: fixed; + z-index: 10000; + pointer-events: none; + opacity: 0.8; + transform: rotate(5deg); + box-shadow: 4px 8px 16px rgba(0, 0, 0, 0.5); + background-color: var(--main-background-color); + border: 1px solid var(--main-border-color); + border-radius: 5px; + padding: 0.5em; + font-size: 0.9em; + max-width: 200px; + word-wrap: break-word; +} \ No newline at end of file diff --git a/apps/client/src/widgets/collections/board/index.tsx b/apps/client/src/widgets/collections/board/index.tsx new file mode 100644 index 000000000..a84e934b8 --- /dev/null +++ b/apps/client/src/widgets/collections/board/index.tsx @@ -0,0 +1,59 @@ +import { useEffect, useState } from "preact/hooks"; +import { ViewModeProps } from "../interface"; +import "./index.css"; +import { ColumnMap, getBoardData } from "./data"; +import { useNoteLabel } from "../../react/hooks"; + +export interface BoardViewData { + columns?: BoardColumnData[]; +} + +export interface BoardColumnData { + value: string; +} + +export default function BoardView({ note: parentNote, noteIds, viewConfig, saveConfig }: ViewModeProps) { + const [ statusAttribute ] = useNoteLabel(parentNote, "board:groupBy"); + const [ byColumn, setByColumn ] = useState(); + const [ columns, setColumns ] = useState(); + + useEffect(() => { + getBoardData(parentNote, statusAttribute ?? "status", viewConfig ?? {}).then(({ byColumn, newPersistedData }) => { + setByColumn(byColumn); + + if (newPersistedData) { + viewConfig = { ...newPersistedData }; + saveConfig(newPersistedData); + } + + // Use the order from persistedData.columns, then add any new columns found + const orderedColumns = viewConfig?.columns?.map(col => col.value) || []; + const allColumns = Array.from(byColumn.keys()); + const newColumns = allColumns.filter(col => !orderedColumns.includes(col)); + setColumns([...orderedColumns, ...newColumns]); + }); + }, [ parentNote ]); + + return ( +
+
+ {columns?.map(column => ( + + ))} +
+
+ ) +} + +function Column({ column }: { column: string }) { + return ( +
+

+ {column} + +

+
+ ) +} 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 20c51141a..df354ace4 100644 --- a/apps/client/src/widgets/view_widgets/board_view/api.ts +++ b/apps/client/src/widgets/view_widgets/board_view/api.ts @@ -135,16 +135,13 @@ export default class BoardApi { async refresh(parentNote: FNote) { // Refresh the API data by re-fetching from the parent note - const statusAttribute = parentNote.getLabelValue("board:groupBy") ?? "status"; - this._statusAttribute = statusAttribute; // Use the current in-memory persisted data instead of restoring from storage // This ensures we don't lose recent updates like column renames - const { byColumn, newPersistedData } = await getBoardData(parentNote, statusAttribute, this.persistedData); - + // Update internal state this.byColumn = byColumn; - + if (newPersistedData) { this.persistedData = newPersistedData; this.viewStorage.store(this.persistedData); @@ -161,18 +158,6 @@ export default class BoardApi { const statusAttribute = parentNote.getLabelValue("board:groupBy") ?? "status"; let persistedData = await viewStorage.restore() ?? {}; - const { byColumn, newPersistedData } = await getBoardData(parentNote, statusAttribute, persistedData); - - // 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) { - persistedData = newPersistedData; - viewStorage.store(persistedData); - } return new BoardApi(columns, parentNote.noteId, viewStorage, byColumn, persistedData, statusAttribute); } diff --git a/apps/client/src/widgets/view_widgets/board_view/config.ts b/apps/client/src/widgets/view_widgets/board_view/config.ts deleted file mode 100644 index 92dd99f5f..000000000 --- a/apps/client/src/widgets/view_widgets/board_view/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface BoardColumnData { - value: string; -} - -export interface BoardData { - columns?: BoardColumnData[]; -} diff --git a/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts b/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts index 4f1cf64dc..e73f74a51 100644 --- a/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts +++ b/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts @@ -329,24 +329,6 @@ export class DifferentialBoardRenderer { } private createColumn(column: string, columnItems: { note: any; branch: any }[]): JQuery { - const $columnEl = $("
") - .addClass("board-column") - .attr("data-column", column); - - // Create header - const $titleEl = $("

").attr("data-column-value", column); - - // Create title text - const $titleText = $("").text(column); - - // Create edit icon - const $editIcon = $("") - .addClass("edit-icon icon bx bx-edit-alt") - .attr("title", "Click to edit column title"); - - $titleEl.append($titleText, $editIcon); - $columnEl.append($titleEl); - // Setup column dragging this.dragHandler.setupColumnDrag($columnEl, column); 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 1a4a48bb2..ec203068f 100644 --- a/apps/client/src/widgets/view_widgets/board_view/index.ts +++ b/apps/client/src/widgets/view_widgets/board_view/index.ts @@ -9,279 +9,6 @@ import BoardApi from "./api"; import { BoardDragHandler, DragContext } from "./drag_handler"; import { DifferentialBoardRenderer } from "./differential_renderer"; -const TPL = /*html*/` -
- - -
-
-`; - export default class BoardView extends ViewMode { private $root: JQuery; From 4b769da90b4338bd41a23f41cec04345d0852030 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Sep 2025 20:38:12 +0300 Subject: [PATCH 2/5] chore(react/collections/board): render items --- .../src/widgets/collections/board/index.tsx | 23 +++++++++++++-- .../board_view/differential_renderer.ts | 28 ------------------- 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/apps/client/src/widgets/collections/board/index.tsx b/apps/client/src/widgets/collections/board/index.tsx index a84e934b8..4220cca8e 100644 --- a/apps/client/src/widgets/collections/board/index.tsx +++ b/apps/client/src/widgets/collections/board/index.tsx @@ -3,6 +3,8 @@ import { ViewModeProps } from "../interface"; import "./index.css"; import { ColumnMap, getBoardData } from "./data"; import { useNoteLabel } from "../../react/hooks"; +import FNote from "../../../entities/fnote"; +import FBranch from "../../../entities/fbranch"; export interface BoardViewData { columns?: BoardColumnData[]; @@ -37,15 +39,15 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC return (
- {columns?.map(column => ( - + {byColumn && columns?.map(column => ( + ))}
) } -function Column({ column }: { column: string }) { +function Column({ column, columnItems }: { column: string, columnItems?: { note: FNote, branch: FBranch }[] }) { return (

@@ -54,6 +56,21 @@ function Column({ column }: { column: string }) { className="edit-icon icon bx bx-edit-alt" title="Click to edit column title" />

+ + {(columnItems ?? []).map(({ note, branch }) => ( + + ))} +
+ ) +} + +function Card({ note }: { note: FNote, branch: FBranch, column: string }) { + const colorClass = note.getColorClass() || ''; + + return ( +
+ + {note.title}
) } diff --git a/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts b/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts index e73f74a51..474fa7317 100644 --- a/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts +++ b/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts @@ -345,14 +345,6 @@ export class DifferentialBoardRenderer { this.dragHandler.setupNoteDropZone($columnEl, column); this.dragHandler.setupColumnDropZone($columnEl); - // Add cards - for (const item of columnItems) { - if (item.note) { - const $noteEl = this.createCard(item.note, item.branch, column); - $columnEl.append($noteEl); - } - } - // Add "New item" button const $newItemEl = $("
") .addClass("board-new-item") @@ -366,26 +358,6 @@ export class DifferentialBoardRenderer { } private createCard(note: any, branch: any, column: string): JQuery { - const $iconEl = $("") - .addClass("icon") - .addClass(note.getIcon()); - - const colorClass = note.getColorClass() || ''; - - const $noteEl = $("
") - .addClass("board-note") - .attr("data-note-id", note.noteId) - .attr("data-branch-id", branch.branchId) - .attr("data-current-column", column) - .attr("data-icon-class", note.getIcon()) - .attr("data-color-class", colorClass) - .text(note.title); - - // Add color class to the card if it exists - if (colorClass) { - $noteEl.addClass(colorClass); - } - $noteEl.prepend($iconEl); $noteEl.on("click", () => appContext.triggerCommand("openInPopup", { noteIdOrPath: note.noteId })); From ecf8c4ffbeb6cc3265d58615ca1afc139f57e42c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Sep 2025 21:10:31 +0300 Subject: [PATCH 3/5] chore(react/collections/board): get new items to be created --- .../src/widgets/collections/board/api.ts | 31 +++++++++++++++++++ .../src/widgets/collections/board/index.tsx | 16 ++++++++-- .../widgets/view_widgets/board_view/api.ts | 4 --- .../board_view/differential_renderer.ts | 3 +- .../widgets/view_widgets/board_view/index.ts | 27 ---------------- 5 files changed, 46 insertions(+), 35 deletions(-) create mode 100644 apps/client/src/widgets/collections/board/api.ts diff --git a/apps/client/src/widgets/collections/board/api.ts b/apps/client/src/widgets/collections/board/api.ts new file mode 100644 index 000000000..70246efcc --- /dev/null +++ b/apps/client/src/widgets/collections/board/api.ts @@ -0,0 +1,31 @@ +import FNote from "../../../entities/fnote"; +import attributes from "../../../services/attributes"; +import note_create from "../../../services/note_create"; + +export async function createNewItem(parentNote: FNote, column: string) { + try { + // Get the parent note path + const parentNotePath = parentNote.noteId; + const statusAttribute = parentNote.getLabelValue("board:groupBy") ?? "status"; + + // Create a new note as a child of the parent note + const { note: newNote } = await note_create.createNote(parentNotePath, { + activate: false, + title: "New item" + }); + + if (newNote) { + // Set the status label to place it in the correct column + await changeColumn(newNote.noteId, column, statusAttribute); + + // Start inline editing of the newly created card + //this.startInlineEditingCard(newNote.noteId); + } + } catch (error) { + console.error("Failed to create new item:", error); + } +} + +async function changeColumn(noteId: string, newColumn: string, statusAttribute: string) { + await attributes.setLabel(noteId, statusAttribute, newColumn); +} diff --git a/apps/client/src/widgets/collections/board/index.tsx b/apps/client/src/widgets/collections/board/index.tsx index 4220cca8e..d47a066bc 100644 --- a/apps/client/src/widgets/collections/board/index.tsx +++ b/apps/client/src/widgets/collections/board/index.tsx @@ -5,6 +5,9 @@ import { ColumnMap, getBoardData } from "./data"; import { useNoteLabel } from "../../react/hooks"; import FNote from "../../../entities/fnote"; import FBranch from "../../../entities/fbranch"; +import Icon from "../../react/Icon"; +import { t } from "../../../services/i18n"; +import { createNewItem } from "./api"; export interface BoardViewData { columns?: BoardColumnData[]; @@ -40,14 +43,18 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
{byColumn && columns?.map(column => ( - + ))}
) } -function Column({ column, columnItems }: { column: string, columnItems?: { note: FNote, branch: FBranch }[] }) { +function Column({ parentNote, column, columnItems }: { parentNote: FNote, column: string, columnItems?: { note: FNote, branch: FBranch }[] }) { return (

@@ -60,6 +67,11 @@ function Column({ column, columnItems }: { column: string, columnItems?: { note: {(columnItems ?? []).map(({ note, branch }) => ( ))} + +
createNewItem(parentNote, column)}> + {" "} + {t("board_view.new-item")} +

) } 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 df354ace4..086a6a714 100644 --- a/apps/client/src/widgets/view_widgets/board_view/api.ts +++ b/apps/client/src/widgets/view_widgets/board_view/api.ts @@ -29,10 +29,6 @@ export default class BoardApi { return this.byColumn.get(column); } - async changeColumn(noteId: string, newColumn: string) { - await attributes.setLabel(noteId, this._statusAttribute, newColumn); - } - openNote(noteId: string) { appContext.triggerCommand("openInPopup", { noteIdOrPath: noteId }); } diff --git a/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts b/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts index 474fa7317..ba2d0990e 100644 --- a/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts +++ b/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts @@ -349,9 +349,8 @@ export class DifferentialBoardRenderer { const $newItemEl = $("
") .addClass("board-new-item") .attr("data-column", column) - .html(` ${t("board_view.new-item")}`); + .html(` ${}`); - $newItemEl.on("click", () => this.onCreateNewItem(column)); $columnEl.append($newItemEl); return $columnEl; 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 ec203068f..489fd0b95 100644 --- a/apps/client/src/widgets/view_widgets/board_view/index.ts +++ b/apps/client/src/widgets/view_widgets/board_view/index.ts @@ -64,7 +64,6 @@ export default class BoardView extends ViewMode { this.$container, this.api, this.dragHandler, - (column: string) => this.createNewItem(column), this.parentNote, this.viewStorage, () => this.refreshApi() @@ -220,32 +219,6 @@ export default class BoardView extends ViewMode { } } - private async createNewItem(column: string) { - try { - // Get the parent note path - const parentNotePath = this.parentNote.noteId; - - // Create a new note as a child of the parent note - const { note: newNote } = await noteCreateService.createNote(parentNotePath, { - activate: false, - title: "New item" - }); - - if (newNote) { - // Set the status label to place it in the correct column - await this.api?.changeColumn(newNote.noteId, column); - - // Refresh the board to show the new item - await this.renderList(); - - // Start inline editing of the newly created card - this.startInlineEditingCard(newNote.noteId); - } - } catch (error) { - console.error("Failed to create new item:", error); - } - } - async insertItemAtPosition(column: string, relativeToBranchId: string, direction: "before" | "after"): Promise { try { // Create the note without opening it From 6f2d51f3ffc2a3f5a37c7e38ef5c2bf21477ca9d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Sep 2025 21:41:15 +0300 Subject: [PATCH 4/5] chore(react/collections/board): attempt to reload events --- .../src/widgets/collections/board/data.ts | 1 + .../src/widgets/collections/board/index.tsx | 33 +++++++++++++++++-- .../widgets/view_widgets/board_view/index.ts | 25 -------------- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/apps/client/src/widgets/collections/board/data.ts b/apps/client/src/widgets/collections/board/data.ts index 2a59e82b7..47cc10144 100644 --- a/apps/client/src/widgets/collections/board/data.ts +++ b/apps/client/src/widgets/collections/board/data.ts @@ -65,6 +65,7 @@ async function recursiveGroupBy(branches: FBranch[], byColumn: ColumnMap, groupB for (const branch of branches) { const note = await branch.getNote(); if (!note) { + console.warn("Not note found"); continue; } diff --git a/apps/client/src/widgets/collections/board/index.tsx b/apps/client/src/widgets/collections/board/index.tsx index d47a066bc..97cdbf97a 100644 --- a/apps/client/src/widgets/collections/board/index.tsx +++ b/apps/client/src/widgets/collections/board/index.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "preact/hooks"; import { ViewModeProps } from "../interface"; import "./index.css"; import { ColumnMap, getBoardData } from "./data"; -import { useNoteLabel } from "../../react/hooks"; +import { useNoteLabel, useTriliumEvent } from "../../react/hooks"; import FNote from "../../../entities/fnote"; import FBranch from "../../../entities/fbranch"; import Icon from "../../react/Icon"; @@ -22,7 +22,7 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC const [ byColumn, setByColumn ] = useState(); const [ columns, setColumns ] = useState(); - useEffect(() => { + function refresh() { getBoardData(parentNote, statusAttribute ?? "status", viewConfig ?? {}).then(({ byColumn, newPersistedData }) => { setByColumn(byColumn); @@ -37,7 +37,34 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC const newColumns = allColumns.filter(col => !orderedColumns.includes(col)); setColumns([...orderedColumns, ...newColumns]); }); - }, [ parentNote ]); + } + + useEffect(refresh, [ parentNote, noteIds ]); + + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { + // TODO: Re-enable + return; + + // Check if any changes affect our board + const hasRelevantChanges = + // React to changes in status attribute for notes in this board + loadResults.getAttributeRows().some(attr => attr.name === statusAttribute && noteIds.includes(attr.noteId!)) || + // React to changes in note title + loadResults.getNoteIds().some(noteId => noteIds.includes(noteId)) || + // React to changes in branches for subchildren (e.g., moved, added, or removed notes) + loadResults.getBranchRows().some(branch => noteIds.includes(branch.noteId!)) || + // React to changes in note icon or color. + loadResults.getAttributeRows().some(attr => [ "iconClass", "color" ].includes(attr.name ?? "") && noteIds.includes(attr.noteId ?? "")) || + // React to attachment change + loadResults.getAttachmentRows().some(att => att.ownerId === parentNote.noteId && att.title === "board.json") || + // React to changes in "groupBy" + loadResults.getAttributeRows().some(attr => attr.name === "board:groupBy" && attr.noteId === parentNote.noteId); + + if (hasRelevantChanges) { + console.log("Trigger refresh"); + refresh(); + } + }); return (
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 489fd0b95..5b874a5f7 100644 --- a/apps/client/src/widgets/view_widgets/board_view/index.ts +++ b/apps/client/src/widgets/view_widgets/board_view/index.ts @@ -318,31 +318,6 @@ export default class BoardView extends ViewMode { } } - async onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">) { - // Check if any changes affect our board - const hasRelevantChanges = - // React to changes in status attribute for notes in this board - loadResults.getAttributeRows().some(attr => attr.name === this.api?.statusAttribute && this.noteIds.includes(attr.noteId!)) || - // React to changes in note title - loadResults.getNoteIds().some(noteId => this.noteIds.includes(noteId)) || - // React to changes in branches for subchildren (e.g., moved, added, or removed notes) - loadResults.getBranchRows().some(branch => this.noteIds.includes(branch.noteId!)) || - // React to changes in note icon or color. - loadResults.getAttributeRows().some(attr => [ "iconClass", "color" ].includes(attr.name ?? "") && this.noteIds.includes(attr.noteId ?? "")) || - // React to attachment change - loadResults.getAttachmentRows().some(att => att.ownerId === this.parentNote.noteId && att.title === "board.json") || - // React to changes in "groupBy" - loadResults.getAttributeRows().some(attr => attr.name === "board:groupBy" && attr.noteId === this.parentNote.noteId); - - if (hasRelevantChanges && this.renderer) { - // Use differential rendering with API refresh - await this.renderer.renderBoard(true); - } - - // Don't trigger full view refresh - let differential renderer handle it - return false; - } - private onSave() { this.viewStorage.store(this.persistentData); } From b029e0d79025c01d845fb25adfc0b5eebc0ed424 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Sep 2025 22:20:17 +0300 Subject: [PATCH 5/5] chore(react/collections/board): add columns without refresh yet --- .../src/widgets/collections/board/index.css | 12 ++++ .../src/widgets/collections/board/index.tsx | 64 ++++++++++++++++++- .../widgets/view_widgets/board_view/api.ts | 15 ----- .../board_view/differential_renderer.ts | 12 ---- .../widgets/view_widgets/board_view/index.ts | 35 ---------- 5 files changed, 75 insertions(+), 63 deletions(-) diff --git a/apps/client/src/widgets/collections/board/index.css b/apps/client/src/widgets/collections/board/index.css index bc941b54e..d5ed0ba5f 100644 --- a/apps/client/src/widgets/collections/board/index.css +++ b/apps/client/src/widgets/collections/board/index.css @@ -247,6 +247,18 @@ font-size: 1.2em; } +.board-add-column input { + background: var(--main-background-color); + border: 1px solid var(--main-text-color); + border-radius: 4px; + padding: 0.5em; + color: var(--main-text-color); + font-family: inherit; + font-size: inherit; + width: 100%; + text-align: center; +} + .board-drag-preview { position: fixed; z-index: 10000; diff --git a/apps/client/src/widgets/collections/board/index.tsx b/apps/client/src/widgets/collections/board/index.tsx index 97cdbf97a..880321e82 100644 --- a/apps/client/src/widgets/collections/board/index.tsx +++ b/apps/client/src/widgets/collections/board/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "preact/hooks"; +import { useCallback, useEffect, useRef, useState } from "preact/hooks"; import { ViewModeProps } from "../interface"; import "./index.css"; import { ColumnMap, getBoardData } from "./data"; @@ -8,6 +8,7 @@ import FBranch from "../../../entities/fbranch"; import Icon from "../../react/Icon"; import { t } from "../../../services/i18n"; import { createNewItem } from "./api"; +import FormTextBox from "../../react/FormTextBox"; export interface BoardViewData { columns?: BoardColumnData[]; @@ -76,6 +77,8 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC parentNote={parentNote} /> ))} + +
) @@ -113,3 +116,62 @@ function Card({ note }: { note: FNote, branch: FBranch, column: string }) {
) } + +function AddNewColumn({ viewConfig, saveConfig }: { viewConfig?: BoardViewData, saveConfig: (data: BoardViewData) => void }) { + const [ isCreatingNewColumn, setIsCreatingNewColumn ] = useState(false); + const columnNameRef = useRef(null); + + const addColumnCallback = useCallback(() => { + setIsCreatingNewColumn(true); + }, []); + + const finishEdit = useCallback((save: boolean) => { + const columnName = columnNameRef.current?.value; + if (!columnName || !save) { + setIsCreatingNewColumn(false); + return; + } + + // Add the new column to persisted data if it doesn't exist + if (!viewConfig) { + viewConfig = {}; + } + + if (!viewConfig.columns) { + viewConfig.columns = []; + } + + const existingColumn = viewConfig.columns.find(col => col.value === columnName); + if (!existingColumn) { + viewConfig.columns.push({ value: columnName }); + saveConfig(viewConfig); + } + }, []); + + return ( +
+ {!isCreatingNewColumn + ? <> + {" "} + {t("board_view.add-column")} + + : <> + finishEdit(true)} + onKeyDown={(e: KeyboardEvent) => { + if (e.key === "Enter") { + e.preventDefault(); + finishEdit(true); + } else if (e.key === "Escape") { + e.preventDefault(); + finishEdit(false); + } + }} + /> + } +
+ ) +} 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 086a6a714..2a6ed25bd 100644 --- a/apps/client/src/widgets/view_widgets/board_view/api.ts +++ b/apps/client/src/widgets/view_widgets/board_view/api.ts @@ -91,21 +91,6 @@ 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; - } - async reorderColumns(newColumnOrder: string[]) { // Update the column order in persisted data if (!this.persistedData.columns) { diff --git a/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts b/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts index ba2d0990e..54658f6ee 100644 --- a/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts +++ b/apps/client/src/widgets/view_widgets/board_view/differential_renderer.ts @@ -95,8 +95,6 @@ export class DifferentialBoardRenderer { const $columnEl = this.createColumn(column, columnItems); this.$container.append($columnEl); } - - this.addAddColumnButton(); } private async differentialRender(oldState: BoardState, newState: BoardState): Promise { @@ -366,16 +364,6 @@ export class DifferentialBoardRenderer { return $noteEl; } - private addAddColumnButton(): void { - if (this.$container.find('.board-add-column').length === 0) { - const $addColumnEl = $("
") - .addClass("board-add-column") - .html(` ${t("board_view.add-column")}`); - - this.$container.append($addColumnEl); - } - } - forceFullRender(): void { this.lastState = null; if (this.updateTimeout) { 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 5b874a5f7..83767445c 100644 --- a/apps/client/src/widgets/view_widgets/board_view/index.ts +++ b/apps/client/src/widgets/view_widgets/board_view/index.ts @@ -246,27 +246,6 @@ 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(); @@ -283,21 +262,7 @@ export default class BoardView extends ViewMode { 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) {