mirror of
https://github.com/zadam/trilium.git
synced 2025-10-21 07:38:53 +02:00
feat(views/board): set up differential renderer
This commit is contained in:
parent
4826898c55
commit
d98be19c9a
@ -0,0 +1,327 @@
|
|||||||
|
import { BoardDragHandler, DragContext } from "./drag_handler";
|
||||||
|
import BoardApi from "./api";
|
||||||
|
import appContext from "../../../components/app_context";
|
||||||
|
import FNote from "../../../entities/fnote";
|
||||||
|
import ViewModeStorage from "../view_mode_storage";
|
||||||
|
import { BoardData } from "./config";
|
||||||
|
|
||||||
|
export interface BoardState {
|
||||||
|
columns: { [key: string]: { note: any; branch: any }[] };
|
||||||
|
columnOrder: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DifferentialBoardRenderer {
|
||||||
|
private $container: JQuery<HTMLElement>;
|
||||||
|
private api: BoardApi;
|
||||||
|
private dragHandler: BoardDragHandler;
|
||||||
|
private lastState: BoardState | null = null;
|
||||||
|
private onCreateNewItem: (column: string) => void;
|
||||||
|
private updateTimeout: number | null = null;
|
||||||
|
private pendingUpdate = false;
|
||||||
|
private parentNote: FNote;
|
||||||
|
private viewStorage: ViewModeStorage<BoardData>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
$container: JQuery<HTMLElement>,
|
||||||
|
api: BoardApi,
|
||||||
|
dragHandler: BoardDragHandler,
|
||||||
|
onCreateNewItem: (column: string) => void,
|
||||||
|
parentNote: FNote,
|
||||||
|
viewStorage: ViewModeStorage<BoardData>
|
||||||
|
) {
|
||||||
|
this.$container = $container;
|
||||||
|
this.api = api;
|
||||||
|
this.dragHandler = dragHandler;
|
||||||
|
this.onCreateNewItem = onCreateNewItem;
|
||||||
|
this.parentNote = parentNote;
|
||||||
|
this.viewStorage = viewStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
async renderBoard(refreshApi: boolean = false): Promise<void> {
|
||||||
|
// Refresh API data if requested
|
||||||
|
if (refreshApi) {
|
||||||
|
this.api = await BoardApi.build(this.parentNote, this.viewStorage);
|
||||||
|
this.dragHandler.updateApi(this.api);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debounce rapid updates
|
||||||
|
if (this.updateTimeout) {
|
||||||
|
clearTimeout(this.updateTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateTimeout = window.setTimeout(async () => {
|
||||||
|
await this.performUpdate();
|
||||||
|
this.updateTimeout = null;
|
||||||
|
}, 16); // ~60fps
|
||||||
|
}
|
||||||
|
|
||||||
|
private async performUpdate(): Promise<void> {
|
||||||
|
const currentState = this.getCurrentState();
|
||||||
|
|
||||||
|
if (!this.lastState) {
|
||||||
|
// First render - do full render
|
||||||
|
await this.fullRender(currentState);
|
||||||
|
} else {
|
||||||
|
// Differential render - only update what changed
|
||||||
|
await this.differentialRender(this.lastState, currentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastState = currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCurrentState(): BoardState {
|
||||||
|
const columns: { [key: string]: { note: any; branch: any }[] } = {};
|
||||||
|
const columnOrder: string[] = [];
|
||||||
|
|
||||||
|
for (const column of this.api.columns) {
|
||||||
|
columnOrder.push(column);
|
||||||
|
columns[column] = this.api.getColumn(column) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return { columns, columnOrder };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fullRender(state: BoardState): Promise<void> {
|
||||||
|
this.$container.empty();
|
||||||
|
|
||||||
|
for (const column of state.columnOrder) {
|
||||||
|
const columnItems = state.columns[column];
|
||||||
|
const $columnEl = this.createColumn(column, columnItems);
|
||||||
|
this.$container.append($columnEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addAddColumnButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async differentialRender(oldState: BoardState, newState: BoardState): Promise<void> {
|
||||||
|
// Store scroll positions before making changes
|
||||||
|
const scrollPositions = this.saveScrollPositions();
|
||||||
|
|
||||||
|
// Handle column additions/removals
|
||||||
|
this.updateColumns(oldState, newState);
|
||||||
|
|
||||||
|
// Handle card updates within existing columns
|
||||||
|
for (const column of newState.columnOrder) {
|
||||||
|
this.updateColumnCards(column, oldState.columns[column] || [], newState.columns[column]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore scroll positions
|
||||||
|
this.restoreScrollPositions(scrollPositions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveScrollPositions(): { [column: string]: number } {
|
||||||
|
const positions: { [column: string]: number } = {};
|
||||||
|
this.$container.find('.board-column').each((_, el) => {
|
||||||
|
const column = $(el).attr('data-column');
|
||||||
|
if (column) {
|
||||||
|
positions[column] = el.scrollTop;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private restoreScrollPositions(positions: { [column: string]: number }): void {
|
||||||
|
this.$container.find('.board-column').each((_, el) => {
|
||||||
|
const column = $(el).attr('data-column');
|
||||||
|
if (column && positions[column] !== undefined) {
|
||||||
|
el.scrollTop = positions[column];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateColumns(oldState: BoardState, newState: BoardState): void {
|
||||||
|
// Remove columns that no longer exist
|
||||||
|
for (const oldColumn of oldState.columnOrder) {
|
||||||
|
if (!newState.columnOrder.includes(oldColumn)) {
|
||||||
|
this.$container.find(`[data-column="${oldColumn}"]`).remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new columns
|
||||||
|
for (const newColumn of newState.columnOrder) {
|
||||||
|
if (!oldState.columnOrder.includes(newColumn)) {
|
||||||
|
const columnItems = newState.columns[newColumn];
|
||||||
|
const $columnEl = this.createColumn(newColumn, columnItems);
|
||||||
|
|
||||||
|
// Insert at correct position
|
||||||
|
const insertIndex = newState.columnOrder.indexOf(newColumn);
|
||||||
|
const $existingColumns = this.$container.find('.board-column');
|
||||||
|
|
||||||
|
if (insertIndex === 0) {
|
||||||
|
this.$container.prepend($columnEl);
|
||||||
|
} else if (insertIndex >= $existingColumns.length) {
|
||||||
|
this.$container.find('.board-add-column').before($columnEl);
|
||||||
|
} else {
|
||||||
|
$($existingColumns[insertIndex - 1]).after($columnEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateColumnCards(column: string, oldCards: { note: any; branch: any }[], newCards: { note: any; branch: any }[]): void {
|
||||||
|
const $column = this.$container.find(`[data-column="${column}"]`);
|
||||||
|
if (!$column.length) return;
|
||||||
|
|
||||||
|
const $cardContainer = $column;
|
||||||
|
const oldCardIds = oldCards.map(item => item.note.noteId);
|
||||||
|
const newCardIds = newCards.map(item => item.note.noteId);
|
||||||
|
|
||||||
|
// Remove cards that no longer exist
|
||||||
|
$cardContainer.find('.board-note').each((_, el) => {
|
||||||
|
const noteId = $(el).attr('data-note-id');
|
||||||
|
if (noteId && !newCardIds.includes(noteId)) {
|
||||||
|
$(el).addClass('fade-out');
|
||||||
|
setTimeout(() => $(el).remove(), 150);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add or update cards
|
||||||
|
for (let i = 0; i < newCards.length; i++) {
|
||||||
|
const item = newCards[i];
|
||||||
|
const noteId = item.note.noteId;
|
||||||
|
let $existingCard = $cardContainer.find(`[data-note-id="${noteId}"]`);
|
||||||
|
|
||||||
|
if ($existingCard.length) {
|
||||||
|
// Update existing card if title changed
|
||||||
|
const currentTitle = $existingCard.text().trim();
|
||||||
|
if (currentTitle !== item.note.title) {
|
||||||
|
$existingCard.contents().filter(function() {
|
||||||
|
return this.nodeType === 3; // Text nodes
|
||||||
|
}).remove();
|
||||||
|
$existingCard.append(item.note.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure card is in correct position
|
||||||
|
this.ensureCardPosition($existingCard, i, $cardContainer);
|
||||||
|
} else {
|
||||||
|
// Create new card
|
||||||
|
const $newCard = this.createCard(item.note, item.branch, column);
|
||||||
|
$newCard.addClass('fade-in').css('opacity', '0');
|
||||||
|
|
||||||
|
// Insert at correct position
|
||||||
|
if (i === 0) {
|
||||||
|
$cardContainer.find('h3').after($newCard);
|
||||||
|
} else {
|
||||||
|
const $prevCard = $cardContainer.find('.board-note').eq(i - 1);
|
||||||
|
if ($prevCard.length) {
|
||||||
|
$prevCard.after($newCard);
|
||||||
|
} else {
|
||||||
|
$cardContainer.find('.board-new-item').before($newCard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger fade in animation
|
||||||
|
setTimeout(() => $newCard.css('opacity', '1'), 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureCardPosition($card: JQuery<HTMLElement>, targetIndex: number, $container: JQuery<HTMLElement>): void {
|
||||||
|
const $allCards = $container.find('.board-note');
|
||||||
|
const currentIndex = $allCards.index($card);
|
||||||
|
|
||||||
|
if (currentIndex !== targetIndex) {
|
||||||
|
if (targetIndex === 0) {
|
||||||
|
$container.find('h3').after($card);
|
||||||
|
} else {
|
||||||
|
const $targetPrev = $allCards.eq(targetIndex - 1);
|
||||||
|
if ($targetPrev.length) {
|
||||||
|
$targetPrev.after($card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createColumn(column: string, columnItems: { note: any; branch: any }[]): JQuery<HTMLElement> {
|
||||||
|
const $columnEl = $("<div>")
|
||||||
|
.addClass("board-column")
|
||||||
|
.attr("data-column", column);
|
||||||
|
|
||||||
|
// Create header
|
||||||
|
const $titleEl = $("<h3>").attr("data-column-value", column);
|
||||||
|
const $titleText = $("<span>").text(column);
|
||||||
|
const $editIcon = $("<span>")
|
||||||
|
.addClass("edit-icon icon bx bx-edit-alt")
|
||||||
|
.attr("title", "Click to edit column title");
|
||||||
|
$titleEl.append($titleText, $editIcon);
|
||||||
|
$columnEl.append($titleEl);
|
||||||
|
|
||||||
|
// Handle wheel events for scrolling
|
||||||
|
$columnEl.on("wheel", (event) => {
|
||||||
|
const el = $columnEl[0];
|
||||||
|
const needsScroll = el.scrollHeight > el.clientHeight;
|
||||||
|
if (needsScroll) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup drop zone
|
||||||
|
this.dragHandler.setupColumnDropZone($columnEl, column);
|
||||||
|
|
||||||
|
// 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 = $("<div>")
|
||||||
|
.addClass("board-new-item")
|
||||||
|
.attr("data-column", column)
|
||||||
|
.html('<span class="icon bx bx-plus"></span>New item');
|
||||||
|
|
||||||
|
$newItemEl.on("click", () => this.onCreateNewItem(column));
|
||||||
|
$columnEl.append($newItemEl);
|
||||||
|
|
||||||
|
return $columnEl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createCard(note: any, branch: any, column: string): JQuery<HTMLElement> {
|
||||||
|
const $iconEl = $("<span>")
|
||||||
|
.addClass("icon")
|
||||||
|
.addClass(note.getIcon());
|
||||||
|
|
||||||
|
const $noteEl = $("<div>")
|
||||||
|
.addClass("board-note")
|
||||||
|
.attr("data-note-id", note.noteId)
|
||||||
|
.attr("data-branch-id", branch.branchId)
|
||||||
|
.attr("data-current-column", column)
|
||||||
|
.text(note.title);
|
||||||
|
|
||||||
|
$noteEl.prepend($iconEl);
|
||||||
|
$noteEl.on("click", () => appContext.triggerCommand("openInPopup", { noteIdOrPath: note.noteId }));
|
||||||
|
|
||||||
|
// Setup drag functionality
|
||||||
|
this.dragHandler.setupNoteDrag($noteEl, note, branch);
|
||||||
|
|
||||||
|
return $noteEl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addAddColumnButton(): void {
|
||||||
|
if (this.$container.find('.board-add-column').length === 0) {
|
||||||
|
const $addColumnEl = $("<div>")
|
||||||
|
.addClass("board-add-column")
|
||||||
|
.html('<span class="icon bx bx-plus"></span>Add Column');
|
||||||
|
|
||||||
|
this.$container.append($addColumnEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forceFullRender(): void {
|
||||||
|
this.lastState = null;
|
||||||
|
if (this.updateTimeout) {
|
||||||
|
clearTimeout(this.updateTimeout);
|
||||||
|
this.updateTimeout = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async flushPendingUpdates(): Promise<void> {
|
||||||
|
if (this.updateTimeout) {
|
||||||
|
clearTimeout(this.updateTimeout);
|
||||||
|
this.updateTimeout = null;
|
||||||
|
await this.performUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -35,6 +35,10 @@ export class BoardDragHandler {
|
|||||||
this.setupTouchDrag($noteEl, note, branch);
|
this.setupTouchDrag($noteEl, note, branch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateApi(newApi: BoardApi) {
|
||||||
|
this.api = newApi;
|
||||||
|
}
|
||||||
|
|
||||||
private setupMouseDrag($noteEl: JQuery<HTMLElement>, note: any, branch: any) {
|
private setupMouseDrag($noteEl: JQuery<HTMLElement>, note: any, branch: any) {
|
||||||
$noteEl.on("dragstart", (e) => {
|
$noteEl.on("dragstart", (e) => {
|
||||||
this.context.draggedNote = note;
|
this.context.draggedNote = note;
|
||||||
|
@ -8,6 +8,7 @@ import SpacedUpdate from "../../../services/spaced_update";
|
|||||||
import { setupContextMenu } from "./context_menu";
|
import { setupContextMenu } from "./context_menu";
|
||||||
import BoardApi from "./api";
|
import BoardApi from "./api";
|
||||||
import { BoardDragHandler, DragContext } from "./drag_handler";
|
import { BoardDragHandler, DragContext } from "./drag_handler";
|
||||||
|
import { DifferentialBoardRenderer } from "./differential_renderer";
|
||||||
|
|
||||||
const TPL = /*html*/`
|
const TPL = /*html*/`
|
||||||
<div class="board-view">
|
<div class="board-view">
|
||||||
@ -104,7 +105,26 @@ const TPL = /*html*/`
|
|||||||
position: relative;
|
position: relative;
|
||||||
background-color: var(--main-background-color);
|
background-color: var(--main-background-color);
|
||||||
border: 1px solid var(--main-border-color);
|
border: 1px solid var(--main-border-color);
|
||||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
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:hover {
|
.board-view-container .board-note:hover {
|
||||||
@ -223,6 +243,7 @@ export default class BoardView extends ViewMode<BoardData> {
|
|||||||
private persistentData: BoardData;
|
private persistentData: BoardData;
|
||||||
private api?: BoardApi;
|
private api?: BoardApi;
|
||||||
private dragHandler?: BoardDragHandler;
|
private dragHandler?: BoardDragHandler;
|
||||||
|
private renderer?: DifferentialBoardRenderer;
|
||||||
|
|
||||||
constructor(args: ViewModeArgs) {
|
constructor(args: ViewModeArgs) {
|
||||||
super(args, "board");
|
super(args, "board");
|
||||||
@ -244,13 +265,17 @@ export default class BoardView extends ViewMode<BoardData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async renderList(): Promise<JQuery<HTMLElement> | undefined> {
|
async renderList(): Promise<JQuery<HTMLElement> | undefined> {
|
||||||
this.$container.empty();
|
if (!this.renderer) {
|
||||||
await this.renderBoard(this.$container[0]);
|
// First time setup
|
||||||
|
this.$container.empty();
|
||||||
|
await this.initializeRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.renderer!.renderBoard();
|
||||||
return this.$root;
|
return this.$root;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async renderBoard(el: HTMLElement) {
|
private async initializeRenderer() {
|
||||||
this.api = await BoardApi.build(this.parentNote, this.viewStorage);
|
this.api = await BoardApi.build(this.parentNote, this.viewStorage);
|
||||||
this.dragHandler = new BoardDragHandler(
|
this.dragHandler = new BoardDragHandler(
|
||||||
this.$container,
|
this.$container,
|
||||||
@ -259,100 +284,41 @@ export default class BoardView extends ViewMode<BoardData> {
|
|||||||
async () => { await this.renderList(); }
|
async () => { await this.renderList(); }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.renderer = new DifferentialBoardRenderer(
|
||||||
|
this.$container,
|
||||||
|
this.api,
|
||||||
|
this.dragHandler,
|
||||||
|
(column: string) => this.createNewItem(column),
|
||||||
|
this.parentNote,
|
||||||
|
this.viewStorage
|
||||||
|
);
|
||||||
|
|
||||||
setupContextMenu({
|
setupContextMenu({
|
||||||
$container: this.$container,
|
$container: this.$container,
|
||||||
api: this.api
|
api: this.api
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const column of this.api.columns) {
|
// Setup column title editing and add column functionality
|
||||||
const columnItems = this.api.getColumn(column);
|
this.setupBoardInteractions();
|
||||||
if (!columnItems) {
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the column data to get custom title
|
private setupBoardInteractions() {
|
||||||
const columnTitle = column;
|
// Handle column title editing
|
||||||
|
this.$container.on('click', 'h3[data-column-value]', (e) => {
|
||||||
const $columnEl = $("<div>")
|
|
||||||
.addClass("board-column")
|
|
||||||
.attr("data-column", column);
|
|
||||||
|
|
||||||
const $titleEl = $("<h3>")
|
|
||||||
.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) => {
|
|
||||||
const el = $columnEl[0];
|
|
||||||
const needsScroll = el.scrollHeight > el.clientHeight;
|
|
||||||
if (needsScroll) {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup drop zone for the column
|
|
||||||
this.dragHandler!.setupColumnDropZone($columnEl, column);
|
|
||||||
|
|
||||||
for (const item of columnItems) {
|
|
||||||
const note = item.note;
|
|
||||||
const branch = item.branch;
|
|
||||||
if (!note) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const $iconEl = $("<span>")
|
|
||||||
.addClass("icon")
|
|
||||||
.addClass(note.getIcon());
|
|
||||||
|
|
||||||
const $noteEl = $("<div>")
|
|
||||||
.addClass("board-note")
|
|
||||||
.attr("data-note-id", note.noteId)
|
|
||||||
.attr("data-branch-id", branch.branchId)
|
|
||||||
.attr("data-current-column", column)
|
|
||||||
.text(note.title);
|
|
||||||
|
|
||||||
$noteEl.prepend($iconEl);
|
|
||||||
$noteEl.on("click", () => appContext.triggerCommand("openInPopup", { noteIdOrPath: note.noteId }));
|
|
||||||
|
|
||||||
// Setup drag functionality for the note
|
|
||||||
this.dragHandler!.setupNoteDrag($noteEl, note, branch);
|
|
||||||
|
|
||||||
$columnEl.append($noteEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add "New item" link at the bottom of the column
|
|
||||||
const $newItemEl = $("<div>")
|
|
||||||
.addClass("board-new-item")
|
|
||||||
.attr("data-column", column)
|
|
||||||
.html('<span class="icon bx bx-plus"></span>New item');
|
|
||||||
|
|
||||||
$newItemEl.on("click", () => {
|
|
||||||
this.createNewItem(column);
|
|
||||||
});
|
|
||||||
|
|
||||||
$columnEl.append($newItemEl);
|
|
||||||
|
|
||||||
$(el).append($columnEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add "Add Column" button at the end
|
|
||||||
const $addColumnEl = $("<div>")
|
|
||||||
.addClass("board-add-column")
|
|
||||||
.html('<span class="icon bx bx-plus"></span>Add Column');
|
|
||||||
|
|
||||||
$addColumnEl.on("click", (e) => {
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.startCreatingNewColumn($addColumnEl);
|
const $titleEl = $(e.currentTarget);
|
||||||
|
const columnValue = $titleEl.attr('data-column-value');
|
||||||
|
if (columnValue) {
|
||||||
|
const columnItems = this.api?.getColumn(columnValue) || [];
|
||||||
|
this.startEditingColumnTitle($titleEl, columnValue, columnItems);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$(el).append($addColumnEl);
|
// Handle add column button
|
||||||
|
this.$container.on('click', '.board-add-column', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.startCreatingNewColumn($(e.currentTarget));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTitleStructure(title: string): { $titleText: JQuery<HTMLElement>; $editIcon: JQuery<HTMLElement> } {
|
private createTitleStructure(title: string): { $titleText: JQuery<HTMLElement>; $editIcon: JQuery<HTMLElement> } {
|
||||||
@ -364,13 +330,6 @@ export default class BoardView extends ViewMode<BoardData> {
|
|||||||
return { $titleText, $editIcon };
|
return { $titleText, $editIcon };
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupColumnTitleEdit($titleEl: JQuery<HTMLElement>, columnValue: string, columnItems: { branch: any; note: any; }[]) {
|
|
||||||
$titleEl.on("click", (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
this.startEditingColumnTitle($titleEl, columnValue, columnItems);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private startEditingColumnTitle($titleEl: JQuery<HTMLElement>, columnValue: string, columnItems: { branch: any; note: any; }[]) {
|
private startEditingColumnTitle($titleEl: JQuery<HTMLElement>, columnValue: string, columnItems: { branch: any; note: any; }[]) {
|
||||||
if ($titleEl.hasClass("editing")) {
|
if ($titleEl.hasClass("editing")) {
|
||||||
return; // Already editing
|
return; // Already editing
|
||||||
@ -461,6 +420,11 @@ export default class BoardView extends ViewMode<BoardData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forceFullRefresh() {
|
||||||
|
this.renderer?.forceFullRender();
|
||||||
|
return this.renderList();
|
||||||
|
}
|
||||||
|
|
||||||
private startCreatingNewColumn($addColumnEl: JQuery<HTMLElement>) {
|
private startCreatingNewColumn($addColumnEl: JQuery<HTMLElement>) {
|
||||||
if ($addColumnEl.hasClass("editing")) {
|
if ($addColumnEl.hasClass("editing")) {
|
||||||
return; // Already editing
|
return; // Already editing
|
||||||
@ -535,26 +499,23 @@ export default class BoardView extends ViewMode<BoardData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">) {
|
async onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">) {
|
||||||
// React to changes in "status" attribute for notes in this board
|
// Check if any changes affect our board
|
||||||
if (loadResults.getAttributeRows().some(attr => attr.name === "status" && this.noteIds.includes(attr.noteId!))) {
|
const hasRelevantChanges =
|
||||||
return true;
|
// React to changes in "status" attribute for notes in this board
|
||||||
}
|
loadResults.getAttributeRows().some(attr => attr.name === "status" && this.noteIds.includes(attr.noteId!)) ||
|
||||||
|
// React to changes in note title
|
||||||
// React to changes in note title.
|
loadResults.getNoteIds().some(noteId => this.noteIds.includes(noteId)) ||
|
||||||
if (loadResults.getNoteIds().some(noteId => this.noteIds.includes(noteId))) {
|
// React to changes in branches for subchildren (e.g., moved, added, or removed notes)
|
||||||
return true;
|
loadResults.getBranchRows().some(branch => this.noteIds.includes(branch.noteId!)) ||
|
||||||
}
|
// React to attachment change
|
||||||
|
loadResults.getAttachmentRows().some(att => att.ownerId === this.parentNote.noteId && att.title === "board.json");
|
||||||
// React to changes in branches for subchildren (e.g., moved, added, or removed notes)
|
|
||||||
if (loadResults.getBranchRows().some(branch => this.noteIds.includes(branch.noteId!))) {
|
if (hasRelevantChanges && this.renderer) {
|
||||||
return true;
|
// Use differential rendering with API refresh
|
||||||
}
|
await this.renderer.renderBoard(true);
|
||||||
|
|
||||||
// React to attachment change.
|
|
||||||
if (loadResults.getAttachmentRows().some(att => att.ownerId === this.parentNote.noteId && att.title === "board.json")) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't trigger full view refresh - let differential renderer handle it
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user