- Run record
- loading
Loading this Run…
Loading the Revisions of this Run…
A published source file of Revision 0 of Boardly — Website Relaunch 1.0.0, shown as text in the browser.
import type { BoardState, Card, Column, Filters, Location } from "../types";
export const DONE_COLUMN_ID = "done";
export function locate(board: BoardState, cardId: string): Location | null {
for (const column of board.columns) {
const index = column.card_ids.indexOf(cardId);
if (index !== -1) return { columnId: column.id, index };
}
return null;
}
export function getColumn(board: BoardState, columnId: string): Column | undefined {
return board.columns.find((c) => c.id === columnId);
}
/** True when adding `cardId` (or a new card, when null) to the column would break its WIP limit. */
export function wouldExceedLimit(board: BoardState, columnId: string, cardId: string | null): boolean {
const column = getColumn(board, columnId);
if (!column || column.wip_limit == null) return false;
const others = column.card_ids.filter((id) => id !== cardId).length;
return others + 1 > column.wip_limit;
}
/** Index to insert at (in the list with `cardId` removed) so the card lands before `beforeId`. */
export function indexBefore(board: BoardState, columnId: string, cardId: string, beforeId: string | null): number {
const column = getColumn(board, columnId);
if (!column) return 0;
const ids = column.card_ids.filter((id) => id !== cardId);
if (beforeId == null) return ids.length;
const i = ids.indexOf(beforeId);
return i === -1 ? ids.length : i;
}
export function moveCard(board: BoardState, cardId: string, toColumnId: string, toIndex: number): BoardState {
const columns = board.columns.map((column) => {
let ids = column.card_ids.filter((id) => id !== cardId);
if (column.id === toColumnId) {
const at = Math.max(0, Math.min(toIndex, ids.length));
ids = [...ids.slice(0, at), cardId, ...ids.slice(at)];
}
return ids.length === column.card_ids.length && column.id !== toColumnId
? column
: { ...column, card_ids: ids };
});
return { ...board, columns };
}
export function insertCard(board: BoardState, card: Card, columnId: string, index: number): BoardState {
const cards = { ...board.cards, [card.id]: card };
return moveCard({ ...board, cards }, card.id, columnId, index);
}
export function removeCard(board: BoardState, cardId: string): BoardState {
const cards = { ...board.cards };
delete cards[cardId];
const columns = board.columns.map((column) =>
column.card_ids.includes(cardId)
? { ...column, card_ids: column.card_ids.filter((id) => id !== cardId) }
: column,
);
return { columns, cards };
}
export function updateCard(board: BoardState, card: Card): BoardState {
return { ...board, cards: { ...board.cards, [card.id]: card } };
}
export function isOverdue(card: Card, columnId: string, today: string): boolean {
return card.due != null && card.due < today && columnId !== DONE_COLUMN_ID;
}
function fold(text: string): string {
return text
.normalize("NFD")
.replace(/[̀-ͯ]/g, "")
.toLowerCase();
}
export function matchesFilters(card: Card, filters: Filters): boolean {
if (filters.assignee === "unassigned") {
if (card.assignee !== null) return false;
} else if (filters.assignee !== "all" && card.assignee !== filters.assignee) {
return false;
}
if (filters.label !== "all" && !card.labels.includes(filters.label)) return false;
const query = fold(filters.query.trim());
if (query && !fold(`${card.title}\n${card.description}`).includes(query)) return false;
return true;
}
export function hasActiveFilters(filters: Filters): boolean {
return filters.assignee !== "all" || filters.label !== "all" || filters.query.trim() !== "";
}
export function makeId(prefix: string): string {
return `${prefix}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
}Source is browser-viewable only. There is no download or archive endpoint, and no additional license is granted beyond inspection and research.