Files
lms-sb/src/lib/tools/dataview.ts
T
admins 6317dade9e Add Dataview and Bases generators (Toolbox Phase 2)
Two more inside-LMS generators under /tools, completing the 6-tool set:
- Dataview: DQL builder (TABLE/LIST/TASK/CALENDAR, FROM/WHERE/SORT/LIMIT),
  output wrapped in a dataview code block
- Bases: .base YAML builder (views, filters and:, order, sort, groupBy, limit);
  filter expressions emitted as single-quoted YAML scalars for safety

Syntax verified against official Obsidian docs (research workflow). Audit fix:
Bases sort entries use 'column:' (not 'property:') per documented working .base
files; 'property:' is kept only for groupBy. 14 new tests (47 total).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:04:09 +05:00

72 lines
2.4 KiB
TypeScript

export type DataviewQueryType = "TABLE" | "LIST" | "TASK" | "CALENDAR";
export interface DataviewColumn {
/** выражение/поле, напр. file.name, status, file.mtime */
expr: string;
/** опц. человекочитаемый заголовок колонки (AS "…") */
alias?: string;
}
export interface DataviewInput {
queryType: DataviewQueryType;
/** WITHOUT ID — скрыть первую колонку-ссылку (TABLE/LIST) */
withoutId?: boolean;
/** колонки/поля (TABLE — все; LIST/CALENDAR — берётся первое) */
columns?: DataviewColumn[];
/** источник FROM как есть: '#tag', '"Папка"', '[[Заметка]]', их комбинации */
from?: string;
/** условие WHERE как есть */
where?: string;
/** поле сортировки */
sortField?: string;
sortDir?: "ASC" | "DESC";
/** LIMIT n */
limit?: number;
}
function renderColumns(cols: DataviewColumn[]): string {
return cols
.filter((c) => c.expr.trim())
.map((c) => {
const expr = c.expr.trim();
const alias = c.alias?.trim();
return alias ? `${expr} AS "${alias.replace(/"/g, '\\"')}"` : expr;
})
.join(", ");
}
/** Собирает запрос Dataview DQL и оборачивает в кодблок dataview. */
export function buildDataview(input: DataviewInput): string {
const lines: string[] = [];
const cols = input.columns ?? [];
const withoutId = input.withoutId ? " WITHOUT ID" : "";
switch (input.queryType) {
case "TABLE": {
const c = renderColumns(cols);
lines.push(`TABLE${withoutId}${c ? " " + c : ""}`);
break;
}
case "LIST": {
const first = cols.find((c) => c.expr.trim())?.expr.trim();
lines.push(`LIST${withoutId}${first ? " " + first : ""}`);
break;
}
case "TASK":
lines.push("TASK");
break;
case "CALENDAR": {
const dateField = cols.find((c) => c.expr.trim())?.expr.trim() || "file.ctime";
lines.push(`CALENDAR ${dateField}`);
break;
}
}
if (input.from?.trim()) lines.push(`FROM ${input.from.trim()}`);
if (input.where?.trim()) lines.push(`WHERE ${input.where.trim()}`);
if (input.sortField?.trim()) lines.push(`SORT ${input.sortField.trim()} ${input.sortDir ?? "DESC"}`);
if (input.limit && input.limit > 0) lines.push(`LIMIT ${Math.floor(input.limit)}`);
return "```dataview\n" + lines.join("\n") + "\n```";
}