Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/note-boards/src/board-card.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { MarkdownEditor } from "@notes/editor";
import { PopupMenu, useDraggable, usePreventChildDrag } from "@notes/ui";
import { DragEvent, useEffect, useRef, useState } from "react";
import type { BoardColumn, RichCard } from "./board-format";
import type { IBoardColumn, RichCard } from "./board-format";

const LABEL_COLORS = ["#e2f0fb", "#fde8d8", "#d9f2e8", "#f5e6fb", "#fef9c3"];

Expand All @@ -26,7 +26,7 @@ export function BoardCard({
onDragEnter: (cardId: string, columnName: string) => void;
handleDeleteCard: (cardId: string) => void;
updateCardState: (card: RichCard) => void;
column: BoardColumn;
column: IBoardColumn;
onOpenModal: (card: RichCard) => void;
isColumnDragActive?: boolean;
}) {
Expand Down
182 changes: 182 additions & 0 deletions packages/note-boards/src/board-column.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { useDraggable } from "@notes/ui";
import { Fragment, DragEvent } from "react";
import { BoardCard } from "./board-card";
import type { IBoardColumn, RichCard } from "./board-format";

export function BoardColumn({
column,
cards,
onRenameColumn,
onDeleteColumn,
onColumnDragStart,
onColumnDragEnd,
draggingColumnName,
setColumnDropBefore,
onDropColumn,
dropTarget,
setDropTarget,
onAddCard,
onMoveCard,
newCardId,
updateCardState,
handleDeleteCard,
setModalCard,
dragRef,
}: {
column: IBoardColumn;
cards: Map<string, RichCard>;
onRenameColumn: (columnName: string) => void;
onDeleteColumn: (columnName: string) => void;
onColumnDragStart: (e: DragEvent, columnName: string) => void;
onColumnDragEnd: () => void;
draggingColumnName: string | null;
setColumnDropBefore: (columnName: string | null) => void;
onDropColumn: (e: DragEvent, beforeColumnName: string | null) => void;
dropTarget: { column: string; beforeCardId: string | null } | null;
setDropTarget: (target: { column: string; beforeCardId: string | null } | null) => void;
onAddCard: (columnName: string) => void;
onMoveCard: (
drag: {
cardId: string;
fromColumn: string;
},
toColumn: string,
beforeCardId: string | null,
) => void;
newCardId: string | null;
updateCardState: (updated: RichCard) => void;
handleDeleteCard: (cardId: string) => void;
setModalCard: (card: RichCard | null) => void;
dragRef: React.RefObject<{ cardId: string; fromColumn: string } | null>;
}) {
const dragHandle = useDraggable(
{
onDragStart: onColumnDragStart,
onDragEnd: onColumnDragEnd,
},
column.name,
);

const onCardDragStart = (event: DragEvent, cardId: string, fromColumn: string) => {
dragRef.current = { cardId, fromColumn };
event.dataTransfer.effectAllowed = "move";
// Mark this drag as a card drag so nested elements can distinguish it
try {
event.dataTransfer.setData("application/x-notes-board-card", cardId);
} catch (error) {
console.error("Failed to set drag data:", error);
}
};

const onCardDragEnd = () => {
setDropTarget(null);
dragRef.current = null;
};

const onDropCard = (event: DragEvent, toColumn: string, beforeCardId: string | null) => {
event.preventDefault();
event.stopPropagation();
setDropTarget(null);
const drag = dragRef.current;
dragRef.current = null;
if (!drag || drag.cardId === beforeCardId) return;
void onMoveCard(drag, toColumn, beforeCardId);
};

const onCardDragEnter = (cardId: string, columnName: string) => {
setDropTarget({ column: columnName, beforeCardId: cardId });
};

return (
<section
key={column.name}
className="board-column"
onDragEnter={(e) => {
e.stopPropagation();
if (draggingColumnName) {
setColumnDropBefore(column.name);
return;
}
setDropTarget({ column: column.name, beforeCardId: null });
}}
onDragOver={(e) => e.preventDefault()}
onDrop={(e) => {
if (draggingColumnName) {
onDropColumn(e, column.name);
return;
}
// Use the tracked dropTarget so drops on silhouette land in the right spot
const beforeId = dropTarget?.column === column.name ? dropTarget.beforeCardId : null;
onDropCard(e, column.name, beforeId);
}}
onDragLeave={(e) => {
if (e.currentTarget.contains(e.relatedTarget as Node)) {
return;
}
if (!draggingColumnName) {
setDropTarget(null);
}
if (!e.currentTarget.closest(".board-scroll")?.contains(e.relatedTarget as Node)) {
setColumnDropBefore(null);
}
}}
>
<header className="board-column-head" {...dragHandle}>
<span className="board-column-name" onDoubleClick={() => void onRenameColumn(column.name)}>
{column.name}
</span>
<span className="board-column-count">{column.cards.length}</span>
<button
className="board-column-del"
aria-label={`Delete column ${column.name}`}
onClick={() => onDeleteColumn(column.name)}
>
×
</button>
</header>

<div className="board-cards">
{column.cards.map((cardId) => {
const card = cards.get(cardId);
if (!card) return null;
return (
<Fragment key={cardId}>
{dropTarget?.column === column.name && dropTarget.beforeCardId === cardId && (
<div
className="board-card-silhouette"
onDragEnter={(e) => e.stopPropagation()}
onDragOver={(e) => e.preventDefault()}
/>
)}
<BoardCard
card={card}
isNew={card.id === newCardId}
onDragStart={onCardDragStart}
onDragEnd={onCardDragEnd}
onDropCard={onDropCard}
onDragEnter={onCardDragEnter}
updateCardState={updateCardState}
handleDeleteCard={handleDeleteCard}
column={column}
onOpenModal={setModalCard}
isColumnDragActive={Boolean(draggingColumnName)}
/>
</Fragment>
);
})}
{/* Silhouette at end of column */}
{dropTarget?.column === column.name && dropTarget.beforeCardId === null && (
<div
className="board-card-silhouette"
onDragEnter={(e) => e.stopPropagation()}
onDragOver={(e) => e.preventDefault()}
/>
)}
</div>

<button className="board-add-card" onClick={() => void onAddCard(column.name)}>
+ Add card
</button>
</section>
);
}
6 changes: 3 additions & 3 deletions packages/note-boards/src/board-format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ export interface RichCard {
frontmatter?: FrontmatterProp[];
}

export interface BoardColumn {
export interface IBoardColumn {
name: string;
/** Card IDs referencing files in the companion dot-folder. */
cards: string[];
}

export interface BoardModel {
frontmatter: FrontmatterProp[];
columns: BoardColumn[];
columns: IBoardColumn[];
}

let counter = 0;
Expand All @@ -41,7 +41,7 @@ export function newCardId(): string {
*/
export function parseBoard(markdown: string): BoardModel {
const parsed = parseFrontmatter(markdown);
const columns = getFrontmatterField<BoardColumn[]>(parsed.props, "columns") ?? [];
const columns = getFrontmatterField<IBoardColumn[]>(parsed.props, "columns") ?? [];

if (columns.length === 0) {
columns.push({ name: "Todo", cards: [] });
Expand Down
Loading