@lapis-notes/bases 0.1.0

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.
Files changed (155) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE.md +8 -0
  3. package/README.md +20 -0
  4. package/dist/bases-plugin.d.ts +10 -0
  5. package/dist/bases-plugin.js +118 -0
  6. package/dist/bases-view/app-database-query-source.d.ts +30 -0
  7. package/dist/bases-view/app-database-query-source.js +248 -0
  8. package/dist/bases-view/bases.svelte.d.ts +569 -0
  9. package/dist/bases-view/bases.svelte.js +910 -0
  10. package/dist/bases-view/card-view/components/card-placeholder.svelte +68 -0
  11. package/dist/bases-view/card-view/components/card-placeholder.svelte.d.ts +7 -0
  12. package/dist/bases-view/card-view/components/card.svelte +289 -0
  13. package/dist/bases-view/card-view/components/card.svelte.d.ts +7 -0
  14. package/dist/bases-view/card-view/index.d.ts +11 -0
  15. package/dist/bases-view/card-view/index.js +55 -0
  16. package/dist/bases-view/cell-editor-options.d.ts +2 -0
  17. package/dist/bases-view/cell-editor-options.js +26 -0
  18. package/dist/bases-view/cell-editor.svelte +206 -0
  19. package/dist/bases-view/cell-editor.svelte.d.ts +12 -0
  20. package/dist/bases-view/cell.svelte +132 -0
  21. package/dist/bases-view/cell.svelte.d.ts +16 -0
  22. package/dist/bases-view/column-definition.d.ts +22 -0
  23. package/dist/bases-view/column-definition.js +149 -0
  24. package/dist/bases-view/columns.d.ts +15 -0
  25. package/dist/bases-view/columns.js +103 -0
  26. package/dist/bases-view/components/Tag.svelte +14 -0
  27. package/dist/bases-view/components/Tag.svelte.d.ts +8 -0
  28. package/dist/bases-view/components/autocomplete.svelte +45 -0
  29. package/dist/bases-view/components/autocomplete.svelte.d.ts +17 -0
  30. package/dist/bases-view/components/card-image.svelte +121 -0
  31. package/dist/bases-view/components/card-image.svelte.d.ts +11 -0
  32. package/dist/bases-view/components/column-visibility.svelte +388 -0
  33. package/dist/bases-view/components/column-visibility.svelte.d.ts +13 -0
  34. package/dist/bases-view/components/datetime.svelte +35 -0
  35. package/dist/bases-view/components/datetime.svelte.d.ts +7 -0
  36. package/dist/bases-view/components/file.svelte +31 -0
  37. package/dist/bases-view/components/file.svelte.d.ts +8 -0
  38. package/dist/bases-view/components/filters/filter-editor.svelte +102 -0
  39. package/dist/bases-view/components/filters/filter-editor.svelte.d.ts +10 -0
  40. package/dist/bases-view/components/filters/filter-group.svelte +142 -0
  41. package/dist/bases-view/components/filters/filter-group.svelte.d.ts +11 -0
  42. package/dist/bases-view/components/filters/filter-properties.svelte +77 -0
  43. package/dist/bases-view/components/filters/filter-properties.svelte.d.ts +8 -0
  44. package/dist/bases-view/components/filters/filter-root.svelte +112 -0
  45. package/dist/bases-view/components/filters/filter-root.svelte.d.ts +12 -0
  46. package/dist/bases-view/components/filters/filter-row.svelte +351 -0
  47. package/dist/bases-view/components/filters/filter-row.svelte.d.ts +11 -0
  48. package/dist/bases-view/components/group/group-root.svelte +120 -0
  49. package/dist/bases-view/components/group/group-root.svelte.d.ts +8 -0
  50. package/dist/bases-view/components/limit.svelte +100 -0
  51. package/dist/bases-view/components/limit.svelte.d.ts +12 -0
  52. package/dist/bases-view/components/query-editor.svelte +230 -0
  53. package/dist/bases-view/components/query-editor.svelte.d.ts +15 -0
  54. package/dist/bases-view/components/sort/sort-direction-labels.d.ts +9 -0
  55. package/dist/bases-view/components/sort/sort-direction-labels.js +25 -0
  56. package/dist/bases-view/components/sort/sort-root.svelte +239 -0
  57. package/dist/bases-view/components/sort/sort-root.svelte.d.ts +11 -0
  58. package/dist/bases-view/components/sort/sort-row.svelte +190 -0
  59. package/dist/bases-view/components/sort/sort-row.svelte.d.ts +15 -0
  60. package/dist/bases-view/components/summary-strip.svelte +56 -0
  61. package/dist/bases-view/components/summary-strip.svelte.d.ts +8 -0
  62. package/dist/bases-view/components/tag-chip.svelte +47 -0
  63. package/dist/bases-view/components/tag-chip.svelte.d.ts +12 -0
  64. package/dist/bases-view/components/views.svelte +178 -0
  65. package/dist/bases-view/components/views.svelte.d.ts +12 -0
  66. package/dist/bases-view/data-table-adapter/data-table.svelte.d.ts +45 -0
  67. package/dist/bases-view/data-table-adapter/data-table.svelte.js +112 -0
  68. package/dist/bases-view/data-table-adapter/flex-render.svelte +43 -0
  69. package/dist/bases-view/data-table-adapter/flex-render.svelte.d.ts +30 -0
  70. package/dist/bases-view/data-table-adapter/index.d.ts +3 -0
  71. package/dist/bases-view/data-table-adapter/index.js +3 -0
  72. package/dist/bases-view/data-table-adapter/render-helpers.d.ts +101 -0
  73. package/dist/bases-view/data-table-adapter/render-helpers.js +110 -0
  74. package/dist/bases-view/db.d.ts +31 -0
  75. package/dist/bases-view/db.js +174 -0
  76. package/dist/bases-view/document-core.d.ts +2 -0
  77. package/dist/bases-view/document-core.js +166 -0
  78. package/dist/bases-view/export-core.d.ts +4 -0
  79. package/dist/bases-view/export-core.js +31 -0
  80. package/dist/bases-view/file-fields-core.d.ts +31 -0
  81. package/dist/bases-view/file-fields-core.js +114 -0
  82. package/dist/bases-view/filter-parser.d.ts +27 -0
  83. package/dist/bases-view/filter-parser.js +315 -0
  84. package/dist/bases-view/functions-core.d.ts +66 -0
  85. package/dist/bases-view/functions-core.js +265 -0
  86. package/dist/bases-view/functions.d.ts +5 -0
  87. package/dist/bases-view/functions.js +107 -0
  88. package/dist/bases-view/fuzzy-search.d.ts +6 -0
  89. package/dist/bases-view/fuzzy-search.js +15 -0
  90. package/dist/bases-view/hooks/useResizeObserver.svelte.d.ts +7 -0
  91. package/dist/bases-view/hooks/useResizeObserver.svelte.js +26 -0
  92. package/dist/bases-view/index.d.ts +66 -0
  93. package/dist/bases-view/index.js +231 -0
  94. package/dist/bases-view/list-view/index.d.ts +10 -0
  95. package/dist/bases-view/list-view/index.js +17 -0
  96. package/dist/bases-view/list-view/list.svelte +156 -0
  97. package/dist/bases-view/list-view/list.svelte.d.ts +7 -0
  98. package/dist/bases-view/map-view/index.d.ts +10 -0
  99. package/dist/bases-view/map-view/index.js +17 -0
  100. package/dist/bases-view/map-view/view.svelte +28 -0
  101. package/dist/bases-view/map-view/view.svelte.d.ts +7 -0
  102. package/dist/bases-view/metadata-invalidation-core.d.ts +34 -0
  103. package/dist/bases-view/metadata-invalidation-core.js +162 -0
  104. package/dist/bases-view/metadata-type-info.d.ts +7 -0
  105. package/dist/bases-view/metadata-type-info.js +10 -0
  106. package/dist/bases-view/models.d.ts +90 -0
  107. package/dist/bases-view/models.js +133 -0
  108. package/dist/bases-view/query-result-core.d.ts +8 -0
  109. package/dist/bases-view/query-result-core.js +16 -0
  110. package/dist/bases-view/resource-url.d.ts +2 -0
  111. package/dist/bases-view/resource-url.js +21 -0
  112. package/dist/bases-view/search-core.d.ts +9 -0
  113. package/dist/bases-view/search-core.js +20 -0
  114. package/dist/bases-view/summary-core.d.ts +7 -0
  115. package/dist/bases-view/summary-core.js +17 -0
  116. package/dist/bases-view/table-view/components/div-table.svelte +464 -0
  117. package/dist/bases-view/table-view/components/div-table.svelte.d.ts +32 -0
  118. package/dist/bases-view/table-view/components/dnd-style.d.ts +3 -0
  119. package/dist/bases-view/table-view/components/dnd-style.js +6 -0
  120. package/dist/bases-view/table-view/components/sort-header.svelte +48 -0
  121. package/dist/bases-view/table-view/components/sort-header.svelte.d.ts +11 -0
  122. package/dist/bases-view/table-view/components/table-column-tracks.d.ts +14 -0
  123. package/dist/bases-view/table-view/components/table-column-tracks.js +27 -0
  124. package/dist/bases-view/table-view/components/table-header.svelte +146 -0
  125. package/dist/bases-view/table-view/components/table-header.svelte.d.ts +10 -0
  126. package/dist/bases-view/table-view/components/table-placeholder.svelte +46 -0
  127. package/dist/bases-view/table-view/components/table-placeholder.svelte.d.ts +30 -0
  128. package/dist/bases-view/table-view/components/table-virtualizer-core.d.ts +13 -0
  129. package/dist/bases-view/table-view/components/table-virtualizer-core.js +12 -0
  130. package/dist/bases-view/table-view/components/table.svelte +997 -0
  131. package/dist/bases-view/table-view/components/table.svelte.d.ts +32 -0
  132. package/dist/bases-view/table-view/index.svelte.d.ts +10 -0
  133. package/dist/bases-view/table-view/index.svelte.js +30 -0
  134. package/dist/bases-view/unknown-view/index.d.ts +10 -0
  135. package/dist/bases-view/unknown-view/index.js +19 -0
  136. package/dist/bases-view/unknown-view/view.svelte +11 -0
  137. package/dist/bases-view/unknown-view/view.svelte.d.ts +6 -0
  138. package/dist/bases-view/view-header.svelte +142 -0
  139. package/dist/bases-view/view-header.svelte.d.ts +28 -0
  140. package/dist/bases-view/view-registration-inventory.d.ts +22 -0
  141. package/dist/bases-view/view-registration-inventory.js +7 -0
  142. package/dist/bases-view/view-settings.svelte +350 -0
  143. package/dist/bases-view/view-settings.svelte.d.ts +9 -0
  144. package/dist/bases-view/view.svelte +192 -0
  145. package/dist/bases-view/view.svelte.d.ts +15 -0
  146. package/dist/bases-view-surface.svelte +31 -0
  147. package/dist/bases-view-surface.svelte.d.ts +14 -0
  148. package/dist/index.d.ts +10 -0
  149. package/dist/index.js +8 -0
  150. package/dist/serialized-write-queue.d.ts +10 -0
  151. package/dist/serialized-write-queue.js +36 -0
  152. package/dist/styles.css +2164 -0
  153. package/manifest.json +33 -0
  154. package/package.json +90 -0
  155. package/styles.css +1 -0
@@ -0,0 +1,910 @@
1
+ import { BasesViewConfig as SharedBasesViewConfig, Component, TFile, } from "@lapis-notes/api";
2
+ import { DateTime, Duration } from "luxon";
3
+ import { Context, Decimal, parseDateTime, parseDuration, } from "peaql";
4
+ import {} from "./models";
5
+ import { BasesTable, VaultFile } from "./db";
6
+ import { columnsFor, frontMatterTypesForColumns, } from "./columns";
7
+ import { generateQuery } from "./filter-parser";
8
+ import { isEqual } from "lodash-es";
9
+ import { groupEntries } from "./query-result-core";
10
+ import { filterEntriesBySearch } from "./search-core";
11
+ import { collectMetadataDependencies, } from "./metadata-invalidation-core";
12
+ import { appDatabaseRowsToVaultRecords, buildBasesAppDatabaseQuery, queryBasesAppDatabaseRows, } from "./app-database-query-source";
13
+ import { mount, unmount } from "svelte";
14
+ export { BasesViewConfig } from "@lapis-notes/api";
15
+ /**
16
+ * Represent a single "row" or file in a base.
17
+ *
18
+ * @since 1.10.0
19
+ * @public
20
+ */
21
+ export class BasesEntry {
22
+ id;
23
+ file;
24
+ context;
25
+ constructor(id, file, context = {}) {
26
+ this.id = id;
27
+ this.file = file;
28
+ this.context = context;
29
+ }
30
+ /**
31
+ * Get the value of the property.
32
+ *
33
+ * @since 1.10.0
34
+ * @throws Error if the property is a formula and cannot be evaluated.
35
+ * @public
36
+ */
37
+ getValue(propertyId) {
38
+ if (propertyId.startsWith("file.") ||
39
+ propertyId.startsWith("note.") ||
40
+ propertyId.startsWith("formula.")) {
41
+ return this.context[propertyId] ?? null;
42
+ }
43
+ return (this.context[`note.${propertyId}`] ?? this.context[propertyId] ?? null);
44
+ }
45
+ }
46
+ /**
47
+ * Container type for data which can expose functions for retrieving, comparing,
48
+ * and rendering the data. Most commonly used in conjunction with formulas for
49
+ * Bases. Values can be used as formula parameters, intermediate values, and the
50
+ * result of evaluation.
51
+ *
52
+ * @since 1.10.0
53
+ * @public
54
+ */
55
+ export class Value {
56
+ /**
57
+ * @since 1.10.0
58
+ * @public
59
+ */
60
+ static equals(a, b) {
61
+ if (!a || !b)
62
+ return a === b;
63
+ return a.equals(b);
64
+ }
65
+ /**
66
+ * @since 1.10.0
67
+ * @public
68
+ */
69
+ static looseEquals(a, b) {
70
+ if (!a || !b)
71
+ return a == b;
72
+ return a.looseEquals(b);
73
+ }
74
+ /**
75
+ * @since 1.10.0
76
+ * @public
77
+ */
78
+ equals(other) {
79
+ if (!(other instanceof Value))
80
+ return false;
81
+ return this === other;
82
+ }
83
+ /**
84
+ * @since 1.10.0
85
+ * @public
86
+ */
87
+ looseEquals(other) {
88
+ if (!(other instanceof Value))
89
+ return false;
90
+ return this == other;
91
+ }
92
+ /**
93
+ * Render this value into the provided HTMLElement.
94
+ *
95
+ * @since 1.10.0
96
+ * @public
97
+ */
98
+ renderTo(el, ctx) { }
99
+ }
100
+ /**
101
+ * Base type for all non-null {@link Values}.
102
+ *
103
+ * @since 1.10.0
104
+ * @public
105
+ */
106
+ export class NotNullValue extends Value {
107
+ }
108
+ /**
109
+ * {@link Value} which represents null. NullValue is a singleton and
110
+ * `NullValue.value` should be used instead of calling the constructor.
111
+ *
112
+ * @since 1.10.0
113
+ * @public
114
+ */
115
+ export class NullValue extends Value {
116
+ /**
117
+ * @since 1.10.0
118
+ * @public
119
+ */
120
+ static value = new NullValue();
121
+ get value() {
122
+ return null;
123
+ }
124
+ /**
125
+ * @since 1.10.0
126
+ * @public
127
+ */
128
+ toString() {
129
+ return "";
130
+ }
131
+ isTruthy() {
132
+ return false;
133
+ }
134
+ equals(other) {
135
+ return other === null || other === undefined;
136
+ }
137
+ looseEquals(other) {
138
+ return other == null || other == undefined;
139
+ }
140
+ }
141
+ /**
142
+ * Base type for {@link Values} which wrap a single primitive.
143
+ *
144
+ * @since 1.10.0
145
+ * @public
146
+ */
147
+ export class PrimitiveValue extends NotNullValue {
148
+ value;
149
+ /**
150
+ * @since 1.10.0
151
+ * @public
152
+ */
153
+ constructor(value) {
154
+ super();
155
+ this.value = value;
156
+ }
157
+ /**
158
+ * @since 1.10.0
159
+ * @public
160
+ */
161
+ toString() {
162
+ return this.value?.toString();
163
+ }
164
+ /**
165
+ * @since 1.10.0
166
+ * @public
167
+ */
168
+ isTruthy() {
169
+ return this.value ? true : false;
170
+ }
171
+ equals(other) {
172
+ if (!(other instanceof PrimitiveValue))
173
+ return false;
174
+ return this.value === other.value;
175
+ }
176
+ looseEquals(other) {
177
+ if (!(other instanceof PrimitiveValue))
178
+ return false;
179
+ return this.value == other.value;
180
+ }
181
+ }
182
+ /**
183
+ * {@link Value} wrapping a boolean.
184
+ *
185
+ * @since 1.10.0
186
+ * @public
187
+ */
188
+ export class BooleanValue extends PrimitiveValue {
189
+ /**
190
+ * @since 1.10.0
191
+ * @public
192
+ */
193
+ static type = "boolean";
194
+ looseEquals(other) {
195
+ if (other instanceof BooleanValue) {
196
+ return this.value === other.value;
197
+ }
198
+ else if (other instanceof PrimitiveValue) {
199
+ if (typeof other.value === "number") {
200
+ return other.value > 0;
201
+ }
202
+ else if (typeof other.value === "string") {
203
+ return other.value.toLowerCase() === this.toString();
204
+ }
205
+ }
206
+ return false;
207
+ }
208
+ }
209
+ /**
210
+ * {@link Value} wrapping a number.
211
+ *
212
+ * @since 1.10.0
213
+ * @public
214
+ */
215
+ export class NumberValue extends PrimitiveValue {
216
+ /**
217
+ * @since 1.10.0
218
+ * @public
219
+ */
220
+ static type = "number";
221
+ equals(other) {
222
+ return this.number === other.number;
223
+ }
224
+ looseEquals(other) {
225
+ if (other instanceof NumberValue) {
226
+ return this.number === other.number;
227
+ }
228
+ return false;
229
+ }
230
+ toString() {
231
+ return this.number.toString();
232
+ }
233
+ get number() {
234
+ if (typeof this.value === "number") {
235
+ return this.value;
236
+ }
237
+ return this.value.number;
238
+ }
239
+ }
240
+ /**
241
+ * {@link Value} wrapping a string.
242
+ *
243
+ * @since 1.10.0
244
+ * @public
245
+ */
246
+ export class StringValue extends PrimitiveValue {
247
+ /**
248
+ * @since 1.10.0
249
+ * @public
250
+ */
251
+ static type = "string";
252
+ }
253
+ export class DateValue extends NotNullValue {
254
+ value;
255
+ constructor(value) {
256
+ super();
257
+ if (value instanceof DateTime) {
258
+ this.value = value;
259
+ }
260
+ else if (value instanceof Date) {
261
+ this.value = DateTime.fromJSDate(value);
262
+ }
263
+ else if (typeof value === "number") {
264
+ this.value = DateTime.fromMillis(value);
265
+ }
266
+ else {
267
+ this.value = parseDateTime(value);
268
+ }
269
+ }
270
+ /**
271
+ * @since 1.10.0
272
+ * @public
273
+ */
274
+ toString() {
275
+ return this.value.toISO()?.substring(0, 16) ?? "";
276
+ }
277
+ /**
278
+ * @since 1.10.0
279
+ * @returns A new DateValue with any time portion in this DateValue removed.
280
+ * @public
281
+ */
282
+ dateOnly() {
283
+ return new DateValue(this.value.startOf("day"));
284
+ }
285
+ /**
286
+ * @since 1.10.0
287
+ * @returns A new {@link RelativeDateValue} based on this DateValue.
288
+ * @public
289
+ */
290
+ relative() {
291
+ return this.value.toRelative() ?? "";
292
+ }
293
+ /**
294
+ * @since 1.10.0
295
+ * @public
296
+ */
297
+ isTruthy() {
298
+ return this.value.isValid;
299
+ }
300
+ equals(other) {
301
+ if (!(other instanceof DateValue))
302
+ return false;
303
+ return this.value.toMillis() === other.value.toMillis();
304
+ }
305
+ looseEquals(other) {
306
+ if (other instanceof DateValue) {
307
+ return this.value.toMillis() === other.value.toMillis();
308
+ }
309
+ else if (other instanceof NumberValue) {
310
+ return this.value.toMillis() === other.number;
311
+ }
312
+ return false;
313
+ }
314
+ /**
315
+ * Create new DateValue from an input string.
316
+ *
317
+ * @since 1.10.0
318
+ * @example
319
+ * parseFromString("2025-12-31");
320
+ * parseFromString("2025-12-31T23:59");
321
+ * parseFromString("2025-12-31T23:59:59");
322
+ * parseFromString("2025-12-31T23:59:59Z-07");
323
+ *
324
+ * @param input - An ISO 8601 date or datetime string.
325
+ * @public
326
+ */
327
+ static parseFromString(input) {
328
+ try {
329
+ return new DateValue(parseDateTime(input));
330
+ }
331
+ catch (e) {
332
+ return null;
333
+ }
334
+ }
335
+ }
336
+ export class DurationValue extends NotNullValue {
337
+ value;
338
+ constructor(value) {
339
+ super();
340
+ this.value = value;
341
+ }
342
+ /**
343
+ * @since 1.10.0
344
+ * @public
345
+ */
346
+ toString() {
347
+ return this.value.toString();
348
+ }
349
+ /**
350
+ * @since 1.10.0
351
+ * @public
352
+ */
353
+ isTruthy() {
354
+ return this.value.isValid;
355
+ }
356
+ /**
357
+ * Modifies the provided {@DateValue} by this duration.
358
+ *
359
+ * @since 1.10.0
360
+ * @public
361
+ */
362
+ addToDate(value, subtract) {
363
+ return new DateValue(subtract ? value.value.minus(this.value) : value.value.plus(this.value));
364
+ }
365
+ /**
366
+ * Convert this duration into milliseconds.
367
+ *
368
+ * @since 1.10.0
369
+ * @public
370
+ */
371
+ getMilliseconds() {
372
+ return this.value.toMillis();
373
+ }
374
+ equals(other) {
375
+ if (!(other instanceof DurationValue))
376
+ return false;
377
+ return this.getMilliseconds() === other.getMilliseconds();
378
+ }
379
+ looseEquals(other) {
380
+ if (other instanceof DurationValue) {
381
+ return this.getMilliseconds() === other.getMilliseconds();
382
+ }
383
+ else if (other instanceof NumberValue) {
384
+ return this.getMilliseconds() === other.number;
385
+ }
386
+ return false;
387
+ }
388
+ /**
389
+ * Create a new DurationValue using an ISO 8601 duration. See
390
+ * {@link https://en.wikipedia.org/wiki/ISO_8601#Durations} for duration format
391
+ * details.
392
+ *
393
+ * @since 1.10.0
394
+ * @public
395
+ */
396
+ static parseFromString(input) {
397
+ try {
398
+ return new DurationValue(parseDuration(input));
399
+ }
400
+ catch (_) { }
401
+ return null;
402
+ }
403
+ /**
404
+ * Create a new DurationValue from milliseconds.
405
+ *
406
+ * @since 1.10.0
407
+ * @public
408
+ */
409
+ static fromMilliseconds(milliseconds) {
410
+ return new DurationValue(Duration.fromMillis(milliseconds));
411
+ }
412
+ }
413
+ export class FileValue extends NotNullValue {
414
+ file;
415
+ constructor(file) {
416
+ super();
417
+ this.file = file;
418
+ }
419
+ get value() {
420
+ return this.file;
421
+ }
422
+ /**
423
+ * @since 1.10.0
424
+ * @public
425
+ */
426
+ toString() {
427
+ return this.file.path;
428
+ }
429
+ /**
430
+ * @since 1.10.0
431
+ * @public
432
+ */
433
+ isTruthy() {
434
+ return true;
435
+ }
436
+ equals(other) {
437
+ if (!(other instanceof FileValue))
438
+ return false;
439
+ return this.file.path === other.file.path;
440
+ }
441
+ looseEquals(other) {
442
+ if (other instanceof FileValue) {
443
+ return other.file.path === this.file.path;
444
+ }
445
+ else if (other instanceof PrimitiveValue) {
446
+ return this.file.path === other.value;
447
+ }
448
+ return false;
449
+ }
450
+ }
451
+ export class ObjectValue extends NotNullValue {
452
+ value;
453
+ constructor(value) {
454
+ super();
455
+ this.value = value;
456
+ }
457
+ equals(other) {
458
+ if (!(other instanceof ObjectValue))
459
+ return false;
460
+ return isEqual(this, other);
461
+ }
462
+ looseEquals(other) {
463
+ return isEqual(this, other);
464
+ }
465
+ toString() {
466
+ return this.value.toString();
467
+ }
468
+ isTruthy() {
469
+ return true;
470
+ }
471
+ }
472
+ function unknownView(type) {
473
+ return {
474
+ name: "Unknown",
475
+ type: type ?? "unknown",
476
+ filter: { and: [] },
477
+ order: [],
478
+ sort: [],
479
+ limit: 0,
480
+ };
481
+ }
482
+ function toEntry(value, type) {
483
+ if (value instanceof Value) {
484
+ return value;
485
+ }
486
+ if (value === null || value === undefined) {
487
+ return NullValue.value;
488
+ }
489
+ else if (type === String) {
490
+ return new StringValue(value);
491
+ }
492
+ else if (type === Number || type == Decimal || typeof value === "number") {
493
+ return new NumberValue(value);
494
+ }
495
+ else if (type === DateTime || type === Date) {
496
+ return new DateValue(value);
497
+ }
498
+ else if (type === Boolean) {
499
+ return new BooleanValue(value);
500
+ }
501
+ else if (type === Duration) {
502
+ return new DurationValue(value);
503
+ }
504
+ else if (type === TFile || type == VaultFile) {
505
+ return new FileValue(value);
506
+ }
507
+ return new ObjectValue(value);
508
+ }
509
+ function convertEntries(dataset) {
510
+ const entries = [];
511
+ const [rows, types] = dataset;
512
+ for (const row of rows) {
513
+ const record = row["$rowId"];
514
+ if (!record)
515
+ continue;
516
+ const context = {};
517
+ for (const key of Object.keys(row)) {
518
+ if (key === "$rowId")
519
+ continue;
520
+ const id = key;
521
+ context[id] = toEntry(row[id], types[id]);
522
+ }
523
+ entries.push(new BasesEntry(record.id, record.file, context));
524
+ }
525
+ return entries;
526
+ }
527
+ export class QueryController extends Component {
528
+ views;
529
+ readOnly;
530
+ app;
531
+ data = $state([]);
532
+ loadError = $state(null);
533
+ doc = $state();
534
+ properties = $state({});
535
+ formulas = $state({});
536
+ searchQuery = $state("");
537
+ searchPanelOpen = $state(false);
538
+ columns = $derived(columnsFor(this.data, this.properties, this.formulas, this.app));
539
+ frontMatterTypes = $derived(frontMatterTypesForColumns(this.columns));
540
+ db = $derived(Context.create(new BasesTable("bases")
541
+ .withApp(this.app)
542
+ .withPropertyColumns(this.frontMatterTypes)
543
+ .data(() => this.data)).withDefaultTable("bases"));
544
+ selectedView = $state();
545
+ #view = $state();
546
+ prevDoc = "";
547
+ lastViewResults = new WeakMap();
548
+ query = $derived(generateQuery(this.selectedView, this.formulas, this.doc.filters));
549
+ metadataDependencies = $derived.by(() => {
550
+ return collectMetadataDependencies({
551
+ documentFilter: this.doc.filters,
552
+ viewFilter: this.selectedView?.filter,
553
+ order: (this.selectedView?.order ?? []),
554
+ sort: this.selectedView?.sort ?? [],
555
+ groupByProperty: this.selectedView?.groupBy?.property ?? null,
556
+ imageProperty: typeof this.selectedView?.image === "string"
557
+ ? this.selectedView.image
558
+ : null,
559
+ formulas: this.formulas,
560
+ });
561
+ });
562
+ reloadSignature = $derived.by(() => {
563
+ return JSON.stringify({
564
+ documentFilter: $state.snapshot(this.doc.filters),
565
+ viewFilter: $state.snapshot(this.selectedView?.filter),
566
+ sort: $state.snapshot(this.selectedView?.sort ?? []),
567
+ limit: this.selectedView?.limit ?? 0,
568
+ });
569
+ });
570
+ dataset = $state([
571
+ [],
572
+ {},
573
+ ]);
574
+ queryResult = $derived.by(() => {
575
+ const visibleProperties = (this.selectedView?.order ??
576
+ []);
577
+ const entries = filterEntriesBySearch(convertEntries(this.dataset), visibleProperties, this.searchQuery);
578
+ return new BasesQueryResult(entries, this.getAllColumns().map((it) => it.id), this.selectedView?.groupBy?.property);
579
+ });
580
+ constructor(app, document, views, onChange, readOnly = false) {
581
+ super();
582
+ this.views = views;
583
+ this.readOnly = readOnly;
584
+ this.app = app;
585
+ this.doc = document;
586
+ this.properties = this.doc.properties || {};
587
+ this.formulas = this.doc.formulas || {};
588
+ this.selectedView =
589
+ this.doc.views.find((it) => it.name === this.doc.activeView) ||
590
+ this.doc.views[0] ||
591
+ unknownView();
592
+ this.prevDoc = JSON.stringify(document);
593
+ $effect(() => {
594
+ if (!this.selectedView)
595
+ return;
596
+ this.doc.activeView = this.selectedView.name;
597
+ });
598
+ $effect(() => {
599
+ const props = {
600
+ ...$state.snapshot(this.doc),
601
+ properties: $state.snapshot(this.properties),
602
+ formulas: $state.snapshot(this.formulas),
603
+ };
604
+ const content = JSON.stringify(props);
605
+ if (content !== this.prevDoc) {
606
+ onChange(props);
607
+ this.prevDoc = content;
608
+ }
609
+ });
610
+ $effect(() => {
611
+ if (!this.#view)
612
+ return;
613
+ this.syncViewData(this.#view, this.queryResult);
614
+ });
615
+ $effect(() => {
616
+ this.reloadSignature;
617
+ void this.reload();
618
+ });
619
+ $effect(() => {
620
+ this.dataset = this.getRecords(this.query);
621
+ });
622
+ this.registerEvent(this.app.metadataCache.on("index-changed", (change) => {
623
+ if (change.reset || change.domains.includes("metadata"))
624
+ void this.reload();
625
+ }));
626
+ this.registerEvent(this.app.metadataCache.on("loaded", () => {
627
+ void this.reload();
628
+ }));
629
+ }
630
+ get view() {
631
+ return this.#view;
632
+ }
633
+ set view(view) {
634
+ this.#view = view;
635
+ }
636
+ syncViewData(view, result) {
637
+ if (this.lastViewResults.get(view) === result) {
638
+ return;
639
+ }
640
+ this.lastViewResults.set(view, result);
641
+ view.data = result;
642
+ view.onDataUpdated();
643
+ }
644
+ get viewConfig() {
645
+ return this.views.get(this.selectedView.type) ?? this.views.get("unknown");
646
+ }
647
+ getRecords(qs) {
648
+ const types = {};
649
+ try {
650
+ const [columns, data] = this.db.execute(qs);
651
+ columns.forEach((col) => {
652
+ types[col.name.description] = col.type;
653
+ });
654
+ const dataset = data.map((row) => {
655
+ return columns.reduce((acc, value, i) => {
656
+ acc[value.name.description] = row[i];
657
+ return acc;
658
+ }, {});
659
+ });
660
+ return [dataset, types];
661
+ }
662
+ catch (e) {
663
+ console.error("Error executing query", e);
664
+ return [[], types];
665
+ }
666
+ }
667
+ getAllColumns() {
668
+ return Object.values(this.columns);
669
+ }
670
+ isVisible(id) {
671
+ return this.selectedView && this.selectedView.order.includes(id);
672
+ }
673
+ toggleVisibility(id) {
674
+ if (this.selectedView) {
675
+ const index = this.selectedView.order.findIndex((it) => it === id);
676
+ if (index === -1) {
677
+ this.selectedView.order.push(id);
678
+ return;
679
+ }
680
+ this.selectedView.order.splice(index, 1);
681
+ }
682
+ }
683
+ getColumn(id) {
684
+ return this.columns[id];
685
+ }
686
+ get count() {
687
+ return this.dataset[0].length;
688
+ }
689
+ get searchCount() {
690
+ return this.queryResult.data.length;
691
+ }
692
+ toggleSearchPanel() {
693
+ this.searchPanelOpen = !this.searchPanelOpen;
694
+ }
695
+ async reload() {
696
+ const generation = (this
697
+ ._reloadGeneration ?? 0) + 1;
698
+ this._reloadGeneration = generation;
699
+ try {
700
+ const rows = await queryBasesAppDatabaseRows(this.app.appDatabase, buildBasesAppDatabaseQuery({
701
+ documentFilter: this.doc.filters,
702
+ viewFilter: this.selectedView?.filter,
703
+ sort: this.selectedView?.sort ?? [],
704
+ limit: this.selectedView?.limit ?? 0,
705
+ }));
706
+ if (this
707
+ ._reloadGeneration !== generation) {
708
+ return;
709
+ }
710
+ const records = appDatabaseRowsToVaultRecords(this.app, rows);
711
+ if (this.metadataDependencies.backlinks && records.length) {
712
+ const backlinks = new Map();
713
+ for (let offset = 0; offset < records.length; offset += 400) {
714
+ const links = await this.app.metadataCache.queryLinks({
715
+ direction: "incoming",
716
+ paths: records.slice(offset, offset + 400).map((record) => record.id),
717
+ resolution: "resolved",
718
+ limit: 100_000,
719
+ });
720
+ for (const link of links) {
721
+ if (!link.resolvedTargetPath)
722
+ continue;
723
+ const sources = backlinks.get(link.resolvedTargetPath) ?? new Set();
724
+ sources.add(link.sourcePath);
725
+ backlinks.set(link.resolvedTargetPath, sources);
726
+ }
727
+ }
728
+ for (const record of records) {
729
+ record.backlinks = [...(backlinks.get(record.id) ?? [])];
730
+ }
731
+ }
732
+ this.loadError = null;
733
+ this.data = records;
734
+ }
735
+ catch (error) {
736
+ console.error("Error loading Bases rows from AppDatabase", error);
737
+ if (this
738
+ ._reloadGeneration !== generation) {
739
+ return;
740
+ }
741
+ this.loadError = error instanceof Error ? error.message : String(error);
742
+ this.data = [];
743
+ }
744
+ }
745
+ }
746
+ export class BasesView extends Component {
747
+ controller;
748
+ mountedView = null;
749
+ mountedViewContainer = null;
750
+ /**
751
+ * @since 1.10.0
752
+ * @public
753
+ */
754
+ app;
755
+ /**
756
+ * The config object for this view.
757
+ *
758
+ * @since 1.10.0
759
+ * @public
760
+ */
761
+ config = $state(new SharedBasesViewConfig({
762
+ type: "unknown",
763
+ sort: [],
764
+ order: [],
765
+ limit: 0,
766
+ name: "Unknown",
767
+ filter: { and: [] },
768
+ }, {}));
769
+ /**
770
+ * The most recent output from executing the bases query, applying filters,
771
+ * and evaluating formulas. This object will be replaced with a new result set
772
+ * when changes to the vault or Bases config occur, so views should not keep a
773
+ * reference to it. Also note the contained BasesEntry objects will be
774
+ * recreated.
775
+ *
776
+ * @since 1.10.0
777
+ * @public
778
+ */
779
+ data = $state(new BasesQueryResult([], []));
780
+ constructor(controller) {
781
+ super();
782
+ this.controller = controller;
783
+ }
784
+ mountViewComponent(component, props, containerEl) {
785
+ this.unmountViewComponent();
786
+ containerEl.replaceChildren();
787
+ const target = containerEl.createDiv("bases-view__renderer");
788
+ this.mountedViewContainer = target;
789
+ this.mountedView = mount(component, {
790
+ props,
791
+ target,
792
+ });
793
+ }
794
+ unmountViewComponent() {
795
+ if (this.mountedView) {
796
+ unmount(this.mountedView);
797
+ this.mountedView = null;
798
+ }
799
+ this.mountedViewContainer?.remove();
800
+ this.mountedViewContainer = null;
801
+ }
802
+ onunload() {
803
+ this.unmountViewComponent();
804
+ }
805
+ /**
806
+ * All available properties from the dataset.
807
+ *
808
+ * @since 1.10.0
809
+ * @public
810
+ */
811
+ get allProperties() {
812
+ if (!this.data)
813
+ return [];
814
+ return this.data.properties;
815
+ }
816
+ async createFileForView(baseFileName, frontmatterProcessor) {
817
+ const fileName = (baseFileName?.trim() || "Untitled").replace(/\.md$/i, "");
818
+ const path = this.app.fileManager.getAvailablePathForAttachment(`${fileName}.md`, "");
819
+ const file = await this.app.vault.create(path, "");
820
+ if (frontmatterProcessor) {
821
+ await this.app.fileManager.processFrontMatter(file, frontmatterProcessor);
822
+ }
823
+ await this.app.openFile(file);
824
+ }
825
+ }
826
+ /**
827
+ * A group of BasesEntry objects for a given value of the groupBy key. If there
828
+ * are entries in the results which do not have a value for the groupBy key, the
829
+ * key will be the {@link NullValue}.
830
+ *
831
+ * @since 1.10.0
832
+ * @public
833
+ */
834
+ export class BasesEntryGroup {
835
+ entries;
836
+ key;
837
+ constructor(entries, key) {
838
+ this.entries = entries;
839
+ this.key = key;
840
+ }
841
+ /**
842
+ * @since 1.10.0
843
+ * @returns True iff this entry group has a non-null key.
844
+ * @public
845
+ */
846
+ hasKey() {
847
+ if (this.key === null || this.key === undefined) {
848
+ return false;
849
+ }
850
+ return this.key.isTruthy();
851
+ }
852
+ }
853
+ /**
854
+ * The BasesQueryResult contains all of the available information from executing
855
+ * the bases query, applying filters, and evaluating formulas. The `data` or
856
+ * `groupedData` should be displayed by your view.
857
+ *
858
+ * @since 1.10.0
859
+ * @public
860
+ */
861
+ export class BasesQueryResult {
862
+ data;
863
+ properties;
864
+ groupBy;
865
+ constructor(data, properties, groupBy) {
866
+ this.data = data;
867
+ this.properties = properties;
868
+ this.groupBy = groupBy;
869
+ }
870
+ get groupedData() {
871
+ return groupEntries(this.data, this.groupBy, (left, right) => Value.looseEquals(left, right)).map((group) => new BasesEntryGroup(group.entries, group.key ?? undefined));
872
+ }
873
+ getSummaryValue(queryController, entries, prop, summaryKey) {
874
+ const values = entries
875
+ .map((entry) => entry.getValue(prop))
876
+ .filter((value) => !!value && !(value instanceof NullValue));
877
+ const numbers = values
878
+ .filter((value) => value instanceof NumberValue)
879
+ .map((value) => value.number);
880
+ switch (summaryKey.toLowerCase()) {
881
+ case "count":
882
+ return new NumberValue(entries.length);
883
+ case "count-empty":
884
+ return new NumberValue(entries.length - values.length);
885
+ case "count-non-empty":
886
+ case "count-filled":
887
+ return new NumberValue(values.length);
888
+ case "sum":
889
+ return numbers.length
890
+ ? new NumberValue(numbers.reduce((total, value) => total + value, 0))
891
+ : NullValue.value;
892
+ case "average":
893
+ case "avg":
894
+ return numbers.length
895
+ ? new NumberValue(numbers.reduce((total, value) => total + value, 0) /
896
+ numbers.length)
897
+ : NullValue.value;
898
+ case "min":
899
+ return numbers.length
900
+ ? new NumberValue(Math.min(...numbers))
901
+ : NullValue.value;
902
+ case "max":
903
+ return numbers.length
904
+ ? new NumberValue(Math.max(...numbers))
905
+ : NullValue.value;
906
+ default:
907
+ return NullValue.value;
908
+ }
909
+ }
910
+ }