mirror of
https://github.com/zadam/trilium.git
synced 2025-10-20 15:19:01 +02:00
chore(collections/board): clean up old code
This commit is contained in:
parent
8611328a03
commit
f55a39eab6
@ -140,16 +140,6 @@
|
||||
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);
|
||||
|
@ -1,383 +0,0 @@
|
||||
import { BoardDragHandler } 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";
|
||||
import { t } from "../../../services/i18n.js";
|
||||
|
||||
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>;
|
||||
private onRefreshApi: () => Promise<void>;
|
||||
|
||||
constructor(
|
||||
$container: JQuery<HTMLElement>,
|
||||
api: BoardApi,
|
||||
dragHandler: BoardDragHandler,
|
||||
onCreateNewItem: (column: string) => void,
|
||||
parentNote: FNote,
|
||||
viewStorage: ViewModeStorage<BoardData>,
|
||||
onRefreshApi: () => Promise<void>
|
||||
) {
|
||||
this.$container = $container;
|
||||
this.api = api;
|
||||
this.dragHandler = dragHandler;
|
||||
this.onCreateNewItem = onCreateNewItem;
|
||||
this.parentNote = parentNote;
|
||||
this.viewStorage = viewStorage;
|
||||
this.onRefreshApi = onRefreshApi;
|
||||
}
|
||||
|
||||
async renderBoard(refreshApi = false): Promise<void> {
|
||||
// Refresh API data if requested
|
||||
if (refreshApi) {
|
||||
await this.onRefreshApi();
|
||||
}
|
||||
|
||||
// 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> {
|
||||
// Clean up any stray drag indicators before updating
|
||||
this.dragHandler.cleanup();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// Check if column order has changed
|
||||
const orderChanged = !this.arraysEqual(oldState.columnOrder, newState.columnOrder);
|
||||
|
||||
if (orderChanged) {
|
||||
// If order changed, we need to reorder the columns in the DOM
|
||||
this.reorderColumns(newState.columnOrder);
|
||||
}
|
||||
|
||||
// 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 arraysEqual(a: string[], b: string[]): boolean {
|
||||
return a.length === b.length && a.every((val, index) => val === b[index]);
|
||||
}
|
||||
|
||||
private reorderColumns(newOrder: string[]): void {
|
||||
// Get all existing column elements
|
||||
const $columns = this.$container.find('.board-column');
|
||||
const $addColumnButton = this.$container.find('.board-add-column');
|
||||
|
||||
// Create a map of column elements by their data-column attribute
|
||||
const columnElements = new Map<string, JQuery<HTMLElement>>();
|
||||
$columns.each((_, el) => {
|
||||
const $el = $(el);
|
||||
const columnValue = $el.attr('data-column');
|
||||
if (columnValue) {
|
||||
columnElements.set(columnValue, $el);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove all columns from DOM (but keep references)
|
||||
$columns.detach();
|
||||
|
||||
// Re-insert columns in the new order
|
||||
let $insertAfter: JQuery<HTMLElement> | null = null;
|
||||
for (const columnValue of newOrder) {
|
||||
const $columnEl = columnElements.get(columnValue);
|
||||
if ($columnEl) {
|
||||
if ($insertAfter) {
|
||||
$insertAfter.after($columnEl);
|
||||
} else {
|
||||
// Insert at the beginning
|
||||
this.$container.prepend($columnEl);
|
||||
}
|
||||
$insertAfter = $columnEl;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure add column button is at the end
|
||||
if ($addColumnButton.length) {
|
||||
this.$container.append($addColumnButton);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
const $existingCard = $cardContainer.find(`[data-note-id="${noteId}"]`);
|
||||
const isNewCard = !oldCardIds.includes(noteId);
|
||||
|
||||
if ($existingCard.length) {
|
||||
// Check for changes in title, icon, or color
|
||||
const currentTitle = $existingCard.text().trim();
|
||||
const currentIconClass = $existingCard.attr('data-icon-class');
|
||||
const currentColorClass = $existingCard.attr('data-color-class') || '';
|
||||
|
||||
const newIconClass = item.note.getIcon();
|
||||
const newColorClass = item.note.getColorClass() || '';
|
||||
|
||||
let hasChanges = false;
|
||||
|
||||
// Update title if changed
|
||||
if (currentTitle !== item.note.title) {
|
||||
$existingCard.contents().filter(function() {
|
||||
return this.nodeType === 3; // Text nodes
|
||||
}).remove();
|
||||
$existingCard.append(document.createTextNode(item.note.title));
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
// Update icon if changed
|
||||
if (currentIconClass !== newIconClass) {
|
||||
const $icon = $existingCard.find('.icon');
|
||||
$icon.removeClass().addClass('icon').addClass(newIconClass);
|
||||
$existingCard.attr('data-icon-class', newIconClass);
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
// Update color if changed
|
||||
if (currentColorClass !== newColorClass) {
|
||||
// Remove old color class if it exists
|
||||
if (currentColorClass) {
|
||||
$existingCard.removeClass(currentColorClass);
|
||||
}
|
||||
// Add new color class if it exists
|
||||
if (newColorClass) {
|
||||
$existingCard.addClass(newColorClass);
|
||||
}
|
||||
$existingCard.attr('data-color-class', newColorClass);
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
// Add subtle animation if there were changes
|
||||
if (hasChanges) {
|
||||
$existingCard.addClass('card-updated');
|
||||
setTimeout(() => $existingCard.removeClass('card-updated'), 300);
|
||||
}
|
||||
|
||||
// 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> {
|
||||
// Setup column dragging
|
||||
this.dragHandler.setupColumnDrag($columnEl, column);
|
||||
|
||||
// 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 zones for both notes and columns
|
||||
this.dragHandler.setupNoteDropZone($columnEl, column);
|
||||
this.dragHandler.setupColumnDropZone($columnEl);
|
||||
|
||||
// Add "New item" button
|
||||
const $newItemEl = $("<div>")
|
||||
.addClass("board-new-item")
|
||||
.attr("data-column", column)
|
||||
.html(`<span class="icon bx bx-plus"></span> ${}`);
|
||||
|
||||
$columnEl.append($newItemEl);
|
||||
|
||||
return $columnEl;
|
||||
}
|
||||
|
||||
private createCard(note: any, branch: any, column: string): JQuery<HTMLElement> {
|
||||
$noteEl.prepend($iconEl);
|
||||
$noteEl.on("click", () => appContext.triggerCommand("openInPopup", { noteIdOrPath: note.noteId }));
|
||||
|
||||
// Setup drag functionality
|
||||
this.dragHandler.setupNoteDrag($noteEl, note, branch);
|
||||
|
||||
return $noteEl;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
import BoardApi from "./api";
|
||||
import { DragContext } from "./drag_types";
|
||||
import { NoteDragHandler } from "./note_drag_handler";
|
||||
import { ColumnDragHandler } from "./column_drag_handler";
|
||||
|
||||
export class BoardDragHandler {
|
||||
private noteDragHandler: NoteDragHandler;
|
||||
private columnDragHandler: ColumnDragHandler;
|
||||
|
||||
constructor(
|
||||
$container: JQuery<HTMLElement>,
|
||||
api: BoardApi,
|
||||
context: DragContext,
|
||||
) {
|
||||
// Initialize specialized drag handlers
|
||||
this.noteDragHandler = new NoteDragHandler($container, api, context);
|
||||
this.columnDragHandler = new ColumnDragHandler($container, api, context);
|
||||
}
|
||||
|
||||
// Note drag methods - delegate to NoteDragHandler
|
||||
setupNoteDrag($noteEl: JQuery<HTMLElement>, note: any, branch: any) {
|
||||
this.noteDragHandler.setupNoteDrag($noteEl, note, branch);
|
||||
}
|
||||
|
||||
setupNoteDropZone($columnEl: JQuery<HTMLElement>, column: string) {
|
||||
this.noteDragHandler.setupNoteDropZone($columnEl, column);
|
||||
}
|
||||
|
||||
// Column drag methods - delegate to ColumnDragHandler
|
||||
setupColumnDrag($columnEl: JQuery<HTMLElement>, columnValue: string) {
|
||||
this.columnDragHandler.setupColumnDrag($columnEl, columnValue);
|
||||
}
|
||||
|
||||
setupColumnDropZone($columnEl: JQuery<HTMLElement>) {
|
||||
this.columnDragHandler.setupColumnDropZone($columnEl);
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
this.noteDragHandler.cleanup();
|
||||
this.columnDragHandler.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
// Export the drag context type for external use
|
||||
export type { DragContext } from "./drag_types";
|
@ -1,11 +0,0 @@
|
||||
export interface DragContext {
|
||||
draggedNote: any;
|
||||
draggedBranch: any;
|
||||
draggedNoteElement: JQuery<HTMLElement> | null;
|
||||
draggedColumn: string | null;
|
||||
draggedColumnElement: JQuery<HTMLElement> | null;
|
||||
}
|
||||
|
||||
export interface BaseDragHandler {
|
||||
cleanup(): void;
|
||||
}
|
@ -1,322 +0,0 @@
|
||||
import branchService from "../../../services/branches";
|
||||
import BoardApi from "./api";
|
||||
import { DragContext, BaseDragHandler } from "./drag_types";
|
||||
|
||||
export class NoteDragHandler implements BaseDragHandler {
|
||||
private $container: JQuery<HTMLElement>;
|
||||
private api: BoardApi;
|
||||
private context: DragContext;
|
||||
|
||||
constructor(
|
||||
$container: JQuery<HTMLElement>,
|
||||
api: BoardApi,
|
||||
context: DragContext,
|
||||
) {
|
||||
this.$container = $container;
|
||||
this.api = api;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
setupNoteDrag($noteEl: JQuery<HTMLElement>, note: any, branch: any) {
|
||||
$noteEl.attr("draggable", "true");
|
||||
|
||||
// Mouse drag events
|
||||
this.setupMouseDrag($noteEl, note, branch);
|
||||
|
||||
// Touch drag events
|
||||
this.setupTouchDrag($noteEl, note, branch);
|
||||
}
|
||||
|
||||
setupNoteDropZone($columnEl: JQuery<HTMLElement>, column: string) {
|
||||
$columnEl.on("dragover", (e) => {
|
||||
// Only handle note drops when a note is being dragged
|
||||
if (this.context.draggedNote && !this.context.draggedColumn) {
|
||||
e.preventDefault();
|
||||
const originalEvent = e.originalEvent as DragEvent;
|
||||
if (originalEvent.dataTransfer) {
|
||||
originalEvent.dataTransfer.dropEffect = "move";
|
||||
}
|
||||
|
||||
$columnEl.addClass("drag-over");
|
||||
this.showDropIndicator($columnEl, e);
|
||||
}
|
||||
});
|
||||
|
||||
$columnEl.on("dragleave", (e) => {
|
||||
// Only remove drag-over if we're leaving the column entirely
|
||||
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("drag-over");
|
||||
this.cleanupNoteDropIndicators($columnEl);
|
||||
}
|
||||
});
|
||||
|
||||
$columnEl.on("drop", async (e) => {
|
||||
if (this.context.draggedNote && !this.context.draggedColumn) {
|
||||
e.preventDefault();
|
||||
$columnEl.removeClass("drag-over");
|
||||
|
||||
if (this.context.draggedNote && this.context.draggedNoteElement && this.context.draggedBranch) {
|
||||
await this.handleNoteDrop($columnEl, column);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
this.cleanupAllDropIndicators();
|
||||
this.$container.find('.board-column').removeClass('drag-over');
|
||||
}
|
||||
|
||||
private setupMouseDrag($noteEl: JQuery<HTMLElement>, note: any, branch: any) {
|
||||
$noteEl.on("dragstart", (e) => {
|
||||
this.context.draggedNote = note;
|
||||
this.context.draggedBranch = branch;
|
||||
this.context.draggedNoteElement = $noteEl;
|
||||
$noteEl.addClass("dragging");
|
||||
|
||||
// Set drag data
|
||||
const originalEvent = e.originalEvent as DragEvent;
|
||||
if (originalEvent.dataTransfer) {
|
||||
originalEvent.dataTransfer.effectAllowed = "move";
|
||||
originalEvent.dataTransfer.setData("text/plain", note.noteId);
|
||||
}
|
||||
});
|
||||
|
||||
$noteEl.on("dragend", () => {
|
||||
$noteEl.removeClass("dragging");
|
||||
this.context.draggedNote = null;
|
||||
this.context.draggedBranch = null;
|
||||
this.context.draggedNoteElement = null;
|
||||
|
||||
// Clean up all drop indicators properly
|
||||
this.cleanupAllDropIndicators();
|
||||
});
|
||||
}
|
||||
|
||||
private setupTouchDrag($noteEl: JQuery<HTMLElement>, note: any, branch: any) {
|
||||
let isDragging = false;
|
||||
let startY = 0;
|
||||
let startX = 0;
|
||||
let dragThreshold = 10; // Minimum distance to start dragging
|
||||
let $dragPreview: JQuery<HTMLElement> | null = null;
|
||||
|
||||
$noteEl.on("touchstart", (e) => {
|
||||
const touch = (e.originalEvent as TouchEvent).touches[0];
|
||||
startX = touch.clientX;
|
||||
startY = touch.clientY;
|
||||
isDragging = false;
|
||||
$dragPreview = null;
|
||||
});
|
||||
|
||||
$noteEl.on("touchmove", (e) => {
|
||||
e.preventDefault(); // Prevent scrolling
|
||||
const touch = (e.originalEvent as TouchEvent).touches[0];
|
||||
const deltaX = Math.abs(touch.clientX - startX);
|
||||
const deltaY = Math.abs(touch.clientY - startY);
|
||||
|
||||
// Start dragging if we've moved beyond threshold
|
||||
if (!isDragging && (deltaX > dragThreshold || deltaY > dragThreshold)) {
|
||||
isDragging = true;
|
||||
this.context.draggedNote = note;
|
||||
this.context.draggedBranch = branch;
|
||||
this.context.draggedNoteElement = $noteEl;
|
||||
$noteEl.addClass("dragging");
|
||||
|
||||
// Create drag preview
|
||||
$dragPreview = this.createDragPreview($noteEl, touch.clientX, touch.clientY);
|
||||
}
|
||||
|
||||
if (isDragging && $dragPreview) {
|
||||
// Update drag preview position
|
||||
$dragPreview.css({
|
||||
left: touch.clientX - ($dragPreview.outerWidth() || 0) / 2,
|
||||
top: touch.clientY - ($dragPreview.outerHeight() || 0) / 2
|
||||
});
|
||||
|
||||
// Find element under touch point
|
||||
const elementBelow = document.elementFromPoint(touch.clientX, touch.clientY);
|
||||
if (elementBelow) {
|
||||
const $columnEl = $(elementBelow).closest('.board-column');
|
||||
|
||||
if ($columnEl.length > 0) {
|
||||
// Remove drag-over from all columns
|
||||
this.$container.find('.board-column').removeClass('drag-over');
|
||||
$columnEl.addClass('drag-over');
|
||||
|
||||
// Show drop indicator
|
||||
this.showDropIndicatorAtPoint($columnEl, touch.clientY);
|
||||
} else {
|
||||
// Remove all drag indicators if not over a column
|
||||
this.$container.find('.board-column').removeClass('drag-over');
|
||||
this.cleanupAllDropIndicators();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$noteEl.on("touchend", async (e) => {
|
||||
if (isDragging) {
|
||||
const touch = (e.originalEvent as TouchEvent).changedTouches[0];
|
||||
const elementBelow = document.elementFromPoint(touch.clientX, touch.clientY);
|
||||
if (elementBelow) {
|
||||
const $columnEl = $(elementBelow).closest('.board-column');
|
||||
|
||||
if ($columnEl.length > 0) {
|
||||
const column = $columnEl.attr('data-column');
|
||||
if (column && this.context.draggedNote && this.context.draggedNoteElement && this.context.draggedBranch) {
|
||||
await this.handleNoteDrop($columnEl, column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
$noteEl.removeClass("dragging");
|
||||
this.context.draggedNote = null;
|
||||
this.context.draggedBranch = null;
|
||||
this.context.draggedNoteElement = null;
|
||||
this.$container.find('.board-column').removeClass('drag-over');
|
||||
this.cleanupAllDropIndicators();
|
||||
|
||||
// Remove drag preview
|
||||
if ($dragPreview) {
|
||||
$dragPreview.remove();
|
||||
$dragPreview = null;
|
||||
}
|
||||
}
|
||||
isDragging = false;
|
||||
});
|
||||
}
|
||||
|
||||
private createDragPreview($noteEl: JQuery<HTMLElement>, x: number, y: number): JQuery<HTMLElement> {
|
||||
// Clone the note element for the preview
|
||||
const $preview = $noteEl.clone();
|
||||
|
||||
$preview
|
||||
.addClass('board-drag-preview')
|
||||
.css({
|
||||
position: 'fixed',
|
||||
left: x - ($noteEl.outerWidth() || 0) / 2,
|
||||
top: y - ($noteEl.outerHeight() || 0) / 2,
|
||||
pointerEvents: 'none',
|
||||
zIndex: 10000
|
||||
})
|
||||
.appendTo('body');
|
||||
|
||||
return $preview;
|
||||
}
|
||||
|
||||
private showDropIndicator($columnEl: JQuery<HTMLElement>, e: JQuery.DragOverEvent) {
|
||||
const originalEvent = e.originalEvent as DragEvent;
|
||||
const mouseY = originalEvent.clientY;
|
||||
this.showDropIndicatorAtY($columnEl, mouseY);
|
||||
}
|
||||
|
||||
private showDropIndicatorAtPoint($columnEl: JQuery<HTMLElement>, touchY: number) {
|
||||
this.showDropIndicatorAtY($columnEl, touchY);
|
||||
}
|
||||
|
||||
private showDropIndicatorAtY($columnEl: JQuery<HTMLElement>, y: number) {
|
||||
const columnRect = $columnEl[0].getBoundingClientRect();
|
||||
const relativeY = y - columnRect.top;
|
||||
|
||||
// Clean up any existing drop indicators in this column first
|
||||
this.cleanupNoteDropIndicators($columnEl);
|
||||
|
||||
// Create a new drop indicator
|
||||
const $dropIndicator = $("<div>").addClass("board-drop-indicator");
|
||||
|
||||
// Find the best position to insert the note
|
||||
const $notes = this.context.draggedNoteElement ?
|
||||
$columnEl.find(".board-note").not(this.context.draggedNoteElement) :
|
||||
$columnEl.find(".board-note");
|
||||
let insertAfterElement: HTMLElement | null = null;
|
||||
|
||||
$notes.each((_, noteEl) => {
|
||||
const noteRect = noteEl.getBoundingClientRect();
|
||||
const noteMiddle = noteRect.top + noteRect.height / 2 - columnRect.top;
|
||||
|
||||
if (relativeY > noteMiddle) {
|
||||
insertAfterElement = noteEl;
|
||||
}
|
||||
});
|
||||
|
||||
// Position the drop indicator
|
||||
if (insertAfterElement) {
|
||||
$(insertAfterElement).after($dropIndicator);
|
||||
} else {
|
||||
// Insert at the beginning (after the header)
|
||||
const $header = $columnEl.find("h3");
|
||||
$header.after($dropIndicator);
|
||||
}
|
||||
|
||||
$dropIndicator.addClass("show");
|
||||
}
|
||||
|
||||
private async handleNoteDrop($columnEl: JQuery<HTMLElement>, column: string) {
|
||||
const draggedNoteElement = this.context.draggedNoteElement;
|
||||
const draggedNote = this.context.draggedNote;
|
||||
const draggedBranch = this.context.draggedBranch;
|
||||
|
||||
if (draggedNote && draggedNoteElement && draggedBranch) {
|
||||
const currentColumn = draggedNoteElement.attr("data-current-column");
|
||||
|
||||
// Capture drop indicator position BEFORE removing it
|
||||
const dropIndicator = $columnEl.find(".board-drop-indicator.show");
|
||||
let targetBranchId: string | null = null;
|
||||
let moveType: "before" | "after" | null = null;
|
||||
|
||||
if (dropIndicator.length > 0) {
|
||||
// Find the note element that the drop indicator is positioned relative to
|
||||
const nextNote = dropIndicator.next(".board-note");
|
||||
const prevNote = dropIndicator.prev(".board-note");
|
||||
|
||||
if (nextNote.length > 0) {
|
||||
targetBranchId = nextNote.attr("data-branch-id") || null;
|
||||
moveType = "before";
|
||||
} else if (prevNote.length > 0) {
|
||||
targetBranchId = prevNote.attr("data-branch-id") || null;
|
||||
moveType = "after";
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Handle column change
|
||||
if (currentColumn !== column) {
|
||||
await this.api.changeColumn(draggedNote.noteId, column);
|
||||
}
|
||||
|
||||
// Handle position change (works for both same column and different column moves)
|
||||
if (targetBranchId && moveType) {
|
||||
if (moveType === "before") {
|
||||
await branchService.moveBeforeBranch([draggedBranch.branchId], targetBranchId);
|
||||
} else if (moveType === "after") {
|
||||
await branchService.moveAfterBranch([draggedBranch.branchId], targetBranchId);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the data attributes
|
||||
draggedNoteElement.attr("data-current-column", column);
|
||||
} catch (error) {
|
||||
console.error("Failed to update note position:", error);
|
||||
} finally {
|
||||
// Always clean up drop indicators after drop operation
|
||||
this.cleanupAllDropIndicators();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private cleanupAllDropIndicators() {
|
||||
// Remove all drop indicators from the DOM to prevent layout issues
|
||||
this.$container.find(".board-drop-indicator").remove();
|
||||
}
|
||||
|
||||
private cleanupNoteDropIndicators($columnEl: JQuery<HTMLElement>) {
|
||||
// Remove note drop indicators from a specific column
|
||||
$columnEl.find(".board-drop-indicator").remove();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user