@riebeckite/plugin-dataview 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1258 @@
1
+ // index.ts
2
+ import {
3
+ createStyleAsset,
4
+ definePlugin
5
+ } from "@riebeckite/core";
6
+
7
+ // src/remark.ts
8
+ import { visit } from "unist-util-visit";
9
+
10
+ // src/parse.ts
11
+ var QUERY_TYPES = /* @__PURE__ */ new Set([
12
+ "list",
13
+ "table",
14
+ "task",
15
+ "calendar"
16
+ ]);
17
+ var CLAUSE_PATTERN = /^(from|where|sort|group\s+by|limit)\b(.*)$/i;
18
+ var FIELD_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*$/;
19
+ var DataviewParseError = class extends Error {
20
+ };
21
+ function parseDataview(source) {
22
+ try {
23
+ return { status: "ok", spec: parseSpec(source) };
24
+ } catch (error) {
25
+ return {
26
+ status: "error",
27
+ message: error instanceof DataviewParseError ? error.message : error instanceof Error ? error.message : String(error)
28
+ };
29
+ }
30
+ }
31
+ function parseSpec(source) {
32
+ const lines = source.split(/\r?\n/);
33
+ const firstIndex = lines.findIndex((line) => line.trim().length > 0);
34
+ if (firstIndex < 0) {
35
+ throw new DataviewParseError("the dataview block is empty.");
36
+ }
37
+ const first = lines[firstIndex].trim();
38
+ const typeMatch = /^([A-Za-z]+)\b(.*)$/.exec(first);
39
+ if (!typeMatch) {
40
+ throw new DataviewParseError("a dataview block must start with a query type.");
41
+ }
42
+ const type = typeMatch[1].toLowerCase();
43
+ if (!QUERY_TYPES.has(type)) {
44
+ throw new DataviewParseError(
45
+ `unsupported query type \`${typeMatch[1]}\`; expected LIST, TABLE, TASK, or CALENDAR.`
46
+ );
47
+ }
48
+ const { headLines, clauses } = segment(lines.slice(firstIndex + 1));
49
+ const headText = [typeMatch[2].trim(), ...headLines].filter((part) => part.length > 0).join(" ").trim();
50
+ let columns = [];
51
+ let expression = null;
52
+ if (type === "table") {
53
+ columns = headText.length > 0 ? parseColumns(headText) : [{ field: "file.link" }];
54
+ } else if (type === "list" || type === "calendar") {
55
+ expression = headText.length > 0 ? headText : null;
56
+ } else if (headText.length > 0) {
57
+ throw new DataviewParseError("TASK does not accept a column or expression list.");
58
+ }
59
+ const fromText = takeClause(clauses, "from");
60
+ const whereText = takeClause(clauses, "where");
61
+ const sortText = takeClause(clauses, "sort");
62
+ const groupText = takeClause(clauses, "group by");
63
+ const limitText = takeClause(clauses, "limit");
64
+ return {
65
+ type,
66
+ columns,
67
+ expression,
68
+ from: fromText ? parseFrom(fromText) : null,
69
+ where: whereText ? parseExpression(whereText) : null,
70
+ sort: sortText ? parseSort(sortText) : [],
71
+ groupBy: groupText ? parseField(groupText, "GROUP BY") : null,
72
+ limit: limitText ? parseLimit(limitText) : null
73
+ };
74
+ }
75
+ function segment(lines) {
76
+ const headLines = [];
77
+ const clauses = [];
78
+ let current = null;
79
+ for (const raw of lines) {
80
+ const line = raw.trim();
81
+ if (line.length === 0) continue;
82
+ const match = CLAUSE_PATTERN.exec(line);
83
+ if (match) {
84
+ const key = match[1].toLowerCase().replace(/\s+/g, " ");
85
+ const text = match[2].trim();
86
+ if (current) clauses.push(current);
87
+ current = { key, lines: text.length > 0 ? [text] : [] };
88
+ continue;
89
+ }
90
+ if (current) current.lines.push(line);
91
+ else headLines.push(line);
92
+ }
93
+ if (current) clauses.push(current);
94
+ return { headLines, clauses };
95
+ }
96
+ function takeClause(clauses, key) {
97
+ const matches = clauses.filter((clause) => clause.key === key);
98
+ if (matches.length === 0) return null;
99
+ if (matches.length > 1) {
100
+ throw new DataviewParseError(`the \`${key.toUpperCase()}\` clause appears more than once.`);
101
+ }
102
+ const text = matches[0].lines.join(" ").trim();
103
+ if (text.length === 0) {
104
+ throw new DataviewParseError(`the \`${key.toUpperCase()}\` clause is empty.`);
105
+ }
106
+ return text;
107
+ }
108
+ function parseColumns(text) {
109
+ const parts = splitTopLevel(text, ",");
110
+ if (parts.length === 0) return [{ field: "file.link" }];
111
+ return parts.map(parseColumn);
112
+ }
113
+ function parseColumn(part) {
114
+ const asMatch = /\s+as\s+([\s\S]+)$/i.exec(part);
115
+ let fieldPart = part;
116
+ let label;
117
+ if (asMatch && asMatch.index !== void 0) {
118
+ fieldPart = part.slice(0, asMatch.index).trim();
119
+ label = unquote(asMatch[1].trim());
120
+ }
121
+ if (fieldPart.length === 0) {
122
+ throw new DataviewParseError("a TABLE column is empty.");
123
+ }
124
+ const field = parseField(fieldPart, "TABLE column");
125
+ return label === void 0 ? { field } : { field, label };
126
+ }
127
+ function parseSort(text) {
128
+ return splitTopLevel(text, ",").map((part) => {
129
+ const match = /^(\S+)(?:\s+(asc|desc))?$/i.exec(part.trim());
130
+ if (!match) {
131
+ throw new DataviewParseError(`unsupported SORT term \`${part}\`.`);
132
+ }
133
+ return {
134
+ field: parseField(match[1], "SORT"),
135
+ order: match[2]?.toLowerCase() ?? "asc"
136
+ };
137
+ });
138
+ }
139
+ function parseLimit(text) {
140
+ const match = /^(\d+)$/.exec(text.trim());
141
+ if (!match) {
142
+ throw new DataviewParseError(`LIMIT expects a non-negative integer, received \`${text}\`.`);
143
+ }
144
+ return Number(match[1]);
145
+ }
146
+ function parseField(value, context) {
147
+ const field = value.trim();
148
+ if (!FIELD_PATTERN.test(field)) {
149
+ throw new DataviewParseError(`unsupported ${context} field \`${field}\`.`);
150
+ }
151
+ return field;
152
+ }
153
+ var FROM_TOKEN_PATTERN = /(\[\[[^\]]*\]\])|(#[^\s()]+)|("(?:[^"\\]|\\.)*")|('(?:[^'\\]|\\.)*')|(\()|(\))|(!|-)|([^\s()]+)/g;
154
+ function parseFrom(text) {
155
+ const tokens = [];
156
+ FROM_TOKEN_PATTERN.lastIndex = 0;
157
+ for (let match = FROM_TOKEN_PATTERN.exec(text); match !== null; match = FROM_TOKEN_PATTERN.exec(text)) {
158
+ tokens.push(toFromToken(match));
159
+ }
160
+ if (tokens.length === 0) {
161
+ throw new DataviewParseError("the FROM clause is empty.");
162
+ }
163
+ const state = { tokens, index: 0 };
164
+ const node = parseFromOr(state);
165
+ if (state.index < tokens.length) {
166
+ throw new DataviewParseError("unexpected token in FROM.");
167
+ }
168
+ return node;
169
+ }
170
+ function toFromToken(match) {
171
+ const [raw, link, tag, doubleQuoted, singleQuoted, lparen, rparen, negate] = match;
172
+ if (link !== void 0) {
173
+ const value = link.slice(2, -2).split("|")[0].split("#")[0].trim();
174
+ if (value.length === 0) {
175
+ throw new DataviewParseError("a link source in FROM is empty.");
176
+ }
177
+ return { type: "source", source: { kind: "link", value } };
178
+ }
179
+ if (tag !== void 0) {
180
+ return { type: "source", source: { kind: "tag", value: tag.slice(1) } };
181
+ }
182
+ if (doubleQuoted !== void 0 || singleQuoted !== void 0) {
183
+ return {
184
+ type: "source",
185
+ source: { kind: "folder", value: unquote(raw.trim()) }
186
+ };
187
+ }
188
+ if (lparen !== void 0) return { type: "lparen" };
189
+ if (rparen !== void 0) return { type: "rparen" };
190
+ if (negate !== void 0) return { type: "not" };
191
+ const word = (raw ?? "").toLowerCase();
192
+ if (word === "and") return { type: "and" };
193
+ if (word === "or") return { type: "or" };
194
+ return { type: "source", source: { kind: "folder", value: raw.trim() } };
195
+ }
196
+ function parseFromOr(state) {
197
+ let node = parseFromAnd(state);
198
+ while (state.tokens[state.index]?.type === "or") {
199
+ state.index += 1;
200
+ node = { kind: "or", left: node, right: parseFromAnd(state) };
201
+ }
202
+ return node;
203
+ }
204
+ function parseFromAnd(state) {
205
+ let node = parseFromUnary(state);
206
+ for (; ; ) {
207
+ const token = state.tokens[state.index];
208
+ if (token?.type === "and") {
209
+ state.index += 1;
210
+ node = { kind: "and", left: node, right: parseFromUnary(state) };
211
+ continue;
212
+ }
213
+ if (token?.type === "source" || token?.type === "lparen" || token?.type === "not") {
214
+ node = { kind: "and", left: node, right: parseFromUnary(state) };
215
+ continue;
216
+ }
217
+ return node;
218
+ }
219
+ }
220
+ function parseFromUnary(state) {
221
+ const token = state.tokens[state.index];
222
+ if (!token) throw new DataviewParseError("unexpected end of FROM.");
223
+ if (token.type === "not") {
224
+ state.index += 1;
225
+ return { kind: "not", child: parseFromUnary(state) };
226
+ }
227
+ if (token.type === "lparen") {
228
+ state.index += 1;
229
+ const node = parseFromOr(state);
230
+ if (state.tokens[state.index]?.type !== "rparen") {
231
+ throw new DataviewParseError("unbalanced parentheses in FROM.");
232
+ }
233
+ state.index += 1;
234
+ return node;
235
+ }
236
+ if (token.type === "source") {
237
+ state.index += 1;
238
+ return { kind: "source", source: token.source };
239
+ }
240
+ throw new DataviewParseError("unexpected token in FROM.");
241
+ }
242
+ function parseExpression(text) {
243
+ const tokens = tokenizeExpression(text);
244
+ if (tokens.length === 0) {
245
+ throw new DataviewParseError("the WHERE clause is empty.");
246
+ }
247
+ const state = { tokens, index: 0 };
248
+ const node = parseOr(state);
249
+ if (state.index < tokens.length) {
250
+ throw new DataviewParseError("unexpected token in the WHERE expression.");
251
+ }
252
+ return node;
253
+ }
254
+ function tokenizeExpression(text) {
255
+ const tokens = [];
256
+ let index = 0;
257
+ while (index < text.length) {
258
+ const char = text[index];
259
+ if (char === void 0) break;
260
+ if (/\s/.test(char)) {
261
+ index += 1;
262
+ continue;
263
+ }
264
+ const pair = text.slice(index, index + 2);
265
+ if (pair === ">=" || pair === "<=" || pair === "!=") {
266
+ tokens.push({ type: "op", value: pair });
267
+ index += 2;
268
+ continue;
269
+ }
270
+ if (char === "=" || char === ">" || char === "<") {
271
+ tokens.push({ type: "op", value: char });
272
+ index += 1;
273
+ continue;
274
+ }
275
+ if (char === "!") {
276
+ tokens.push({ type: "not" });
277
+ index += 1;
278
+ continue;
279
+ }
280
+ if (char === "(") {
281
+ tokens.push({ type: "lparen" });
282
+ index += 1;
283
+ continue;
284
+ }
285
+ if (char === ")") {
286
+ tokens.push({ type: "rparen" });
287
+ index += 1;
288
+ continue;
289
+ }
290
+ if (char === ",") {
291
+ tokens.push({ type: "comma" });
292
+ index += 1;
293
+ continue;
294
+ }
295
+ if (char === '"' || char === "'") {
296
+ const quoted = readQuoted(text, index);
297
+ tokens.push({ type: "string", value: quoted.value });
298
+ index = quoted.next;
299
+ continue;
300
+ }
301
+ if (/[0-9]/.test(char) || char === "-" && /[0-9]/.test(text[index + 1] ?? "")) {
302
+ const match = /^-?\d+(?:\.\d+)?/.exec(text.slice(index));
303
+ if (match) {
304
+ tokens.push({ type: "number", value: Number(match[0]) });
305
+ index += match[0].length;
306
+ continue;
307
+ }
308
+ }
309
+ if (/[A-Za-z_$]/.test(char)) {
310
+ const match = /^[A-Za-z_$][A-Za-z0-9_$.]*/.exec(text.slice(index));
311
+ if (match) {
312
+ tokens.push({ type: "word", value: match[0] });
313
+ index += match[0].length;
314
+ continue;
315
+ }
316
+ }
317
+ throw new DataviewParseError(`unexpected character \`${char}\` in WHERE.`);
318
+ }
319
+ return tokens;
320
+ }
321
+ function readQuoted(text, start) {
322
+ const quote = text[start];
323
+ let value = "";
324
+ let index = start + 1;
325
+ while (index < text.length) {
326
+ const char = text[index];
327
+ if (char === void 0) break;
328
+ if (char === "\\" && index + 1 < text.length) {
329
+ value += text[index + 1];
330
+ index += 2;
331
+ continue;
332
+ }
333
+ if (char === quote) return { value, next: index + 1 };
334
+ value += char;
335
+ index += 1;
336
+ }
337
+ throw new DataviewParseError("unterminated string literal in WHERE.");
338
+ }
339
+ function parseOr(state) {
340
+ let node = parseAnd(state);
341
+ while (isWord(state.tokens[state.index], "or")) {
342
+ state.index += 1;
343
+ node = { kind: "or", left: node, right: parseAnd(state) };
344
+ }
345
+ return node;
346
+ }
347
+ function parseAnd(state) {
348
+ let node = parseUnary(state);
349
+ while (isWord(state.tokens[state.index], "and")) {
350
+ state.index += 1;
351
+ node = { kind: "and", left: node, right: parseUnary(state) };
352
+ }
353
+ return node;
354
+ }
355
+ function parseUnary(state) {
356
+ if (state.tokens[state.index]?.type === "not") {
357
+ state.index += 1;
358
+ return { kind: "not", operand: parseUnary(state) };
359
+ }
360
+ return parseComparison(state);
361
+ }
362
+ function parseComparison(state) {
363
+ const left = parsePrimary(state);
364
+ const token = state.tokens[state.index];
365
+ if (token?.type !== "op") return left;
366
+ state.index += 1;
367
+ return {
368
+ kind: "compare",
369
+ operator: token.value,
370
+ left,
371
+ right: parsePrimary(state)
372
+ };
373
+ }
374
+ function parsePrimary(state) {
375
+ const token = state.tokens[state.index];
376
+ if (!token) {
377
+ throw new DataviewParseError("unexpected end of the WHERE expression.");
378
+ }
379
+ if (token.type === "lparen") {
380
+ state.index += 1;
381
+ const node = parseOr(state);
382
+ if (state.tokens[state.index]?.type !== "rparen") {
383
+ throw new DataviewParseError("unbalanced parentheses in WHERE.");
384
+ }
385
+ state.index += 1;
386
+ return node;
387
+ }
388
+ if (token.type === "string" || token.type === "number") {
389
+ state.index += 1;
390
+ return { kind: "literal", value: token.value };
391
+ }
392
+ if (token.type === "word") {
393
+ const lower = token.value.toLowerCase();
394
+ if (lower === "true" || lower === "false") {
395
+ state.index += 1;
396
+ return { kind: "literal", value: lower === "true" };
397
+ }
398
+ if (lower === "null") {
399
+ state.index += 1;
400
+ return { kind: "literal", value: null };
401
+ }
402
+ if (state.tokens[state.index + 1]?.type === "lparen") {
403
+ return parseCall(state, lower);
404
+ }
405
+ state.index += 1;
406
+ return { kind: "field", path: token.value.split(".") };
407
+ }
408
+ throw new DataviewParseError("unexpected token in the WHERE expression.");
409
+ }
410
+ function parseCall(state, name) {
411
+ state.index += 2;
412
+ const args = [];
413
+ if (state.tokens[state.index]?.type !== "rparen") {
414
+ args.push(parseOr(state));
415
+ while (state.tokens[state.index]?.type === "comma") {
416
+ state.index += 1;
417
+ args.push(parseOr(state));
418
+ }
419
+ }
420
+ if (state.tokens[state.index]?.type !== "rparen") {
421
+ throw new DataviewParseError(`unbalanced parentheses in \`${name}()\`.`);
422
+ }
423
+ state.index += 1;
424
+ return { kind: "call", name, args };
425
+ }
426
+ function isWord(token, value) {
427
+ return token?.type === "word" && token.value.toLowerCase() === value;
428
+ }
429
+ function splitTopLevel(text, separator) {
430
+ const parts = [];
431
+ let current = "";
432
+ let depth = 0;
433
+ let quote = null;
434
+ for (const char of text) {
435
+ if (quote) {
436
+ current += char;
437
+ if (char === quote) quote = null;
438
+ continue;
439
+ }
440
+ if (char === '"' || char === "'") {
441
+ quote = char;
442
+ current += char;
443
+ continue;
444
+ }
445
+ if (char === "(") depth += 1;
446
+ if (char === ")") depth = Math.max(0, depth - 1);
447
+ if (char === separator && depth === 0) {
448
+ parts.push(current.trim());
449
+ current = "";
450
+ continue;
451
+ }
452
+ current += char;
453
+ }
454
+ parts.push(current.trim());
455
+ return parts.filter((part) => part.length > 0);
456
+ }
457
+ function unquote(value) {
458
+ if (value.length >= 2) {
459
+ const first = value[0];
460
+ const last = value[value.length - 1];
461
+ if (first === '"' && last === '"' || first === "'" && last === "'") {
462
+ return value.slice(1, -1);
463
+ }
464
+ }
465
+ return value;
466
+ }
467
+
468
+ // src/placeholder.ts
469
+ var DATAVIEW_ATTRIBUTE = "data-rr-dataview";
470
+ function encodeDataviewSource(source) {
471
+ return encodeURIComponent(source);
472
+ }
473
+ function decodeDataviewSource(encoded) {
474
+ return decodeURIComponent(encoded);
475
+ }
476
+ function createDataviewPlaceholder(source) {
477
+ return `<div ${DATAVIEW_ATTRIBUTE}="${encodeDataviewSource(source)}"></div>`;
478
+ }
479
+ function createDataviewPlaceholderPattern() {
480
+ return new RegExp(
481
+ `<div\\b[^>]*\\b${DATAVIEW_ATTRIBUTE}="([^"]*)"[^>]*>\\s*</div>`,
482
+ "g"
483
+ );
484
+ }
485
+
486
+ // src/remark.ts
487
+ var DATAVIEW_SOURCE = "@riebeckite/plugin-dataview";
488
+ function remarkDataview(options = {}) {
489
+ const language = options.language ?? "dataview";
490
+ const unsupportedLanguage = `${language}js`;
491
+ return (tree, file) => {
492
+ visit(tree, "code", (node, index, parent) => {
493
+ if (node.lang === unsupportedLanguage) {
494
+ reportDataviewMessage(
495
+ file,
496
+ `DataviewJS blocks (\`${unsupportedLanguage}\`) are not supported; the block was left as code.`
497
+ );
498
+ return;
499
+ }
500
+ if (node.lang !== language) return;
501
+ if (!parent || index === void 0) return;
502
+ const parsed = parseDataview(node.value);
503
+ if (parsed.status === "error") {
504
+ reportDataviewMessage(
505
+ file,
506
+ `Unsupported dataview query: ${parsed.message}`
507
+ );
508
+ }
509
+ const html = {
510
+ type: "html",
511
+ value: createDataviewPlaceholder(node.value)
512
+ };
513
+ parent.children.splice(index, 1, html);
514
+ });
515
+ };
516
+ }
517
+ function reportDataviewMessage(file, message) {
518
+ const reporter = file?.message;
519
+ if (typeof reporter !== "function") return;
520
+ const diagnostic = reporter.call(file, message);
521
+ if (diagnostic && typeof diagnostic === "object") {
522
+ Object.assign(diagnostic, { source: DATAVIEW_SOURCE });
523
+ }
524
+ }
525
+
526
+ // src/evaluate.ts
527
+ var DataviewEvaluationError = class extends Error {
528
+ };
529
+ function selectDataviewEntries(spec, manifest, defaultLimit) {
530
+ let entries = manifest.entries.filter(
531
+ (entry) => matchesDataviewFrom(spec.from, entry, manifest)
532
+ );
533
+ if (spec.where) {
534
+ entries = entries.filter(
535
+ (entry) => isTruthy(
536
+ evaluateDataviewExpression(spec.where, {
537
+ entry,
538
+ manifest
539
+ })
540
+ )
541
+ );
542
+ }
543
+ if (spec.sort.length > 0) {
544
+ entries = sortEntries(entries, spec.sort, manifest);
545
+ }
546
+ const limit = spec.limit ?? defaultLimit;
547
+ if (limit !== null) entries = entries.slice(0, limit);
548
+ if (spec.groupBy) {
549
+ return {
550
+ entries,
551
+ groups: groupEntries(entries, spec.groupBy, manifest),
552
+ grouped: true
553
+ };
554
+ }
555
+ return { entries, groups: [], grouped: false };
556
+ }
557
+ function matchesDataviewFrom(from, entry, manifest) {
558
+ if (!from) return true;
559
+ switch (from.kind) {
560
+ case "source":
561
+ return matchesSource(from.source, entry, manifest);
562
+ case "not":
563
+ return !matchesDataviewFrom(from.child, entry, manifest);
564
+ case "and":
565
+ return matchesDataviewFrom(from.left, entry, manifest) && matchesDataviewFrom(from.right, entry, manifest);
566
+ case "or":
567
+ return matchesDataviewFrom(from.left, entry, manifest) || matchesDataviewFrom(from.right, entry, manifest);
568
+ default:
569
+ return true;
570
+ }
571
+ }
572
+ function matchesSource(source, entry, manifest) {
573
+ switch (source.kind) {
574
+ case "tag": {
575
+ const wanted = stripHash(source.value).toLowerCase();
576
+ return entry.tags.some((tag) => tag.toLowerCase() === wanted);
577
+ }
578
+ case "folder": {
579
+ const folder = normalizeFolder(source.value);
580
+ if (folder.length === 0) return true;
581
+ return entry.slug === folder || entry.slug.startsWith(`${folder}/`);
582
+ }
583
+ case "link": {
584
+ const target = manifest.contentIndex.get(source.value.toLowerCase());
585
+ if (!target) return false;
586
+ const sources = manifest.incomingLinks.get(target) ?? [];
587
+ return sources.includes(entry.slug);
588
+ }
589
+ default:
590
+ return false;
591
+ }
592
+ }
593
+ function evaluateDataviewExpression(expression, scope) {
594
+ switch (expression.kind) {
595
+ case "literal":
596
+ return expression.value;
597
+ case "field":
598
+ return readDataviewField(scope.entry, expression.path, scope.manifest);
599
+ case "not":
600
+ return !isTruthy(evaluateDataviewExpression(expression.operand, scope));
601
+ case "and":
602
+ return isTruthy(evaluateDataviewExpression(expression.left, scope)) && isTruthy(evaluateDataviewExpression(expression.right, scope));
603
+ case "or":
604
+ return isTruthy(evaluateDataviewExpression(expression.left, scope)) || isTruthy(evaluateDataviewExpression(expression.right, scope));
605
+ case "compare":
606
+ return compareValues(
607
+ expression.operator,
608
+ evaluateDataviewExpression(expression.left, scope),
609
+ evaluateDataviewExpression(expression.right, scope)
610
+ );
611
+ case "call":
612
+ return callFunction(expression, scope);
613
+ default:
614
+ return void 0;
615
+ }
616
+ }
617
+ function callFunction(expression, scope) {
618
+ const args = expression.args.map(
619
+ (arg) => evaluateDataviewExpression(arg, scope)
620
+ );
621
+ switch (expression.name) {
622
+ case "contains":
623
+ return containsValue(args[0], args[1]);
624
+ case "date":
625
+ return toDataviewTime(args[0]);
626
+ case "number": {
627
+ const numeric = toNumber(args[0]);
628
+ return numeric === null ? null : numeric;
629
+ }
630
+ case "string":
631
+ return args[0] === void 0 || args[0] === null ? null : String(args[0]);
632
+ default:
633
+ throw new DataviewEvaluationError(
634
+ `unsupported function \`${expression.name}()\` in WHERE.`
635
+ );
636
+ }
637
+ }
638
+ function readDataviewField(entry, path, _manifest) {
639
+ if (path.length === 0) return void 0;
640
+ const [head, ...rest] = path;
641
+ if (head === "file") return readFileField(entry, rest);
642
+ if (rest.length === 0 && (head === "title" || head === "tags")) {
643
+ if (head === "title") return entry.title;
644
+ return entry.tags;
645
+ }
646
+ return readNested(entry.frontmatter, path);
647
+ }
648
+ function readFileField(entry, path) {
649
+ const field = path[0];
650
+ if (!field) return void 0;
651
+ switch (field) {
652
+ case "name":
653
+ case "title":
654
+ return entry.title;
655
+ case "slug":
656
+ case "path":
657
+ return entry.slug;
658
+ case "folder":
659
+ return folderOf(entry.slug);
660
+ case "link":
661
+ case "permalink":
662
+ case "url":
663
+ return entry.permalink;
664
+ case "tags":
665
+ return entry.tags;
666
+ case "date":
667
+ return entry.frontmatter.date ?? entry.frontmatter.created ?? entry.frontmatter.published ?? entry.frontmatter.updated;
668
+ case "created":
669
+ return entry.frontmatter.created;
670
+ case "updated":
671
+ return entry.frontmatter.updated;
672
+ case "published":
673
+ return entry.frontmatter.published;
674
+ default:
675
+ return void 0;
676
+ }
677
+ }
678
+ function readNested(value, path) {
679
+ let current = value;
680
+ for (const key of path) {
681
+ if (current === null || typeof current !== "object") return void 0;
682
+ current = current[key];
683
+ }
684
+ return current;
685
+ }
686
+ function isTruthy(value) {
687
+ if (value === null || value === void 0) return false;
688
+ if (typeof value === "boolean") return value;
689
+ if (typeof value === "number") return Number.isFinite(value) && value !== 0;
690
+ if (typeof value === "string") return value.trim().length > 0;
691
+ if (Array.isArray(value)) return value.length > 0;
692
+ if (value instanceof Date) return Number.isFinite(value.getTime());
693
+ return true;
694
+ }
695
+ function compareValues(operator, left, right) {
696
+ if (operator === "=") return looseEquals(left, right);
697
+ if (operator === "!=") return !looseEquals(left, right);
698
+ const order = compareOrder(left, right);
699
+ if (order === null) return false;
700
+ switch (operator) {
701
+ case ">":
702
+ return order > 0;
703
+ case "<":
704
+ return order < 0;
705
+ case ">=":
706
+ return order >= 0;
707
+ case "<=":
708
+ return order <= 0;
709
+ }
710
+ }
711
+ function looseEquals(left, right) {
712
+ if (left === null || left === void 0 || right === null || right === void 0) {
713
+ return (left ?? null) === (right ?? null);
714
+ }
715
+ const leftValue = left instanceof Date ? left.getTime() : left;
716
+ const rightValue = right instanceof Date ? right.getTime() : right;
717
+ if (typeof leftValue === "boolean" || typeof rightValue === "boolean") {
718
+ return leftValue === rightValue;
719
+ }
720
+ const leftNumber = toNumber(leftValue);
721
+ const rightNumber = toNumber(rightValue);
722
+ if (leftNumber !== null && rightNumber !== null) {
723
+ return leftNumber === rightNumber;
724
+ }
725
+ return String(leftValue).toLowerCase() === String(rightValue).toLowerCase();
726
+ }
727
+ function compareOrder(left, right) {
728
+ const leftValue = normalizeComparable(left);
729
+ const rightValue = normalizeComparable(right);
730
+ if (leftValue === null || rightValue === null) return null;
731
+ if (typeof leftValue === "number" && typeof rightValue === "number") {
732
+ return leftValue < rightValue ? -1 : leftValue > rightValue ? 1 : 0;
733
+ }
734
+ const leftText = String(leftValue).toLowerCase();
735
+ const rightText = String(rightValue).toLowerCase();
736
+ return leftText < rightText ? -1 : leftText > rightText ? 1 : 0;
737
+ }
738
+ function normalizeComparable(value) {
739
+ if (value === null || value === void 0) return null;
740
+ if (value instanceof Date) {
741
+ return Number.isFinite(value.getTime()) ? value.getTime() : null;
742
+ }
743
+ if (typeof value === "number") {
744
+ return Number.isFinite(value) ? value : null;
745
+ }
746
+ if (typeof value === "boolean") return value ? 1 : 0;
747
+ if (typeof value === "string") {
748
+ const trimmed = value.trim();
749
+ if (trimmed.length > 0 && /^-?\d+(?:\.\d+)?$/.test(trimmed)) {
750
+ return Number(trimmed);
751
+ }
752
+ return value;
753
+ }
754
+ return String(value);
755
+ }
756
+ function containsValue(container, expected) {
757
+ if (container === null || container === void 0) return false;
758
+ if (Array.isArray(container)) {
759
+ return container.some((item) => looseTagEquals(item, expected));
760
+ }
761
+ if (typeof container === "string") {
762
+ if (expected === null || expected === void 0) return false;
763
+ return container.toLowerCase().includes(String(expected).toLowerCase());
764
+ }
765
+ return false;
766
+ }
767
+ function looseTagEquals(left, right) {
768
+ if (typeof left === "string" && typeof right === "string") {
769
+ return stripHash(left).toLowerCase() === stripHash(right).toLowerCase();
770
+ }
771
+ return looseEquals(left, right);
772
+ }
773
+ function toNumber(value) {
774
+ if (value === null || value === void 0) return null;
775
+ if (typeof value === "number") {
776
+ return Number.isFinite(value) ? value : null;
777
+ }
778
+ if (typeof value === "boolean") return value ? 1 : 0;
779
+ if (value instanceof Date) {
780
+ return Number.isFinite(value.getTime()) ? value.getTime() : null;
781
+ }
782
+ if (typeof value === "string") {
783
+ const trimmed = value.trim();
784
+ if (trimmed.length === 0) return null;
785
+ const parsed = Number(trimmed);
786
+ return Number.isFinite(parsed) ? parsed : null;
787
+ }
788
+ return null;
789
+ }
790
+ function toDataviewTime(value) {
791
+ if (value === null || value === void 0) return null;
792
+ if (value instanceof Date) {
793
+ return Number.isFinite(value.getTime()) ? value.getTime() : null;
794
+ }
795
+ if (typeof value === "number") {
796
+ return Number.isFinite(value) ? value : null;
797
+ }
798
+ if (typeof value !== "string") return null;
799
+ const trimmed = value.trim();
800
+ if (trimmed.length === 0) return null;
801
+ const parsed = Date.parse(trimmed);
802
+ return Number.isNaN(parsed) ? null : parsed;
803
+ }
804
+ function sortEntries(entries, sort, manifest) {
805
+ return [...entries].sort((left, right) => {
806
+ for (const key of sort) {
807
+ const path = key.field.split(".");
808
+ const leftValue = readDataviewField(left, path, manifest);
809
+ const rightValue = readDataviewField(right, path, manifest);
810
+ if (leftValue === void 0 && rightValue === void 0) continue;
811
+ if (leftValue === void 0) return 1;
812
+ if (rightValue === void 0) return -1;
813
+ const order = compareOrder(leftValue, rightValue);
814
+ if (order === null) continue;
815
+ if (order !== 0) return key.order === "desc" ? -order : order;
816
+ }
817
+ return 0;
818
+ });
819
+ }
820
+ function groupEntries(entries, field, manifest) {
821
+ const groups = /* @__PURE__ */ new Map();
822
+ const path = field.split(".");
823
+ for (const entry of entries) {
824
+ const key = formatGroupKey(readDataviewField(entry, path, manifest));
825
+ const bucket = groups.get(key) ?? [];
826
+ bucket.push(entry);
827
+ groups.set(key, bucket);
828
+ }
829
+ return [...groups.entries()].map(([key, bucket]) => ({
830
+ key,
831
+ entries: bucket
832
+ }));
833
+ }
834
+ function formatGroupKey(value) {
835
+ if (value === null || value === void 0) return "(none)";
836
+ if (value instanceof Date) {
837
+ return Number.isFinite(value.getTime()) ? value.toISOString().slice(0, 10) : "(none)";
838
+ }
839
+ if (Array.isArray(value)) return value.map((item) => String(item)).join(", ");
840
+ return String(value);
841
+ }
842
+ function stripHash(value) {
843
+ return value.startsWith("#") ? value.slice(1) : value;
844
+ }
845
+ function normalizeFolder(value) {
846
+ return value.trim().replace(/^\/+|\/+$/g, "");
847
+ }
848
+ function folderOf(slug) {
849
+ const index = slug.lastIndexOf("/");
850
+ return index < 0 ? "" : slug.slice(0, index);
851
+ }
852
+
853
+ // src/render.ts
854
+ import {
855
+ escapeHtml,
856
+ escapeHtmlAttribute
857
+ } from "@riebeckite/core";
858
+ var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
859
+ var TASK_ITEM_PATTERN = /<li class="task-list-item">\s*(?:<p>)?<input type="checkbox"([^>]*)>([\s\S]*?)(?:<\/p>)?\s*<\/li>/g;
860
+ var LINK_FIELDS = /* @__PURE__ */ new Set([
861
+ "file.link",
862
+ "file.permalink",
863
+ "file.url",
864
+ "link",
865
+ "permalink",
866
+ "url"
867
+ ]);
868
+ var TITLE_FIELDS = /* @__PURE__ */ new Set(["file.name", "file.title", "title", "name"]);
869
+ var MONTH_NAMES = [
870
+ "January",
871
+ "February",
872
+ "March",
873
+ "April",
874
+ "May",
875
+ "June",
876
+ "July",
877
+ "August",
878
+ "September",
879
+ "October",
880
+ "November",
881
+ "December"
882
+ ];
883
+ var WEEKDAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
884
+ function renderDataview(selection, spec, options, source) {
885
+ const className = options.className;
886
+ const body = renderBody(selection, spec, options);
887
+ const fallback = renderFallback(source, className, options.hideFallback);
888
+ return `<div class="${escapeHtmlAttribute(className)}" data-dataview data-dataview-type="${spec.type}">${body}${fallback}</div>`;
889
+ }
890
+ function renderDataviewError(message, source, options) {
891
+ const className = options.className;
892
+ const fallback = renderFallback(source, className, options.hideFallback);
893
+ return `<div class="${escapeHtmlAttribute(className)} ${escapeHtmlAttribute(`${className}--error`)}" data-dataview role="status"><p class="${escapeHtmlAttribute(`${className}__error`)}">${escapeHtml(message)}</p>${fallback}</div>`;
894
+ }
895
+ function renderBody(selection, spec, options) {
896
+ switch (spec.type) {
897
+ case "table":
898
+ return renderTable(selection, spec, options);
899
+ case "task":
900
+ return renderTasks(selection, options);
901
+ case "calendar":
902
+ return renderCalendar(selection, spec, options);
903
+ default:
904
+ return renderList(selection, spec, options);
905
+ }
906
+ }
907
+ function renderList(selection, spec, options) {
908
+ const className = options.className;
909
+ const sections = groupedSections(selection);
910
+ let output = "";
911
+ let total = 0;
912
+ for (const section of sections) {
913
+ if (section.entries.length === 0) continue;
914
+ const items = section.entries.map((entry) => {
915
+ const meta = spec.expression ? renderMeta(entry, spec.expression, className) : "";
916
+ return `<li class="${escapeHtmlAttribute(`${className}__item`)}">${renderEntryLink(entry, className)}${meta}</li>`;
917
+ }).join("");
918
+ total += section.entries.length;
919
+ output += `${renderGroupHeading(section.key, className)}<ul class="${escapeHtmlAttribute(`${className}__list`)}">${items}</ul>`;
920
+ }
921
+ return total === 0 ? renderEmpty(className) : output;
922
+ }
923
+ function renderTable(selection, spec, options) {
924
+ const className = options.className;
925
+ const columns = spec.columns.length > 0 ? spec.columns : [{ field: "file.link" }];
926
+ const sections = groupedSections(selection);
927
+ let rows = "";
928
+ let total = 0;
929
+ for (const section of sections) {
930
+ if (section.entries.length === 0) continue;
931
+ if (section.key !== null) {
932
+ rows += `<tr class="${escapeHtmlAttribute(`${className}__group-row`)}"><th class="${escapeHtmlAttribute(`${className}__group-cell`)}" colspan="${columns.length}">${escapeHtml(section.key)}</th></tr>`;
933
+ }
934
+ for (const entry of section.entries) {
935
+ total += 1;
936
+ const cells = columns.map(
937
+ (column) => `<td class="${escapeHtmlAttribute(`${className}__cell`)}">${renderColumnValue(entry, column, className)}</td>`
938
+ ).join("");
939
+ rows += `<tr class="${escapeHtmlAttribute(`${className}__row`)}">${cells}</tr>`;
940
+ }
941
+ }
942
+ if (total === 0) return renderEmpty(className);
943
+ const head = columns.map(
944
+ (column) => `<th class="${escapeHtmlAttribute(`${className}__heading`)}" scope="col">${escapeHtml(columnLabel(column))}</th>`
945
+ ).join("");
946
+ return `<table class="${escapeHtmlAttribute(`${className}__table`)}"><thead><tr>${head}</tr></thead><tbody>${rows}</tbody></table>`;
947
+ }
948
+ function renderTasks(selection, options) {
949
+ const className = options.className;
950
+ const sections = groupedSections(selection);
951
+ let output = "";
952
+ let total = 0;
953
+ for (const section of sections) {
954
+ const items = [];
955
+ for (const entry of section.entries) {
956
+ for (const task of extractTasks(entry)) {
957
+ items.push(renderTaskItem(task, entry, className));
958
+ total += 1;
959
+ }
960
+ }
961
+ if (items.length === 0) continue;
962
+ output += `${renderGroupHeading(section.key, className)}<ul class="${escapeHtmlAttribute(`${className}__tasks`)}">${items.join("")}</ul>`;
963
+ }
964
+ return total === 0 ? renderEmpty(className) : output;
965
+ }
966
+ function renderCalendar(selection, spec, options) {
967
+ const className = options.className;
968
+ const field = spec.expression ?? "date";
969
+ const path = field.split(".");
970
+ const dated = selection.entries.map((entry) => ({
971
+ entry,
972
+ time: toDataviewTime(readDataviewField(entry, path))
973
+ })).filter(
974
+ (item) => item.time !== null
975
+ );
976
+ if (dated.length === 0) return renderEmpty(className);
977
+ const latest = Math.max(...dated.map((item) => item.time));
978
+ const base = new Date(latest);
979
+ const year = base.getUTCFullYear();
980
+ const month = base.getUTCMonth();
981
+ const byDay = /* @__PURE__ */ new Map();
982
+ for (const item of dated) {
983
+ const date = new Date(item.time);
984
+ if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month) continue;
985
+ const day = date.getUTCDate();
986
+ const bucket = byDay.get(day) ?? [];
987
+ bucket.push(item.entry);
988
+ byDay.set(day, bucket);
989
+ }
990
+ const firstWeekday = new Date(Date.UTC(year, month, 1)).getUTCDay();
991
+ const daysInMonth = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
992
+ const header = WEEKDAY_NAMES.map(
993
+ (name) => `<th class="${escapeHtmlAttribute(`${className}__calendar-weekday`)}" scope="col">${escapeHtml(name)}</th>`
994
+ ).join("");
995
+ const cells = [];
996
+ for (let index = 0; index < firstWeekday; index += 1) {
997
+ cells.push(
998
+ `<td class="${escapeHtmlAttribute(`${className}__calendar-cell ${className}__calendar-cell--empty`)}"></td>`
999
+ );
1000
+ }
1001
+ for (let day = 1; day <= daysInMonth; day += 1) {
1002
+ const entries = byDay.get(day) ?? [];
1003
+ const links = entries.map((entry) => renderEntryLink(entry, className)).join(" ");
1004
+ cells.push(
1005
+ `<td class="${escapeHtmlAttribute(`${className}__calendar-cell`)}"><span class="${escapeHtmlAttribute(`${className}__calendar-day`)}">${day}</span>${links ? `<span class="${escapeHtmlAttribute(`${className}__calendar-links`)}">${links}</span>` : ""}</td>`
1006
+ );
1007
+ }
1008
+ while (cells.length % 7 !== 0) {
1009
+ cells.push(
1010
+ `<td class="${escapeHtmlAttribute(`${className}__calendar-cell ${className}__calendar-cell--empty`)}"></td>`
1011
+ );
1012
+ }
1013
+ const rows = [];
1014
+ for (let index = 0; index < cells.length; index += 7) {
1015
+ rows.push(`<tr>${cells.slice(index, index + 7).join("")}</tr>`);
1016
+ }
1017
+ return `<table class="${escapeHtmlAttribute(`${className}__calendar`)}"><caption class="${escapeHtmlAttribute(`${className}__calendar-caption`)}">${escapeHtml(`${MONTH_NAMES[month]} ${year}`)}</caption><thead><tr>${header}</tr></thead><tbody>${rows.join("")}</tbody></table>`;
1018
+ }
1019
+ function groupedSections(selection) {
1020
+ if (selection.grouped) {
1021
+ return selection.groups.map((group) => ({
1022
+ key: group.key,
1023
+ entries: group.entries
1024
+ }));
1025
+ }
1026
+ return [{ key: null, entries: selection.entries }];
1027
+ }
1028
+ function renderGroupHeading(key, className) {
1029
+ if (key === null) return "";
1030
+ return `<p class="${escapeHtmlAttribute(`${className}__group`)}">${escapeHtml(key)}</p>`;
1031
+ }
1032
+ function renderColumnValue(entry, column, className) {
1033
+ return renderFieldValue(entry, column.field, className);
1034
+ }
1035
+ function renderMeta(entry, field, className) {
1036
+ const value = renderFieldValue(entry, field.trim(), className);
1037
+ if (!value) return "";
1038
+ return ` <span class="${escapeHtmlAttribute(`${className}__meta`)}">${value}</span>`;
1039
+ }
1040
+ function renderFieldValue(entry, field, className) {
1041
+ const normalized = field.trim();
1042
+ if (LINK_FIELDS.has(normalized) || TITLE_FIELDS.has(normalized)) {
1043
+ return renderEntryLink(entry, className);
1044
+ }
1045
+ if (normalized === "file.tags" || normalized === "tags") {
1046
+ return renderTags(entry.tags, className);
1047
+ }
1048
+ const value = readDataviewField(entry, normalized.split("."));
1049
+ return escapeHtml(formatValue(value));
1050
+ }
1051
+ function renderEntryLink(entry, className) {
1052
+ return `<a class="${escapeHtmlAttribute(`${className}__link`)}" href="${escapeHtmlAttribute(entry.permalink)}">${escapeHtml(entry.title)}</a>`;
1053
+ }
1054
+ function renderTags(tags, className) {
1055
+ return tags.map(
1056
+ (tag) => `<span class="${escapeHtmlAttribute(`${className}__tag`)}">#${escapeHtml(tag)}</span>`
1057
+ ).join(" ");
1058
+ }
1059
+ function extractTasks(entry) {
1060
+ const tasks = [];
1061
+ TASK_ITEM_PATTERN.lastIndex = 0;
1062
+ for (let match = TASK_ITEM_PATTERN.exec(entry.html); match !== null; match = TASK_ITEM_PATTERN.exec(entry.html)) {
1063
+ const attributes = match[1] ?? "";
1064
+ const text = (match[2] ?? "").trim();
1065
+ tasks.push({ checked: /\bchecked\b/.test(attributes), text });
1066
+ }
1067
+ return tasks;
1068
+ }
1069
+ function renderTaskItem(task, entry, className) {
1070
+ const status = task.checked ? "x" : " ";
1071
+ return `<li class="${escapeHtmlAttribute(`${className}__task`)}" data-task="${status}"><span class="${escapeHtmlAttribute(`${className}__task-text`)}">${task.text}</span> <a class="${escapeHtmlAttribute(`${className}__task-link`)}" href="${escapeHtmlAttribute(entry.permalink)}">${escapeHtml(entry.title)}</a></li>`;
1072
+ }
1073
+ function renderFallback(source, className, hideFallback) {
1074
+ if (hideFallback) return "";
1075
+ const trimmed = source.trim();
1076
+ if (trimmed.length === 0) return "";
1077
+ return `<details class="${escapeHtmlAttribute(`${className}__fallback`)}"><summary class="${escapeHtmlAttribute(`${className}__fallback-summary`)}">Dataview query</summary><pre class="${escapeHtmlAttribute(`${className}__fallback-code`)}"><code>${escapeHtml(trimmed)}</code></pre></details>`;
1078
+ }
1079
+ function renderEmpty(className) {
1080
+ return `<p class="${escapeHtmlAttribute(`${className}__empty`)}">No matching content.</p>`;
1081
+ }
1082
+ function columnLabel(column) {
1083
+ return column.label ?? column.field;
1084
+ }
1085
+ function formatValue(value) {
1086
+ if (value === void 0 || value === null) return "";
1087
+ if (value instanceof Date) return formatDate(value);
1088
+ if (Array.isArray(value)) {
1089
+ return value.map((item) => formatValue(item)).join(", ");
1090
+ }
1091
+ if (typeof value === "string") {
1092
+ return DATE_ONLY.test(value.trim()) ? value.trim() : value;
1093
+ }
1094
+ return String(value);
1095
+ }
1096
+ function formatDate(value) {
1097
+ const time = value.getTime();
1098
+ if (!Number.isFinite(time)) return "";
1099
+ return value.toISOString().slice(0, 10);
1100
+ }
1101
+
1102
+ // src/types.ts
1103
+ var DEFAULT_DATAVIEW_CLASS_NAME = "rb-dataview";
1104
+ var DEFAULT_DATAVIEW_LANGUAGE = "dataview";
1105
+ function resolveDataviewOptions(options = {}) {
1106
+ const className = options.className?.trim();
1107
+ const language = options.language?.trim();
1108
+ const limit = options.limit;
1109
+ return {
1110
+ className: className ? className : DEFAULT_DATAVIEW_CLASS_NAME,
1111
+ language: language ? language : DEFAULT_DATAVIEW_LANGUAGE,
1112
+ hideFallback: options.hideFallback === true,
1113
+ limit: typeof limit === "number" && Number.isFinite(limit) && limit >= 0 ? Math.floor(limit) : null
1114
+ };
1115
+ }
1116
+
1117
+ // src/runtime.ts
1118
+ var DATAVIEWJS_MARKER = "language-dataviewjs";
1119
+ var PLUGIN_NAME = "dataview";
1120
+ function createDataviewRuntime(options = {}) {
1121
+ const resolved = resolveDataviewOptions(options);
1122
+ const tracked = /* @__PURE__ */ new Map();
1123
+ return {
1124
+ track(slug, content) {
1125
+ tracked.set(slug, content);
1126
+ },
1127
+ resolve(manifest, diagnostics) {
1128
+ for (const entry of manifest.entries) {
1129
+ if (!entry.html.includes(DATAVIEW_ATTRIBUTE) && !entry.html.includes(DATAVIEWJS_MARKER)) {
1130
+ continue;
1131
+ }
1132
+ const html = replacePlaceholders(entry, manifest, resolved, diagnostics);
1133
+ if (html === entry.html) continue;
1134
+ entry.html = html;
1135
+ const content = tracked.get(entry.slug);
1136
+ if (content) content.html = html;
1137
+ }
1138
+ }
1139
+ };
1140
+ }
1141
+ function replacePlaceholders(entry, manifest, options, diagnostics) {
1142
+ if (entry.html.includes(DATAVIEWJS_MARKER)) {
1143
+ diagnostics.push({
1144
+ code: "dataview-unsupported-language",
1145
+ severity: "warning",
1146
+ pluginName: PLUGIN_NAME,
1147
+ slug: entry.slug,
1148
+ message: "DataviewJS (`dataviewjs`) blocks are not supported and were left as code blocks."
1149
+ });
1150
+ }
1151
+ if (!entry.html.includes(DATAVIEW_ATTRIBUTE)) return entry.html;
1152
+ const pattern = createDataviewPlaceholderPattern();
1153
+ return entry.html.replace(pattern, (_match, encoded) => {
1154
+ let source;
1155
+ try {
1156
+ source = decodeDataviewSource(encoded);
1157
+ } catch (error) {
1158
+ const message = `Dataview block in \`${entry.slug}\` could not be decoded (${formatError(error)}).`;
1159
+ reportInvalid(entry, diagnostics, message);
1160
+ return renderDataviewError(message, "", options);
1161
+ }
1162
+ const parsed = parseDataview(source);
1163
+ if (parsed.status === "error") {
1164
+ const message = `Dataview block in \`${entry.slug}\` ${parsed.message}`;
1165
+ reportInvalid(entry, diagnostics, message);
1166
+ return renderDataviewError(message, source, options);
1167
+ }
1168
+ try {
1169
+ const selection = selectDataviewEntries(
1170
+ parsed.spec,
1171
+ manifest,
1172
+ options.limit
1173
+ );
1174
+ return renderDataview(selection, parsed.spec, options, source);
1175
+ } catch (error) {
1176
+ const message = `Dataview block in \`${entry.slug}\` is unsupported: ${formatError(error)}`;
1177
+ reportInvalid(entry, diagnostics, message);
1178
+ return renderDataviewError(message, source, options);
1179
+ }
1180
+ });
1181
+ }
1182
+ function reportInvalid(entry, diagnostics, message) {
1183
+ diagnostics.push({
1184
+ code: "dataview-invalid",
1185
+ severity: "error",
1186
+ pluginName: PLUGIN_NAME,
1187
+ slug: entry.slug,
1188
+ message
1189
+ });
1190
+ }
1191
+ function formatError(error) {
1192
+ return error instanceof Error ? error.message : String(error);
1193
+ }
1194
+
1195
+ // index.ts
1196
+ function dataviewPlugin(options = {}) {
1197
+ const language = options.language ?? "dataview";
1198
+ const runtime = createDataviewRuntime(options);
1199
+ return definePlugin({
1200
+ name: "dataview",
1201
+ options,
1202
+ provides: ["content.dataview"],
1203
+ validateOptions: validateDataviewOptions,
1204
+ extendMarkdownPipeline: (pipeline) => {
1205
+ pipeline.use(remarkDataview, { language });
1206
+ },
1207
+ onPostProcessed: (context) => {
1208
+ runtime.track(context.slug, context.content);
1209
+ },
1210
+ onManifestCreated: (context) => {
1211
+ runtime.resolve(context.manifest, context.diagnostics);
1212
+ },
1213
+ assets: [createStyleAsset("dataview")]
1214
+ });
1215
+ }
1216
+ var dataview = dataviewPlugin;
1217
+ function validateDataviewOptions(options) {
1218
+ if (!options) return [];
1219
+ const issues = [];
1220
+ if (options.className !== void 0 && (typeof options.className !== "string" || options.className.trim() === "")) {
1221
+ issues.push({ path: "className", message: "Expected a non-empty string." });
1222
+ }
1223
+ if (options.language !== void 0 && (typeof options.language !== "string" || options.language.trim() === "")) {
1224
+ issues.push({ path: "language", message: "Expected a non-empty string." });
1225
+ }
1226
+ if (options.hideFallback !== void 0 && typeof options.hideFallback !== "boolean") {
1227
+ issues.push({ path: "hideFallback", message: "Expected a boolean." });
1228
+ }
1229
+ if (options.limit !== void 0 && (!Number.isFinite(options.limit) || options.limit < 0)) {
1230
+ issues.push({
1231
+ path: "limit",
1232
+ message: "Expected a non-negative finite number."
1233
+ });
1234
+ }
1235
+ return issues;
1236
+ }
1237
+ export {
1238
+ DATAVIEW_ATTRIBUTE,
1239
+ DEFAULT_DATAVIEW_CLASS_NAME,
1240
+ DEFAULT_DATAVIEW_LANGUAGE,
1241
+ DataviewEvaluationError,
1242
+ DataviewParseError,
1243
+ createDataviewPlaceholder,
1244
+ createDataviewPlaceholderPattern,
1245
+ dataview,
1246
+ dataviewPlugin,
1247
+ evaluateDataviewExpression,
1248
+ isTruthy,
1249
+ matchesDataviewFrom,
1250
+ parseDataview,
1251
+ readDataviewField,
1252
+ remarkDataview,
1253
+ renderDataview,
1254
+ renderDataviewError,
1255
+ resolveDataviewOptions,
1256
+ selectDataviewEntries,
1257
+ toDataviewTime
1258
+ };