6317dade9e
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>
72 lines
2.4 KiB
TypeScript
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```";
|
|
}
|