@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.
- package/CHANGELOG.md +6 -0
- package/LICENSE.md +8 -0
- package/README.md +20 -0
- package/dist/bases-plugin.d.ts +10 -0
- package/dist/bases-plugin.js +118 -0
- package/dist/bases-view/app-database-query-source.d.ts +30 -0
- package/dist/bases-view/app-database-query-source.js +248 -0
- package/dist/bases-view/bases.svelte.d.ts +569 -0
- package/dist/bases-view/bases.svelte.js +910 -0
- package/dist/bases-view/card-view/components/card-placeholder.svelte +68 -0
- package/dist/bases-view/card-view/components/card-placeholder.svelte.d.ts +7 -0
- package/dist/bases-view/card-view/components/card.svelte +289 -0
- package/dist/bases-view/card-view/components/card.svelte.d.ts +7 -0
- package/dist/bases-view/card-view/index.d.ts +11 -0
- package/dist/bases-view/card-view/index.js +55 -0
- package/dist/bases-view/cell-editor-options.d.ts +2 -0
- package/dist/bases-view/cell-editor-options.js +26 -0
- package/dist/bases-view/cell-editor.svelte +206 -0
- package/dist/bases-view/cell-editor.svelte.d.ts +12 -0
- package/dist/bases-view/cell.svelte +132 -0
- package/dist/bases-view/cell.svelte.d.ts +16 -0
- package/dist/bases-view/column-definition.d.ts +22 -0
- package/dist/bases-view/column-definition.js +149 -0
- package/dist/bases-view/columns.d.ts +15 -0
- package/dist/bases-view/columns.js +103 -0
- package/dist/bases-view/components/Tag.svelte +14 -0
- package/dist/bases-view/components/Tag.svelte.d.ts +8 -0
- package/dist/bases-view/components/autocomplete.svelte +45 -0
- package/dist/bases-view/components/autocomplete.svelte.d.ts +17 -0
- package/dist/bases-view/components/card-image.svelte +121 -0
- package/dist/bases-view/components/card-image.svelte.d.ts +11 -0
- package/dist/bases-view/components/column-visibility.svelte +388 -0
- package/dist/bases-view/components/column-visibility.svelte.d.ts +13 -0
- package/dist/bases-view/components/datetime.svelte +35 -0
- package/dist/bases-view/components/datetime.svelte.d.ts +7 -0
- package/dist/bases-view/components/file.svelte +31 -0
- package/dist/bases-view/components/file.svelte.d.ts +8 -0
- package/dist/bases-view/components/filters/filter-editor.svelte +102 -0
- package/dist/bases-view/components/filters/filter-editor.svelte.d.ts +10 -0
- package/dist/bases-view/components/filters/filter-group.svelte +142 -0
- package/dist/bases-view/components/filters/filter-group.svelte.d.ts +11 -0
- package/dist/bases-view/components/filters/filter-properties.svelte +77 -0
- package/dist/bases-view/components/filters/filter-properties.svelte.d.ts +8 -0
- package/dist/bases-view/components/filters/filter-root.svelte +112 -0
- package/dist/bases-view/components/filters/filter-root.svelte.d.ts +12 -0
- package/dist/bases-view/components/filters/filter-row.svelte +351 -0
- package/dist/bases-view/components/filters/filter-row.svelte.d.ts +11 -0
- package/dist/bases-view/components/group/group-root.svelte +120 -0
- package/dist/bases-view/components/group/group-root.svelte.d.ts +8 -0
- package/dist/bases-view/components/limit.svelte +100 -0
- package/dist/bases-view/components/limit.svelte.d.ts +12 -0
- package/dist/bases-view/components/query-editor.svelte +230 -0
- package/dist/bases-view/components/query-editor.svelte.d.ts +15 -0
- package/dist/bases-view/components/sort/sort-direction-labels.d.ts +9 -0
- package/dist/bases-view/components/sort/sort-direction-labels.js +25 -0
- package/dist/bases-view/components/sort/sort-root.svelte +239 -0
- package/dist/bases-view/components/sort/sort-root.svelte.d.ts +11 -0
- package/dist/bases-view/components/sort/sort-row.svelte +190 -0
- package/dist/bases-view/components/sort/sort-row.svelte.d.ts +15 -0
- package/dist/bases-view/components/summary-strip.svelte +56 -0
- package/dist/bases-view/components/summary-strip.svelte.d.ts +8 -0
- package/dist/bases-view/components/tag-chip.svelte +47 -0
- package/dist/bases-view/components/tag-chip.svelte.d.ts +12 -0
- package/dist/bases-view/components/views.svelte +178 -0
- package/dist/bases-view/components/views.svelte.d.ts +12 -0
- package/dist/bases-view/data-table-adapter/data-table.svelte.d.ts +45 -0
- package/dist/bases-view/data-table-adapter/data-table.svelte.js +112 -0
- package/dist/bases-view/data-table-adapter/flex-render.svelte +43 -0
- package/dist/bases-view/data-table-adapter/flex-render.svelte.d.ts +30 -0
- package/dist/bases-view/data-table-adapter/index.d.ts +3 -0
- package/dist/bases-view/data-table-adapter/index.js +3 -0
- package/dist/bases-view/data-table-adapter/render-helpers.d.ts +101 -0
- package/dist/bases-view/data-table-adapter/render-helpers.js +110 -0
- package/dist/bases-view/db.d.ts +31 -0
- package/dist/bases-view/db.js +174 -0
- package/dist/bases-view/document-core.d.ts +2 -0
- package/dist/bases-view/document-core.js +166 -0
- package/dist/bases-view/export-core.d.ts +4 -0
- package/dist/bases-view/export-core.js +31 -0
- package/dist/bases-view/file-fields-core.d.ts +31 -0
- package/dist/bases-view/file-fields-core.js +114 -0
- package/dist/bases-view/filter-parser.d.ts +27 -0
- package/dist/bases-view/filter-parser.js +315 -0
- package/dist/bases-view/functions-core.d.ts +66 -0
- package/dist/bases-view/functions-core.js +265 -0
- package/dist/bases-view/functions.d.ts +5 -0
- package/dist/bases-view/functions.js +107 -0
- package/dist/bases-view/fuzzy-search.d.ts +6 -0
- package/dist/bases-view/fuzzy-search.js +15 -0
- package/dist/bases-view/hooks/useResizeObserver.svelte.d.ts +7 -0
- package/dist/bases-view/hooks/useResizeObserver.svelte.js +26 -0
- package/dist/bases-view/index.d.ts +66 -0
- package/dist/bases-view/index.js +231 -0
- package/dist/bases-view/list-view/index.d.ts +10 -0
- package/dist/bases-view/list-view/index.js +17 -0
- package/dist/bases-view/list-view/list.svelte +156 -0
- package/dist/bases-view/list-view/list.svelte.d.ts +7 -0
- package/dist/bases-view/map-view/index.d.ts +10 -0
- package/dist/bases-view/map-view/index.js +17 -0
- package/dist/bases-view/map-view/view.svelte +28 -0
- package/dist/bases-view/map-view/view.svelte.d.ts +7 -0
- package/dist/bases-view/metadata-invalidation-core.d.ts +34 -0
- package/dist/bases-view/metadata-invalidation-core.js +162 -0
- package/dist/bases-view/metadata-type-info.d.ts +7 -0
- package/dist/bases-view/metadata-type-info.js +10 -0
- package/dist/bases-view/models.d.ts +90 -0
- package/dist/bases-view/models.js +133 -0
- package/dist/bases-view/query-result-core.d.ts +8 -0
- package/dist/bases-view/query-result-core.js +16 -0
- package/dist/bases-view/resource-url.d.ts +2 -0
- package/dist/bases-view/resource-url.js +21 -0
- package/dist/bases-view/search-core.d.ts +9 -0
- package/dist/bases-view/search-core.js +20 -0
- package/dist/bases-view/summary-core.d.ts +7 -0
- package/dist/bases-view/summary-core.js +17 -0
- package/dist/bases-view/table-view/components/div-table.svelte +464 -0
- package/dist/bases-view/table-view/components/div-table.svelte.d.ts +32 -0
- package/dist/bases-view/table-view/components/dnd-style.d.ts +3 -0
- package/dist/bases-view/table-view/components/dnd-style.js +6 -0
- package/dist/bases-view/table-view/components/sort-header.svelte +48 -0
- package/dist/bases-view/table-view/components/sort-header.svelte.d.ts +11 -0
- package/dist/bases-view/table-view/components/table-column-tracks.d.ts +14 -0
- package/dist/bases-view/table-view/components/table-column-tracks.js +27 -0
- package/dist/bases-view/table-view/components/table-header.svelte +146 -0
- package/dist/bases-view/table-view/components/table-header.svelte.d.ts +10 -0
- package/dist/bases-view/table-view/components/table-placeholder.svelte +46 -0
- package/dist/bases-view/table-view/components/table-placeholder.svelte.d.ts +30 -0
- package/dist/bases-view/table-view/components/table-virtualizer-core.d.ts +13 -0
- package/dist/bases-view/table-view/components/table-virtualizer-core.js +12 -0
- package/dist/bases-view/table-view/components/table.svelte +997 -0
- package/dist/bases-view/table-view/components/table.svelte.d.ts +32 -0
- package/dist/bases-view/table-view/index.svelte.d.ts +10 -0
- package/dist/bases-view/table-view/index.svelte.js +30 -0
- package/dist/bases-view/unknown-view/index.d.ts +10 -0
- package/dist/bases-view/unknown-view/index.js +19 -0
- package/dist/bases-view/unknown-view/view.svelte +11 -0
- package/dist/bases-view/unknown-view/view.svelte.d.ts +6 -0
- package/dist/bases-view/view-header.svelte +142 -0
- package/dist/bases-view/view-header.svelte.d.ts +28 -0
- package/dist/bases-view/view-registration-inventory.d.ts +22 -0
- package/dist/bases-view/view-registration-inventory.js +7 -0
- package/dist/bases-view/view-settings.svelte +350 -0
- package/dist/bases-view/view-settings.svelte.d.ts +9 -0
- package/dist/bases-view/view.svelte +192 -0
- package/dist/bases-view/view.svelte.d.ts +15 -0
- package/dist/bases-view-surface.svelte +31 -0
- package/dist/bases-view-surface.svelte.d.ts +14 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +8 -0
- package/dist/serialized-write-queue.d.ts +10 -0
- package/dist/serialized-write-queue.js +36 -0
- package/dist/styles.css +2164 -0
- package/manifest.json +33 -0
- package/package.json +90 -0
- package/styles.css +1 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
function emptyFilter() {
|
|
2
|
+
return { and: [] };
|
|
3
|
+
}
|
|
4
|
+
function defaultTableView() {
|
|
5
|
+
return {
|
|
6
|
+
type: "table",
|
|
7
|
+
name: "Table",
|
|
8
|
+
layout: "table",
|
|
9
|
+
order: [],
|
|
10
|
+
sort: [],
|
|
11
|
+
filter: emptyFilter(),
|
|
12
|
+
limit: 0,
|
|
13
|
+
columnSize: {},
|
|
14
|
+
imageAspectRatio: 1,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function defaultCardsView() {
|
|
18
|
+
return {
|
|
19
|
+
type: "cards",
|
|
20
|
+
name: "Cards",
|
|
21
|
+
order: [],
|
|
22
|
+
sort: [],
|
|
23
|
+
filter: emptyFilter(),
|
|
24
|
+
limit: 0,
|
|
25
|
+
image: "",
|
|
26
|
+
imageFit: "contain",
|
|
27
|
+
imageAspectRatio: 1,
|
|
28
|
+
cardSize: 200,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function defaultCustomView(type) {
|
|
32
|
+
return {
|
|
33
|
+
type,
|
|
34
|
+
name: type,
|
|
35
|
+
order: [],
|
|
36
|
+
sort: [],
|
|
37
|
+
filter: emptyFilter(),
|
|
38
|
+
limit: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function normalizeView(view, fallbackName) {
|
|
42
|
+
if (!view || typeof view !== "object")
|
|
43
|
+
return null;
|
|
44
|
+
const candidate = view;
|
|
45
|
+
if (candidate.type === "table") {
|
|
46
|
+
return {
|
|
47
|
+
...defaultTableView(),
|
|
48
|
+
...candidate,
|
|
49
|
+
name: typeof candidate.name === "string" ? candidate.name : fallbackName,
|
|
50
|
+
order: Array.isArray(candidate.order)
|
|
51
|
+
? candidate.order.filter((value) => typeof value === "string")
|
|
52
|
+
: [],
|
|
53
|
+
sort: Array.isArray(candidate.sort) ? candidate.sort : [],
|
|
54
|
+
filter: candidate.filter && typeof candidate.filter === "object"
|
|
55
|
+
? candidate.filter
|
|
56
|
+
: emptyFilter(),
|
|
57
|
+
limit: typeof candidate.limit === "number" ? candidate.limit : 0,
|
|
58
|
+
columnSize: candidate.columnSize && typeof candidate.columnSize === "object"
|
|
59
|
+
? candidate.columnSize
|
|
60
|
+
: {},
|
|
61
|
+
imageAspectRatio: typeof candidate.imageAspectRatio === "number"
|
|
62
|
+
? candidate.imageAspectRatio
|
|
63
|
+
: 1,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (candidate.type === "cards") {
|
|
67
|
+
return {
|
|
68
|
+
...defaultCardsView(),
|
|
69
|
+
...candidate,
|
|
70
|
+
name: typeof candidate.name === "string" ? candidate.name : fallbackName,
|
|
71
|
+
order: Array.isArray(candidate.order)
|
|
72
|
+
? candidate.order.filter((value) => typeof value === "string")
|
|
73
|
+
: [],
|
|
74
|
+
sort: Array.isArray(candidate.sort) ? candidate.sort : [],
|
|
75
|
+
filter: candidate.filter && typeof candidate.filter === "object"
|
|
76
|
+
? candidate.filter
|
|
77
|
+
: emptyFilter(),
|
|
78
|
+
limit: typeof candidate.limit === "number" ? candidate.limit : 0,
|
|
79
|
+
summaries: candidate.summaries && typeof candidate.summaries === "object"
|
|
80
|
+
? candidate.summaries
|
|
81
|
+
: undefined,
|
|
82
|
+
groupBy: candidate.groupBy && typeof candidate.groupBy === "object"
|
|
83
|
+
? candidate.groupBy
|
|
84
|
+
: undefined,
|
|
85
|
+
image: typeof candidate.image === "string" ? candidate.image : "",
|
|
86
|
+
imageFit: candidate.imageFit === "cover" || candidate.imageFit === "contain"
|
|
87
|
+
? candidate.imageFit
|
|
88
|
+
: "contain",
|
|
89
|
+
imageAspectRatio: typeof candidate.imageAspectRatio === "number"
|
|
90
|
+
? candidate.imageAspectRatio
|
|
91
|
+
: 1,
|
|
92
|
+
cardSize: typeof candidate.cardSize === "number" ? candidate.cardSize : 200,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (candidate.type === "list" || candidate.type === "map") {
|
|
96
|
+
return {
|
|
97
|
+
type: candidate.type,
|
|
98
|
+
name: typeof candidate.name === "string" ? candidate.name : fallbackName,
|
|
99
|
+
order: Array.isArray(candidate.order)
|
|
100
|
+
? candidate.order.filter((value) => typeof value === "string")
|
|
101
|
+
: [],
|
|
102
|
+
sort: Array.isArray(candidate.sort) ? candidate.sort : [],
|
|
103
|
+
filter: candidate.filter && typeof candidate.filter === "object"
|
|
104
|
+
? candidate.filter
|
|
105
|
+
: emptyFilter(),
|
|
106
|
+
limit: typeof candidate.limit === "number" ? candidate.limit : 0,
|
|
107
|
+
summaries: candidate.summaries && typeof candidate.summaries === "object"
|
|
108
|
+
? candidate.summaries
|
|
109
|
+
: undefined,
|
|
110
|
+
groupBy: candidate.groupBy && typeof candidate.groupBy === "object"
|
|
111
|
+
? candidate.groupBy
|
|
112
|
+
: undefined,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (typeof candidate.type === "string" && candidate.type.length > 0) {
|
|
116
|
+
return {
|
|
117
|
+
...defaultCustomView(candidate.type),
|
|
118
|
+
...candidate,
|
|
119
|
+
name: typeof candidate.name === "string" ? candidate.name : fallbackName,
|
|
120
|
+
order: Array.isArray(candidate.order)
|
|
121
|
+
? candidate.order.filter((value) => typeof value === "string")
|
|
122
|
+
: [],
|
|
123
|
+
sort: Array.isArray(candidate.sort) ? candidate.sort : [],
|
|
124
|
+
filter: candidate.filter && typeof candidate.filter === "object"
|
|
125
|
+
? candidate.filter
|
|
126
|
+
: emptyFilter(),
|
|
127
|
+
limit: typeof candidate.limit === "number" ? candidate.limit : 0,
|
|
128
|
+
summaries: candidate.summaries && typeof candidate.summaries === "object"
|
|
129
|
+
? candidate.summaries
|
|
130
|
+
: undefined,
|
|
131
|
+
groupBy: candidate.groupBy && typeof candidate.groupBy === "object"
|
|
132
|
+
? candidate.groupBy
|
|
133
|
+
: undefined,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
export function normalizeBasesDocument(input) {
|
|
139
|
+
const candidate = input && typeof input === "object" ? input : {};
|
|
140
|
+
const views = Array.isArray(candidate.views)
|
|
141
|
+
? candidate.views
|
|
142
|
+
.map((view, index) => normalizeView(view, `View ${index + 1}`))
|
|
143
|
+
.filter((view) => !!view)
|
|
144
|
+
: [];
|
|
145
|
+
const normalizedViews = views.length ? views : [defaultTableView()];
|
|
146
|
+
const activeView = typeof candidate.activeView === "string" &&
|
|
147
|
+
normalizedViews.some((view) => view.name === candidate.activeView)
|
|
148
|
+
? candidate.activeView
|
|
149
|
+
: normalizedViews[0].name;
|
|
150
|
+
return {
|
|
151
|
+
filters: candidate.filters && typeof candidate.filters === "object"
|
|
152
|
+
? candidate.filters
|
|
153
|
+
: emptyFilter(),
|
|
154
|
+
properties: candidate.properties && typeof candidate.properties === "object"
|
|
155
|
+
? candidate.properties
|
|
156
|
+
: {},
|
|
157
|
+
formulas: candidate.formulas && typeof candidate.formulas === "object"
|
|
158
|
+
? candidate.formulas
|
|
159
|
+
: {},
|
|
160
|
+
summaries: candidate.summaries && typeof candidate.summaries === "object"
|
|
161
|
+
? candidate.summaries
|
|
162
|
+
: {},
|
|
163
|
+
activeView,
|
|
164
|
+
views: normalizedViews,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type ExportableEntry<TPropertyId extends string> = {
|
|
2
|
+
getValue(propertyId: TPropertyId): unknown;
|
|
3
|
+
};
|
|
4
|
+
export declare function serializeResultsToCsv<TPropertyId extends string>(entries: Array<ExportableEntry<TPropertyId>>, properties: TPropertyId[], displayName: (propertyId: TPropertyId) => string): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
function stringifyValue(value) {
|
|
2
|
+
if (value === null || value === undefined) {
|
|
3
|
+
return "";
|
|
4
|
+
}
|
|
5
|
+
if (typeof value === "object" &&
|
|
6
|
+
value &&
|
|
7
|
+
"value" in value) {
|
|
8
|
+
const inner = value.value;
|
|
9
|
+
return inner === null || inner === undefined ? "" : String(inner);
|
|
10
|
+
}
|
|
11
|
+
return String(value);
|
|
12
|
+
}
|
|
13
|
+
function escapeCsv(value) {
|
|
14
|
+
if (!/[",\n]/.test(value)) {
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
18
|
+
}
|
|
19
|
+
export function serializeResultsToCsv(entries, properties, displayName) {
|
|
20
|
+
const lines = [
|
|
21
|
+
properties
|
|
22
|
+
.map((propertyId) => escapeCsv(displayName(propertyId)))
|
|
23
|
+
.join(","),
|
|
24
|
+
];
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
lines.push(properties
|
|
27
|
+
.map((propertyId) => escapeCsv(stringifyValue(entry.getValue(propertyId))))
|
|
28
|
+
.join(","));
|
|
29
|
+
}
|
|
30
|
+
return lines.join("\n");
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { MetadataType } from "@lapis-notes/api";
|
|
2
|
+
export type FileColumnDefinition = {
|
|
3
|
+
id: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
icon: string;
|
|
6
|
+
type: MetadataType;
|
|
7
|
+
};
|
|
8
|
+
export type FileMetadataCache = {
|
|
9
|
+
frontmatter?: Record<string, unknown>;
|
|
10
|
+
links?: Array<{
|
|
11
|
+
link: string;
|
|
12
|
+
}>;
|
|
13
|
+
embeds?: Array<{
|
|
14
|
+
link: string;
|
|
15
|
+
}>;
|
|
16
|
+
tags?: Array<{
|
|
17
|
+
tag: string;
|
|
18
|
+
}>;
|
|
19
|
+
} | null | undefined;
|
|
20
|
+
export declare function buildFileColumns(properties?: Record<string, {
|
|
21
|
+
displayName: string;
|
|
22
|
+
}>): FileColumnDefinition[];
|
|
23
|
+
export declare function deriveFileMetadata(entryPath: string, cache: FileMetadataCache, backlinkInput?: string[] | Record<string, Record<string, number>>): {
|
|
24
|
+
tags: string[];
|
|
25
|
+
links: string[];
|
|
26
|
+
embeds: string[];
|
|
27
|
+
backlinks: string[];
|
|
28
|
+
properties: {
|
|
29
|
+
[x: string]: unknown;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export function buildFileColumns(properties = {}) {
|
|
2
|
+
return [
|
|
3
|
+
{
|
|
4
|
+
id: "file",
|
|
5
|
+
displayName: properties["file"]?.displayName || "file",
|
|
6
|
+
icon: "lucide-file",
|
|
7
|
+
type: "file",
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
id: "file.name",
|
|
11
|
+
displayName: properties["file.name"]?.displayName || "file name",
|
|
12
|
+
icon: "lucide-text",
|
|
13
|
+
type: "text",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "file.file",
|
|
17
|
+
displayName: properties["file.file"]?.displayName || "file reference",
|
|
18
|
+
icon: "lucide-file",
|
|
19
|
+
type: "file",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "file.ext",
|
|
23
|
+
displayName: properties["file.ext"]?.displayName || "file extension",
|
|
24
|
+
icon: "lucide-text",
|
|
25
|
+
type: "text",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "file.basename",
|
|
29
|
+
displayName: properties["file.basename"]?.displayName || "file base name",
|
|
30
|
+
icon: "lucide-text",
|
|
31
|
+
type: "text",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "file.folder",
|
|
35
|
+
displayName: properties["file.folder"]?.displayName || "folder",
|
|
36
|
+
icon: "lucide-text",
|
|
37
|
+
type: "text",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "file.fullname",
|
|
41
|
+
displayName: properties["file.fullname"]?.displayName || "file full name",
|
|
42
|
+
icon: "lucide-text",
|
|
43
|
+
type: "text",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "file.path",
|
|
47
|
+
displayName: properties["file.path"]?.displayName || "file path",
|
|
48
|
+
icon: "lucide-text",
|
|
49
|
+
type: "text",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "file.ctime",
|
|
53
|
+
displayName: properties["file.ctime"]?.displayName || "created time",
|
|
54
|
+
icon: "lucide-clock",
|
|
55
|
+
type: "datetime",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "file.mtime",
|
|
59
|
+
displayName: properties["file.mtime"]?.displayName || "modified time",
|
|
60
|
+
icon: "lucide-clock",
|
|
61
|
+
type: "datetime",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "file.size",
|
|
65
|
+
displayName: properties["file.size"]?.displayName || "file size",
|
|
66
|
+
icon: "lucide-binary",
|
|
67
|
+
type: "number",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "file.tags",
|
|
71
|
+
displayName: properties["file.tags"]?.displayName || "file tags",
|
|
72
|
+
icon: "lucide-tags",
|
|
73
|
+
type: "tags",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "file.links",
|
|
77
|
+
displayName: properties["file.links"]?.displayName || "file links",
|
|
78
|
+
icon: "lucide-link",
|
|
79
|
+
type: "multitext",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: "file.embeds",
|
|
83
|
+
displayName: properties["file.embeds"]?.displayName || "file embeds",
|
|
84
|
+
icon: "lucide-image",
|
|
85
|
+
type: "multitext",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: "file.backlinks",
|
|
89
|
+
displayName: properties["file.backlinks"]?.displayName || "file backlinks",
|
|
90
|
+
icon: "lucide-waypoints",
|
|
91
|
+
type: "multitext",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "file.properties",
|
|
95
|
+
displayName: properties["file.properties"]?.displayName || "file properties",
|
|
96
|
+
icon: "lucide-braces",
|
|
97
|
+
type: "unknown",
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
export function deriveFileMetadata(entryPath, cache, backlinkInput = []) {
|
|
102
|
+
const backlinks = Array.isArray(backlinkInput)
|
|
103
|
+
? backlinkInput
|
|
104
|
+
: Object.entries(backlinkInput)
|
|
105
|
+
.filter(([sourcePath, targets]) => sourcePath !== entryPath && Boolean(targets[entryPath]))
|
|
106
|
+
.map(([sourcePath]) => sourcePath);
|
|
107
|
+
return {
|
|
108
|
+
tags: (cache?.tags || []).map((it) => it.tag),
|
|
109
|
+
links: (cache?.links || []).map((it) => it.link),
|
|
110
|
+
embeds: (cache?.embeds || []).map((it) => it.link),
|
|
111
|
+
backlinks: backlinks.filter((sourcePath) => sourcePath !== entryPath),
|
|
112
|
+
properties: { ...(cache?.frontmatter || {}) },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Expression } from "peaql";
|
|
2
|
+
import { type BasesViewBase, type Filters } from "./models";
|
|
3
|
+
import type { BasesPropertyId } from "@lapis-notes/api";
|
|
4
|
+
export declare function isOp(op: string): boolean;
|
|
5
|
+
export declare function parsePredicate(query: string): unknown;
|
|
6
|
+
export declare const INVALID_FILTER_FORMULA_MESSAGE = "Invalid filter formula. Check the expression syntax.";
|
|
7
|
+
export type FilterPredicateValidation = {
|
|
8
|
+
valid: true;
|
|
9
|
+
predicate: unknown;
|
|
10
|
+
error: null;
|
|
11
|
+
} | {
|
|
12
|
+
valid: false;
|
|
13
|
+
predicate: null;
|
|
14
|
+
error: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function validateFilterPredicate(query: string): FilterPredicateValidation;
|
|
17
|
+
export declare function resolveFilterDraft(applied: string, draft: string): {
|
|
18
|
+
applied: string;
|
|
19
|
+
error: string | null;
|
|
20
|
+
valid: boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare function normalizeFilter(filter: Filters): Filters;
|
|
23
|
+
export declare function toSQL(filter: unknown, formulas?: Record<string, string>): string;
|
|
24
|
+
export declare function resolveQuery(expr: Expression): unknown;
|
|
25
|
+
export declare function isFilterColumn(column: string): [boolean, string, string];
|
|
26
|
+
export declare function formulaColumn(name: string): BasesPropertyId;
|
|
27
|
+
export declare function generateQuery(view: BasesViewBase, formulas: Record<string, string>, globalFilter?: Filters): string;
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { AttributeExpression, BooleanExpression, ColumnExpression, Decimal, Expression, FunctionExpression, isNull, isValidNumber, LiteralExpression, parseQuery, Query, readString, SubscriptExpression, typeName, } from "peaql";
|
|
2
|
+
import { filterGroupType, filterGroupValues, isFilterGroup, isFilterLine, } from "./models";
|
|
3
|
+
import { isNumber } from "lodash-es";
|
|
4
|
+
import { DateTime } from "luxon";
|
|
5
|
+
const OPS = new Set([
|
|
6
|
+
">",
|
|
7
|
+
"=",
|
|
8
|
+
"<",
|
|
9
|
+
"!=",
|
|
10
|
+
">=",
|
|
11
|
+
"<=",
|
|
12
|
+
"~",
|
|
13
|
+
"~*",
|
|
14
|
+
"!~",
|
|
15
|
+
"!~*",
|
|
16
|
+
"?~",
|
|
17
|
+
"?~*",
|
|
18
|
+
"+",
|
|
19
|
+
"-",
|
|
20
|
+
"*",
|
|
21
|
+
"%",
|
|
22
|
+
"/",
|
|
23
|
+
]);
|
|
24
|
+
export function isOp(op) {
|
|
25
|
+
return OPS.has(op);
|
|
26
|
+
}
|
|
27
|
+
export function parsePredicate(query) {
|
|
28
|
+
if (!query)
|
|
29
|
+
return null;
|
|
30
|
+
const [expr] = parseQuery(`SELECT 1 WHERE ${query}`);
|
|
31
|
+
if (expr instanceof Query && expr.where) {
|
|
32
|
+
return resolveQuery(expr.where);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export const INVALID_FILTER_FORMULA_MESSAGE = "Invalid filter formula. Check the expression syntax.";
|
|
36
|
+
export function validateFilterPredicate(query) {
|
|
37
|
+
if (!query.trim()) {
|
|
38
|
+
return { valid: true, predicate: null, error: null };
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const predicate = parsePredicate(query);
|
|
42
|
+
if (predicate) {
|
|
43
|
+
return { valid: true, predicate, error: null };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// The editor keeps the draft available for correction and reports one
|
|
48
|
+
// stable, user-facing message instead of leaking parser internals.
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
valid: false,
|
|
52
|
+
predicate: null,
|
|
53
|
+
error: INVALID_FILTER_FORMULA_MESSAGE,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function resolveFilterDraft(applied, draft) {
|
|
57
|
+
const validation = validateFilterPredicate(draft);
|
|
58
|
+
return {
|
|
59
|
+
applied: validation.valid ? draft : applied,
|
|
60
|
+
error: validation.error,
|
|
61
|
+
valid: validation.valid,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function normalizeFilter(filter) {
|
|
65
|
+
const values = filterGroupValues(filter).slice();
|
|
66
|
+
values.forEach((value, i) => {
|
|
67
|
+
if (isFilterGroup(value)) {
|
|
68
|
+
normalizeFilter(value);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
const type = filterGroupType(filter);
|
|
72
|
+
return {
|
|
73
|
+
[type]: values.filter((f) => {
|
|
74
|
+
if (typeof f === "string") {
|
|
75
|
+
return f.trim().length > 0;
|
|
76
|
+
}
|
|
77
|
+
return f;
|
|
78
|
+
}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function toSQLValue(value) {
|
|
82
|
+
if (value === null || value === undefined)
|
|
83
|
+
return "null";
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
return value.map(toSQLValue).join(",");
|
|
86
|
+
}
|
|
87
|
+
if (typeof value === "string") {
|
|
88
|
+
value = readString(value);
|
|
89
|
+
}
|
|
90
|
+
else if (typeof value === "number" || value instanceof Decimal) {
|
|
91
|
+
if (!isNull(value) && isValidNumber(value.toString())) {
|
|
92
|
+
return new Decimal(value.toString()).number;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (value instanceof DateTime) {
|
|
96
|
+
value = value.toISO();
|
|
97
|
+
}
|
|
98
|
+
return JSON.stringify(value);
|
|
99
|
+
}
|
|
100
|
+
function replaceFormulas(query, formulas = {}) {
|
|
101
|
+
while (query.match(/formula\["([^"]*)"\]|filter\.[a-zA-Z_][a-zA-Z0-9_]*/g)) {
|
|
102
|
+
query = query.replaceAll(/formula\["([^"]*)"\]|filter\.[a-zA-Z_][a-zA-Z0-9_]*/g, (match, captured) => {
|
|
103
|
+
return formulas[captured] || "null";
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return query;
|
|
107
|
+
}
|
|
108
|
+
export function toSQL(filter, formulas = {}) {
|
|
109
|
+
if (isFilterGroup(filter)) {
|
|
110
|
+
const type = filterGroupType(filter);
|
|
111
|
+
const values = filterGroupValues(filter)
|
|
112
|
+
.map((it) => toSQL(it, formulas))
|
|
113
|
+
.filter((it) => it.trim());
|
|
114
|
+
if (!values.length)
|
|
115
|
+
return "";
|
|
116
|
+
if (type === "not") {
|
|
117
|
+
return `NOT (${values.join(` OR `)})`;
|
|
118
|
+
}
|
|
119
|
+
return "(" + values.join(` ${type.toUpperCase()} `).trim() + ")";
|
|
120
|
+
}
|
|
121
|
+
else if (isFilterLine(filter)) {
|
|
122
|
+
const filt = filter;
|
|
123
|
+
if (filt.custom) {
|
|
124
|
+
return filt.custom;
|
|
125
|
+
}
|
|
126
|
+
if (filt.op &&
|
|
127
|
+
filt.column &&
|
|
128
|
+
(filter.value !== null || filt.value !== undefined)) {
|
|
129
|
+
let op = filter.op;
|
|
130
|
+
let column = filter.column;
|
|
131
|
+
const [isFilter, filterKey] = isFilterColumn(column);
|
|
132
|
+
if (isFilter) {
|
|
133
|
+
if (formulas && filterKey in formulas && formulas[filterKey]) {
|
|
134
|
+
column = `(${formulas[filterKey]})`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (column.match(/^[a-zA-Z0-9_.-]+\[[^\]]+\]$/)) {
|
|
138
|
+
column = column;
|
|
139
|
+
}
|
|
140
|
+
else if (!column.match(/^[a-zA-Z0-9_.-]+$/)) {
|
|
141
|
+
column = `{${column.replaceAll("}", "}}")}}`;
|
|
142
|
+
}
|
|
143
|
+
if (!OPS.has(op) && op.startsWith("!")) {
|
|
144
|
+
op = op.substring(1);
|
|
145
|
+
column = "!" + column;
|
|
146
|
+
}
|
|
147
|
+
if (OPS.has(op)) {
|
|
148
|
+
return `${column} ${op} ${toSQLValue(filt.value)}`;
|
|
149
|
+
}
|
|
150
|
+
const values = Array.isArray(filt.value) ? filt.value : [filt.value];
|
|
151
|
+
return `${column}.${op}(${toSQLValue(values)})`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else if (typeof filter === "string") {
|
|
155
|
+
return replaceFormulas(filter, formulas);
|
|
156
|
+
}
|
|
157
|
+
return "";
|
|
158
|
+
}
|
|
159
|
+
function flattenBooleanExpression(expr, targetOp) {
|
|
160
|
+
const conditions = [];
|
|
161
|
+
for (const arg of expr.args) {
|
|
162
|
+
if (arg instanceof BooleanExpression && arg.op === targetOp) {
|
|
163
|
+
// Same operator - flatten recursively
|
|
164
|
+
conditions.push(...flattenBooleanExpression(arg, targetOp));
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
// Different operator or non-boolean - resolve normally
|
|
168
|
+
conditions.push(resolveQuery(arg));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return conditions;
|
|
172
|
+
}
|
|
173
|
+
export function resolveQuery(expr) {
|
|
174
|
+
if (expr instanceof BooleanExpression) {
|
|
175
|
+
if (expr.args.length == 2) {
|
|
176
|
+
const [left, right] = expr.args;
|
|
177
|
+
const l = resolveQuery(left), r = resolveQuery(right);
|
|
178
|
+
if (typeof l === "string" &&
|
|
179
|
+
(right instanceof LiteralExpression || isScalar(r))) {
|
|
180
|
+
return { column: l, op: expr.op, value: r };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (["AND", "OR", "NOT"].includes(expr.op)) {
|
|
184
|
+
const flattened = flattenBooleanExpression(expr, expr.op);
|
|
185
|
+
if (expr.op === "NOT" && flattened.length == 1) {
|
|
186
|
+
const value = flattened[0];
|
|
187
|
+
if (isFilterLine(value) && !value.custom) {
|
|
188
|
+
if (!OPS.has(value.op)) {
|
|
189
|
+
value.op = "!" + value.op;
|
|
190
|
+
return value;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { [expr.op.toLowerCase()]: flattened };
|
|
195
|
+
}
|
|
196
|
+
return { column: "", op: expr.op, value: "", custom: expr.toString() };
|
|
197
|
+
}
|
|
198
|
+
else if (expr instanceof AttributeExpression) {
|
|
199
|
+
const op = resolveQuery(expr.operand);
|
|
200
|
+
if (typeof expr.name === "string" && typeof op == "string") {
|
|
201
|
+
return `${op}.${expr.name}`;
|
|
202
|
+
}
|
|
203
|
+
else if (typeof op === "string" && expr.name instanceof Expression) {
|
|
204
|
+
const value = resolveQuery(expr.name);
|
|
205
|
+
if (typeof value === "object") {
|
|
206
|
+
if (value && "custom" in value && value.custom) {
|
|
207
|
+
return { column: op, value: "", ...value, custom: expr.toString() };
|
|
208
|
+
}
|
|
209
|
+
return { column: op, ...value };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
return { column: "", op: "", custom: expr.toString(), value: "" };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else if (expr instanceof ColumnExpression) {
|
|
217
|
+
return expr.name;
|
|
218
|
+
}
|
|
219
|
+
else if (expr instanceof LiteralExpression) {
|
|
220
|
+
return expr.value;
|
|
221
|
+
}
|
|
222
|
+
else if (expr instanceof FunctionExpression) {
|
|
223
|
+
if (expr.args.every((it) => {
|
|
224
|
+
return (it instanceof LiteralExpression || it instanceof ColumnExpression);
|
|
225
|
+
})) {
|
|
226
|
+
let values = expr.args.map((it) => resolveQuery(it));
|
|
227
|
+
if (values.length == 1) {
|
|
228
|
+
return { op: expr.name, value: values[0] };
|
|
229
|
+
}
|
|
230
|
+
return { op: expr.name, value: values };
|
|
231
|
+
}
|
|
232
|
+
return { op: expr.name, custom: expr.toString(), value: "" };
|
|
233
|
+
}
|
|
234
|
+
else if (expr instanceof SubscriptExpression) {
|
|
235
|
+
if (expr.operand instanceof ColumnExpression) {
|
|
236
|
+
return `${expr.operand.name}["${expr.key}"]`;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
throw new Error(`Unsupported expression(${typeName(expr)}): ${expr}`);
|
|
240
|
+
}
|
|
241
|
+
function isScalar(value) {
|
|
242
|
+
const type = typeof value;
|
|
243
|
+
return ["string", "boolean", "number"].includes(type);
|
|
244
|
+
}
|
|
245
|
+
export function isFilterColumn(column) {
|
|
246
|
+
if (!column)
|
|
247
|
+
return [false, "", ""];
|
|
248
|
+
column = column.trim();
|
|
249
|
+
if (column.startsWith("formula.")) {
|
|
250
|
+
return [true, column.split(".", 2)[1], `{${column}}`];
|
|
251
|
+
}
|
|
252
|
+
else if (column.startsWith("formula[") && column.endsWith("]")) {
|
|
253
|
+
const id = readString(column.substring(8, column.length - 1));
|
|
254
|
+
return [true, id, `{formula.${id}}`];
|
|
255
|
+
}
|
|
256
|
+
return [false, "", ""];
|
|
257
|
+
}
|
|
258
|
+
export function formulaColumn(name) {
|
|
259
|
+
if (name.match(/^[a-zA-Z0-9_]+$/)) {
|
|
260
|
+
return `formula.${name}`;
|
|
261
|
+
}
|
|
262
|
+
return `formula[${JSON.stringify(name)}]`;
|
|
263
|
+
}
|
|
264
|
+
export function generateQuery(view, formulas, globalFilter) {
|
|
265
|
+
let query = "SET identifier_quoting = bracket;\nSELECT\n";
|
|
266
|
+
const columns = [
|
|
267
|
+
...new Set([
|
|
268
|
+
...view.order,
|
|
269
|
+
...(view.groupBy?.property ? [view.groupBy.property] : []),
|
|
270
|
+
...(typeof view.image === "string" && view.image.length > 0
|
|
271
|
+
? [view.image]
|
|
272
|
+
: []),
|
|
273
|
+
]),
|
|
274
|
+
];
|
|
275
|
+
query += columns
|
|
276
|
+
.map((column) => {
|
|
277
|
+
const [isFilter, filterKey, filterColumn] = isFilterColumn(column);
|
|
278
|
+
if (isFilter) {
|
|
279
|
+
if (filterKey in formulas) {
|
|
280
|
+
if (formulas[filterKey]) {
|
|
281
|
+
return ` (${replaceFormulas(formulas[filterKey], formulas)}) as ${filterColumn}`;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return ` null as ${filterColumn}`;
|
|
285
|
+
}
|
|
286
|
+
else if (column.match(/^[a-zA-Z0-9_.-]+$/)) {
|
|
287
|
+
return ` ${column} as {${column}}`;
|
|
288
|
+
}
|
|
289
|
+
return ` {${column}} as {${column}}`;
|
|
290
|
+
})
|
|
291
|
+
.join(",\n");
|
|
292
|
+
query += columns.length ? ",\n rowId as {$rowId}" : " rowId as {$rowId}";
|
|
293
|
+
const where = [toSQL(globalFilter, formulas), toSQL(view.filter, formulas)]
|
|
294
|
+
.filter((it) => it.length > 0)
|
|
295
|
+
.map((q) => `(${q})`)
|
|
296
|
+
.join(" and ");
|
|
297
|
+
if (where) {
|
|
298
|
+
query += "\nWHERE " + where;
|
|
299
|
+
}
|
|
300
|
+
const sort = view.sort
|
|
301
|
+
.map((s) => {
|
|
302
|
+
const [isFormula] = isFilterColumn(s.property);
|
|
303
|
+
return isFormula
|
|
304
|
+
? `(${replaceFormulas(formulaColumn(s.property), formulas)}) ${s.direction}`
|
|
305
|
+
: `{${s.property}} ${s.direction}`;
|
|
306
|
+
})
|
|
307
|
+
.join(",");
|
|
308
|
+
if (sort.length) {
|
|
309
|
+
query += "\nORDER BY " + sort;
|
|
310
|
+
}
|
|
311
|
+
if (view.limit && view.limit > 0) {
|
|
312
|
+
query += "\nLIMIT " + view.limit;
|
|
313
|
+
}
|
|
314
|
+
return query;
|
|
315
|
+
}
|