/
deka
/
buffer
Обзор
Документация
Войти
/
deka
/
buffer
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
dev
src/converters/board-to-markdown.js
223 строки
8 KB
Maksim Ratnikov
feat: доска в Markdown — таблицы объектов
07 авг 2026, 12:01
07 авг 2026, 12:01
3f61bda
Код
Авторство
О чём код?
// Доска → Markdown-таблицы объектов. // // На доске объекты живут в двух видах, и различать их приходится по форме // таблицы, потому что больше не по чему: // - таблица «Поле | Значение» — это ОДИН объект, её строки это его свойства; // - таблица с заголовками колонок — набор объектов, по объекту на строку. // Таблицы одинаковой формы сливаются в одну Markdown-таблицу, чтобы 65 бланков // команд превратились в 65 строк, а не в 65 отдельных таблиц. const KEY_VALUE_HEADERS = [ ["поле", "значение"], ["field", "value"], ["свойство", "значение"], ["параметр", "значение"] ]; export function boardLayoutToMarkdown(layout, opts = {}) { const collected = collect(layout); const parts = []; if (opts.title) parts.push(`# ${opts.title}`, ""); for (const group of collected.objectGroups) { parts.push(`## ${group.name}`, ""); parts.push(renderTable( ["Дорожка", "Объект", ...group.fields], group.objects.map((object) => [ object.lane, object.name, ...group.fields.map((field) => object.fields.get(field) ?? "") ]) ), ""); } for (const group of collected.rowGroups) { parts.push(`## ${group.name}`, ""); parts.push(renderTable( ["Дорожка", "Объект", ...group.columns], group.rows.map((row) => [row.lane, row.source, ...row.cells]) ), ""); } if (collected.cards.length) { parts.push("## Карточки", ""); parts.push(renderTable( ["Дорожка", "Тип", "Заголовок", "Текст", "Цвет"], collected.cards.map((card) => [card.lane, card.kind, card.title, card.text, card.color]) ), ""); } if (collected.edges.length) { parts.push("## Связи", ""); parts.push(renderTable( ["Откуда", "Куда", "Стрелка"], collected.edges.map((edge) => [edge.from, edge.to, edge.arrow]) ), ""); } if (!parts.length) return "На доске нет ни таблиц, ни карточек.\n"; return parts.join("\n").replace(/\n+$/, "") + "\n"; } function collect(layout) { const objects = []; const rowRecords = []; const cards = []; const titleById = new Map(); const walk = (node, lane) => { if (!node || typeof node !== "object") return; switch (node.type) { case "swimlanes": for (const swimlane of node.lanes ?? []) { walk(swimlane.content, firstLine(swimlane.title) || lane); } return; case "table": readTable(node, lane); return; case "card": titleById.set(node.id, firstLine(node.title) || firstLine(node.text) || node.id); cards.push({ lane: lane ?? "", kind: node.variant === "figure" ? (node.figure ?? "фигура") : (node.variant ?? "sticker"), title: inline(node.title), text: inline(node.text), color: node.color ?? "" }); return; default: for (const item of node.items ?? []) walk(item, lane); } }; const readTable = (table, lane) => { const columns = table.columns ?? []; const rows = table.rows ?? []; const source = firstLine(table.title) || table.id || ""; if (isKeyValue(columns)) { const fields = new Map(); for (const row of rows) { const key = inline(row.cells?.[columns[0].id]); if (!key) continue; fields.set(key, inline(row.cells?.[columns[1].id])); } // Имя объекта — из поля «Команда»/«Название», иначе из заголовка таблицы // после разделителя: «Ландшафт SDD · Знания Ops» → «Знания Ops». const named = fields.get("Команда") || fields.get("Название") || fields.get("Объект"); objects.push({ lane: lane ?? "", name: named || afterSeparator(source), group: beforeSeparator(source), fields }); } else { for (const row of rows) { rowRecords.push({ lane: lane ?? "", source: afterSeparator(source) || source, group: beforeSeparator(source), columns: columns.map((column) => column.title || column.id), cells: columns.map((column) => inline(row.cells?.[column.id])) }); } } for (const row of rows) { for (const nested of Object.values(row.content ?? {})) walk(nested, lane); } }; walk(layout?.content, null); return { objectGroups: groupBy(objects, (object) => object.group, (name, list) => ({ name: name || "Объекты", fields: unionFields(list), objects: list })), rowGroups: groupBy(rowRecords, (record) => `${record.group} ${record.columns.join("|")}`, (key, list) => ({ name: key.split("