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 11cfbbd57..8bbe443a2 100644
--- a/apps/client/src/widgets/view_widgets/board_view/index.ts
+++ b/apps/client/src/widgets/view_widgets/board_view/index.ts
@@ -1,6 +1,7 @@
import { setupHorizontalScrollViaWheel } from "../../widget_utils";
import ViewMode, { ViewModeArgs } from "../view_mode";
import { getBoardData } from "./data";
+import attributeService from "../../../services/attributes";
const TPL = /*html*/`
@@ -22,10 +23,24 @@ const TPL = /*html*/`
.board-view-container .board-column {
width: 250px;
flex-shrink: 0;
+ min-height: 200px;
+ border: 2px solid transparent;
+ border-radius: 8px;
+ padding: 0.5em;
+ background-color: var(--accented-background-color);
+ transition: border-color 0.2s ease;
+ }
+
+ .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-bottom: 0.5em;
+ border-bottom: 1px solid var(--main-border-color);
}
.board-view-container .board-note {
@@ -33,11 +48,41 @@ const TPL = /*html*/`
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;
+ }
+
+ .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 .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;
+ }
@@ -52,6 +97,8 @@ export default class BoardView extends ViewMode
{
private $root: JQuery;
private $container: JQuery;
+ private draggedNote: any = null;
+ private draggedNoteElement: JQuery | null = null;
constructor(args: ViewModeArgs) {
super(args, "board");
@@ -65,7 +112,7 @@ export default class BoardView extends ViewMode {
async renderList(): Promise | undefined> {
this.$container.empty();
- this.renderBoard(this.$container[0]);
+ await this.renderBoard(this.$container[0]);
return this.$root;
}
@@ -81,8 +128,12 @@ export default class BoardView extends ViewMode {
const $columnEl = $("")
.addClass("board-column")
+ .attr("data-column", column)
.append($("
").text(column));
+ // Setup drop zone for the column
+ this.setupColumnDropZone($columnEl, column);
+
for (const note of columnNotes) {
const $iconEl = $("")
.addClass("icon")
@@ -90,8 +141,15 @@ export default class BoardView extends ViewMode {
const $noteEl = $("")
.addClass("board-note")
- .text(note.title); // Assuming FNote has a title property
+ .attr("data-note-id", note.noteId)
+ .attr("data-current-column", column)
+ .text(note.title);
+
$noteEl.prepend($iconEl);
+
+ // Setup drag functionality for the note
+ this.setupNoteDrag($noteEl, note);
+
$columnEl.append($noteEl);
}
@@ -99,4 +157,132 @@ export default class BoardView extends ViewMode
{
}
}
+ private setupNoteDrag($noteEl: JQuery, note: any) {
+ $noteEl.attr("draggable", "true");
+
+ $noteEl.on("dragstart", (e) => {
+ this.draggedNote = note;
+ this.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.draggedNote = null;
+ this.draggedNoteElement = null;
+
+ // Remove all drop indicators
+ this.$container.find(".board-drop-indicator").removeClass("show");
+ });
+ }
+
+ private setupColumnDropZone($columnEl: JQuery, column: string) {
+ $columnEl.on("dragover", (e) => {
+ e.preventDefault();
+ const originalEvent = e.originalEvent as DragEvent;
+ if (originalEvent.dataTransfer) {
+ originalEvent.dataTransfer.dropEffect = "move";
+ }
+
+ if (this.draggedNote) {
+ $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");
+ $columnEl.find(".board-drop-indicator").removeClass("show");
+ }
+ });
+
+ $columnEl.on("drop", async (e) => {
+ e.preventDefault();
+ $columnEl.removeClass("drag-over");
+ $columnEl.find(".board-drop-indicator").removeClass("show");
+
+ if (this.draggedNote && this.draggedNoteElement) {
+ const currentColumn = this.draggedNoteElement.attr("data-current-column");
+
+ if (currentColumn !== column) {
+ try {
+ // Update the note's status label
+ await attributeService.setLabel(this.draggedNote.noteId, "status", column);
+
+ // Move the note element to the new column
+ const dropIndicator = $columnEl.find(".board-drop-indicator.show");
+ if (dropIndicator.length > 0) {
+ dropIndicator.after(this.draggedNoteElement);
+ } else {
+ $columnEl.append(this.draggedNoteElement);
+ }
+
+ // Update the data attribute
+ this.draggedNoteElement.attr("data-current-column", column);
+
+ // Show success feedback (optional)
+ console.log(`Moved note "${this.draggedNote.title}" from "${currentColumn}" to "${column}"`);
+ } catch (error) {
+ console.error("Failed to update note status:", error);
+ // Optionally show user-facing error message
+ }
+ }
+ }
+ });
+ }
+
+ private showDropIndicator($columnEl: JQuery, e: JQuery.DragOverEvent) {
+ const originalEvent = e.originalEvent as DragEvent;
+ const mouseY = originalEvent.clientY;
+ const columnRect = $columnEl[0].getBoundingClientRect();
+ const relativeY = mouseY - columnRect.top;
+
+ // Find existing drop indicator or create one
+ let $dropIndicator = $columnEl.find(".board-drop-indicator");
+ if ($dropIndicator.length === 0) {
+ $dropIndicator = $("").addClass("board-drop-indicator");
+ $columnEl.append($dropIndicator);
+ }
+
+ // Find the best position to insert the note
+ const $notes = this.draggedNoteElement ?
+ $columnEl.find(".board-note").not(this.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");
+ }
+
}