@tensorix/metahub 0.1.3 → 0.1.6
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/LICENSE +235 -0
- package/README.md +87 -147
- package/README.zh-CN.md +127 -0
- package/dist/cli.js +882 -142
- package/dist/cli.js.map +21 -17
- package/dist/core/blocks.d.ts +23 -4
- package/dist/core/blocks.d.ts.map +1 -1
- package/dist/core/databases.d.ts.map +1 -1
- package/dist/core/db.d.ts +6 -0
- package/dist/core/db.d.ts.map +1 -1
- package/dist/core/documents.d.ts.map +1 -1
- package/dist/core/integrity.d.ts +32 -0
- package/dist/core/integrity.d.ts.map +1 -0
- package/dist/core/properties.d.ts.map +1 -1
- package/dist/core/schema.d.ts +1 -1
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/search.d.ts +2 -0
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/snapshot.d.ts +2 -0
- package/dist/core/snapshot.d.ts.map +1 -1
- package/dist/core/sync/server.d.ts +14 -0
- package/dist/core/sync/server.d.ts.map +1 -1
- package/dist/core/sync/webui-routes.d.ts.map +1 -1
- package/dist/core/sync/webui.d.ts +11 -0
- package/dist/core/sync/webui.d.ts.map +1 -1
- package/dist/index.js +406 -125
- package/dist/index.js.map +13 -13
- package/dist/webui.js +32 -33
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -121,7 +121,7 @@ function observeHlc(db, node, remote, now = Date.now()) {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
// src/core/blocks.ts
|
|
124
|
-
function
|
|
124
|
+
function parseDocBlocks(md) {
|
|
125
125
|
if (!md)
|
|
126
126
|
return [];
|
|
127
127
|
const lines = md.replace(/\r\n?/g, `
|
|
@@ -131,24 +131,24 @@ function parseBlocks(md) {
|
|
|
131
131
|
let cur = [];
|
|
132
132
|
let fenceChar = null;
|
|
133
133
|
let fenceLen = 0;
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
cur.
|
|
137
|
-
|
|
138
|
-
cur
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
134
|
+
const closeBlock = () => {
|
|
135
|
+
if (cur.length) {
|
|
136
|
+
blocks.push({ text: cur.join(`
|
|
137
|
+
`), blankAfter: 0 });
|
|
138
|
+
cur = [];
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const onBlank = () => {
|
|
142
|
+
closeBlock();
|
|
143
|
+
if (blocks.length)
|
|
144
|
+
blocks[blocks.length - 1].blankAfter++;
|
|
143
145
|
};
|
|
144
146
|
for (const raw of lines) {
|
|
145
147
|
if (fenceChar) {
|
|
146
148
|
cur.push(raw);
|
|
147
149
|
const m = raw.match(/^\s*(`{3,}|~{3,})\s*$/);
|
|
148
150
|
if (m && m[1][0] === fenceChar && m[1].length >= fenceLen) {
|
|
149
|
-
|
|
150
|
-
`));
|
|
151
|
-
cur = [];
|
|
151
|
+
closeBlock();
|
|
152
152
|
fenceChar = null;
|
|
153
153
|
fenceLen = 0;
|
|
154
154
|
}
|
|
@@ -156,28 +156,34 @@ function parseBlocks(md) {
|
|
|
156
156
|
}
|
|
157
157
|
const open = raw.match(/^\s*(`{3,}|~{3,})/);
|
|
158
158
|
if (open) {
|
|
159
|
-
|
|
159
|
+
closeBlock();
|
|
160
160
|
fenceChar = open[1][0];
|
|
161
161
|
fenceLen = open[1].length;
|
|
162
162
|
cur.push(raw);
|
|
163
163
|
} else if (raw.trim() === "") {
|
|
164
|
-
|
|
164
|
+
onBlank();
|
|
165
165
|
} else {
|
|
166
166
|
cur.push(raw);
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
`));
|
|
173
|
-
} else {
|
|
174
|
-
flushPara();
|
|
175
|
-
}
|
|
169
|
+
closeBlock();
|
|
170
|
+
for (let i = 0;i < blocks.length - 1; i++)
|
|
171
|
+
blocks[i].blankAfter = Math.max(0, blocks[i].blankAfter - 1);
|
|
176
172
|
return blocks;
|
|
177
173
|
}
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
|
|
174
|
+
function serializeDocBlocks(blocks) {
|
|
175
|
+
const live = blocks.filter((b) => !!b.text && b.text.length > 0);
|
|
176
|
+
const out = [];
|
|
177
|
+
live.forEach((b, i) => {
|
|
178
|
+
out.push(...b.text.split(`
|
|
179
|
+
`));
|
|
180
|
+
const extra = Math.max(0, b.blankAfter ?? 0);
|
|
181
|
+
if (i < live.length - 1)
|
|
182
|
+
out.push("");
|
|
183
|
+
for (let k = 0;k < extra; k++)
|
|
184
|
+
out.push("");
|
|
185
|
+
});
|
|
186
|
+
return out.join(`
|
|
181
187
|
`);
|
|
182
188
|
}
|
|
183
189
|
function reconcile(oldTexts, newTexts) {
|
|
@@ -234,9 +240,9 @@ function isBlockManaged(db, docId) {
|
|
|
234
240
|
return db.query("SELECT 1 AS x FROM doc_blocks WHERE doc_id = ? LIMIT 1").get(docId) != null;
|
|
235
241
|
}
|
|
236
242
|
function recomputeDocBody(db, docId) {
|
|
237
|
-
const rows = db.query("SELECT text FROM doc_blocks WHERE doc_id = ? AND __deleted = 0 ORDER BY order_key, id").all(docId);
|
|
243
|
+
const rows = db.query("SELECT text, blank_after FROM doc_blocks WHERE doc_id = ? AND __deleted = 0 ORDER BY order_key, id").all(docId);
|
|
238
244
|
ensureRow(db, "documents", docId);
|
|
239
|
-
db.query("UPDATE documents SET body = ? WHERE id = ?").run(
|
|
245
|
+
db.query("UPDATE documents SET body = ? WHERE id = ?").run(serializeDocBlocks(rows.map((r) => ({ text: r.text, blankAfter: r.blank_after ?? 0 }))), docId);
|
|
240
246
|
}
|
|
241
247
|
function materialize(db, dataset, rowId, col, valueJson) {
|
|
242
248
|
if (dataset === "records") {
|
|
@@ -336,7 +342,7 @@ var init_crdt = __esm(() => {
|
|
|
336
342
|
},
|
|
337
343
|
doc_blocks: {
|
|
338
344
|
table: "doc_blocks",
|
|
339
|
-
cols: new Set(["doc_id", "text", "order_key", "__deleted"])
|
|
345
|
+
cols: new Set(["doc_id", "text", "order_key", "blank_after", "__deleted"])
|
|
340
346
|
},
|
|
341
347
|
sites: {
|
|
342
348
|
table: "sites",
|
|
@@ -531,9 +537,14 @@ var init_sites = __esm(() => {
|
|
|
531
537
|
// src/core/sync/webui.ts
|
|
532
538
|
var exports_webui = {};
|
|
533
539
|
__export(exports_webui, {
|
|
540
|
+
warmWebui: () => warmWebui,
|
|
541
|
+
setWebuiBundle: () => setWebuiBundle,
|
|
534
542
|
serveWebui: () => serveWebui
|
|
535
543
|
});
|
|
536
544
|
import { fileURLToPath } from "url";
|
|
545
|
+
function setWebuiBundle(js) {
|
|
546
|
+
cachedJs = js;
|
|
547
|
+
}
|
|
537
548
|
async function getJs() {
|
|
538
549
|
if (cachedJs != null)
|
|
539
550
|
return cachedJs;
|
|
@@ -549,6 +560,11 @@ async function getJs() {
|
|
|
549
560
|
cachedJs = await res.outputs[0].text();
|
|
550
561
|
return cachedJs;
|
|
551
562
|
}
|
|
563
|
+
async function warmWebui() {
|
|
564
|
+
try {
|
|
565
|
+
await getJs();
|
|
566
|
+
} catch {}
|
|
567
|
+
}
|
|
552
568
|
async function serveWebui(req) {
|
|
553
569
|
if (req.method !== "GET")
|
|
554
570
|
return null;
|
|
@@ -575,7 +591,13 @@ var HTML = `<!doctype html>
|
|
|
575
591
|
<title>Metahub</title>
|
|
576
592
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
577
593
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
578
|
-
|
|
594
|
+
<!-- Load web fonts without blocking first paint: media="print" keeps the
|
|
595
|
+
stylesheet non-render-blocking, then onload flips it to "all". The page
|
|
596
|
+
paints instantly with the system-font fallbacks baked into --ui/--mono and
|
|
597
|
+
swaps to the web fonts when they arrive (or stays on system fonts offline).
|
|
598
|
+
This is what kept the desktop cold-start white for seconds on slow networks. -->
|
|
599
|
+
<link rel="stylesheet" media="print" onload="this.media='all'" href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap">
|
|
600
|
+
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"></noscript>
|
|
579
601
|
<script>try{document.documentElement.dataset.theme=localStorage.getItem('mh-theme')||'system'}catch(e){}</script>
|
|
580
602
|
<style>
|
|
581
603
|
:root {
|
|
@@ -589,6 +611,7 @@ var HTML = `<!doctype html>
|
|
|
589
611
|
--radius:8px; --radius-sm:6px;
|
|
590
612
|
--ui:"Hanken Grotesk",-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
|
|
591
613
|
--mono:"JetBrains Mono",ui-monospace,SFMono-Regular,monospace;
|
|
614
|
+
--note-lh:1.6;
|
|
592
615
|
--code-bg:#f8f8f6;
|
|
593
616
|
--hl-cmt:#9a9aa2; --hl-key:#a626a4; --hl-str:#28823f; --hl-num:#b76a00;
|
|
594
617
|
--hl-fn:#3f51d6; --hl-type:#0a7ea4; --hl-var:#c2410c;
|
|
@@ -621,7 +644,7 @@ var HTML = `<!doctype html>
|
|
|
621
644
|
* { box-sizing: border-box; }
|
|
622
645
|
html, body { margin: 0; height: 100%; }
|
|
623
646
|
body { font-family: var(--ui); font-size: 14px; line-height: 1.55; color: var(--fg); background: var(--bg);
|
|
624
|
-
overflow: hidden;
|
|
647
|
+
overflow: hidden; text-rendering: auto; }
|
|
625
648
|
button { font: inherit; color: inherit; cursor: pointer; background: none; border: 0; padding: 0; }
|
|
626
649
|
input, textarea { font: inherit; color: inherit; }
|
|
627
650
|
.muted { color: var(--muted); }
|
|
@@ -677,6 +700,8 @@ var HTML = `<!doctype html>
|
|
|
677
700
|
.dragging { opacity:.35; }
|
|
678
701
|
.sb-footer { flex:none; padding:6px 8px; border-top:1px solid var(--line); }
|
|
679
702
|
.sb-footer .navitem { font-size:13.5px; }
|
|
703
|
+
.nav-dot { flex:none; width:7px; height:7px; border-radius:50%; background:var(--danger);
|
|
704
|
+
box-shadow:0 0 0 2px var(--surface); margin-right:2px; }
|
|
680
705
|
|
|
681
706
|
/* settings page */
|
|
682
707
|
.set-page { max-width:680px; margin:0 auto; padding:40px 28px 64px; }
|
|
@@ -685,6 +710,20 @@ var HTML = `<!doctype html>
|
|
|
685
710
|
.set-section { margin-top:34px; }
|
|
686
711
|
.set-section-head { font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.07em; color:var(--muted); margin-bottom:4px; }
|
|
687
712
|
.set-section-desc { color:var(--fg-soft); margin-bottom:16px; }
|
|
713
|
+
.set-footer { margin-top:40px; padding-top:20px; border-top:1px solid var(--line); display:flex;
|
|
714
|
+
flex-wrap:wrap; justify-content:center; align-items:center; gap:7px; color:var(--muted); font-size:12px; }
|
|
715
|
+
.set-footer-sep { opacity:.45; }
|
|
716
|
+
.ver-num { font-family:var(--mono); font-size:11px; color:var(--fg-soft); letter-spacing:-.01em; }
|
|
717
|
+
.ver-up { display:inline-flex; align-items:center; gap:6px; }
|
|
718
|
+
.ver-up.err .ver-msg { color:var(--danger); }
|
|
719
|
+
.ver-act { background:none; border:0; padding:0; margin:0; font:inherit; font-size:12px; line-height:1;
|
|
720
|
+
color:var(--muted); cursor:pointer; transition:color .12s; }
|
|
721
|
+
.ver-act:hover { color:var(--accent); }
|
|
722
|
+
.ver-act.accent { color:var(--accent); font-weight:600; }
|
|
723
|
+
.ver-act[aria-disabled="true"] { opacity:.6; cursor:default; pointer-events:none; }
|
|
724
|
+
.ver-dot { width:6px; height:6px; border-radius:50%; background:var(--accent); flex:none; }
|
|
725
|
+
.ver-dot.pulse { animation:verpulse 1.8s ease-in-out infinite; }
|
|
726
|
+
@keyframes verpulse { 0%,100% { opacity:1; } 50% { opacity:.4; } }
|
|
688
727
|
.theme-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
|
|
689
728
|
.theme-card { position:relative; display:flex; flex-direction:column; align-items:flex-start; gap:10px;
|
|
690
729
|
text-align:left; padding:16px; border:1px solid var(--line-strong); border-radius:var(--radius);
|
|
@@ -722,6 +761,7 @@ var HTML = `<!doctype html>
|
|
|
722
761
|
/* main */
|
|
723
762
|
.main { flex:1; display:flex; flex-direction:column; overflow:hidden; min-width:0; }
|
|
724
763
|
.topbar { display:flex; align-items:center; gap:6px; padding:10px 18px; border-bottom:1px solid var(--line); min-height:49px; }
|
|
764
|
+
.topbar.bare { border-bottom:0; }
|
|
725
765
|
.topbar .hamburger { display:none; }
|
|
726
766
|
.topbar .hamburger.show-collapsed { display:grid; }
|
|
727
767
|
.sidebar.collapsed { overflow:hidden; }
|
|
@@ -776,8 +816,10 @@ var HTML = `<!doctype html>
|
|
|
776
816
|
/* The list item's host gutter controls the whole unit, so suppress the nested
|
|
777
817
|
code block's own gutter (it would overlap the list marker on the same row). */
|
|
778
818
|
.block.list-code-host + .block-wrap.nested .gutter { display:none; }
|
|
779
|
-
.editable { outline:none; flex:1; min-width:0; padding:3px 2px; line-height:
|
|
819
|
+
.editable { outline:none; flex:1; min-width:0; padding:3px 2px; line-height:var(--note-lh); }
|
|
780
820
|
.editable:empty::before { content:attr(data-ph); color:var(--muted); pointer-events:none; }
|
|
821
|
+
/* The generic "/" prompt only shows on the focused line; idle blank lines (paragraphs and list items, any depth) stay blank. */
|
|
822
|
+
.editable[data-ph-hint="slash"]:empty:not(:focus)::before { content:""; }
|
|
781
823
|
.b-h1 .editable { font-size:28px; font-weight:700; letter-spacing:-.02em; padding-top:12px; }
|
|
782
824
|
.b-h2 .editable { font-size:22px; font-weight:650; letter-spacing:-.01em; padding-top:8px; }
|
|
783
825
|
.b-h3 .editable { font-size:18px; font-weight:600; padding-top:4px; }
|
|
@@ -829,7 +871,8 @@ var HTML = `<!doctype html>
|
|
|
829
871
|
.hljs-emphasis { font-style:italic; }
|
|
830
872
|
.hljs-strong { font-weight:700; }
|
|
831
873
|
.marker { width:24px; flex:none; text-align:center; color:var(--fg-soft); user-select:none; padding-top:4px; }
|
|
832
|
-
.b-todo .marker { padding-top:
|
|
874
|
+
.b-todo .marker { box-sizing:content-box; padding-top:3px; height:calc(1em * var(--note-lh));
|
|
875
|
+
display:flex; align-items:center; justify-content:center; }
|
|
833
876
|
.b-todo .marker input { width:15px; height:15px; accent-color:var(--accent); cursor:pointer; }
|
|
834
877
|
.b-divider { padding:10px 0; }
|
|
835
878
|
.b-divider hr { border:0; border-top:1px solid var(--line-strong); margin:0; width:100%; }
|
|
@@ -1016,14 +1059,14 @@ var HTML = `<!doctype html>
|
|
|
1016
1059
|
.optrow .x:hover { background:var(--hover-2); color:var(--danger); }
|
|
1017
1060
|
|
|
1018
1061
|
/* modal */
|
|
1019
|
-
.modal-scrim { position:fixed; inset:0; background:rgba(15,15,25,.4); z-index:100; display:
|
|
1062
|
+
.modal-scrim { position:fixed; inset:0; background:rgba(15,15,25,.4); z-index:100; display:flex; align-items:center; justify-content:center;
|
|
1020
1063
|
opacity:0; pointer-events:none; transition:opacity .15s; padding:20px; }
|
|
1021
1064
|
.modal-scrim.open { opacity:1; pointer-events:auto; }
|
|
1022
|
-
.modal { background:var(--bg); border-radius:16px; box-shadow:var(--shadow-lg); width:440px; max-width:100%; overflow:hidden; }
|
|
1065
|
+
.modal { background:var(--bg); border-radius:16px; box-shadow:var(--shadow-lg); width:440px; max-width:100%; max-height:100%; display:flex; flex-direction:column; overflow:hidden; }
|
|
1023
1066
|
.modal-head { padding:20px 22px 0; }
|
|
1024
1067
|
.modal-head h3 { margin:0; font-size:18px; font-weight:700; letter-spacing:-.01em; }
|
|
1025
1068
|
.modal-head p { margin:6px 0 0; color:var(--muted); font-size:13.5px; }
|
|
1026
|
-
.modal-body { padding:18px 22px; }
|
|
1069
|
+
.modal-body { padding:18px 22px; overflow-y:auto; }
|
|
1027
1070
|
.modal-foot { display:flex; justify-content:flex-end; gap:8px; padding:14px 22px; background:var(--surface); border-top:1px solid var(--line); }
|
|
1028
1071
|
.field-label { font-size:12px; font-weight:600; color:var(--fg-soft); margin:12px 0 5px; }
|
|
1029
1072
|
.field-label:first-child { margin-top:0; }
|
|
@@ -1046,7 +1089,71 @@ var HTML = `<!doctype html>
|
|
|
1046
1089
|
@keyframes slidein { from { opacity:0; transform:translateX(-12px); } }
|
|
1047
1090
|
|
|
1048
1091
|
/* misc */
|
|
1049
|
-
.empty { display:
|
|
1092
|
+
.empty { display:flex; flex-direction:column; align-items:center; justify-content:center; height:100%;
|
|
1093
|
+
color:var(--muted); text-align:center; padding:40px; }
|
|
1094
|
+
|
|
1095
|
+
/* ---- empty state: \u77E5\u8BC6\u751F\u957F\u63D2\u753B ---- */
|
|
1096
|
+
.estate-art { width:240px; height:208px; overflow:visible; animation:idleFloat 5s ease-in-out 1.6s infinite; }
|
|
1097
|
+
.estate-art .stroke-a { fill:none; stroke:var(--accent); stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round; }
|
|
1098
|
+
.estate-art .stroke { fill:none; stroke:var(--line-strong); stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round; }
|
|
1099
|
+
.estate-art .fill-a { fill:var(--accent); }
|
|
1100
|
+
.estate-art .fill-soft { fill:var(--accent-soft); }
|
|
1101
|
+
.estate-art .fill-surface { fill:var(--bg); }
|
|
1102
|
+
.estate-art .ground { fill:var(--accent); }
|
|
1103
|
+
.estate-title { margin:28px 0 0; font-size:18px; font-weight:600; letter-spacing:.2px; color:var(--fg);
|
|
1104
|
+
opacity:0; animation:fadeUp .5s ease forwards 1.0s; }
|
|
1105
|
+
.estate-hint { margin:9px 0 0; font-size:13.5px; line-height:1.6; color:var(--muted); max-width:300px;
|
|
1106
|
+
opacity:0; animation:fadeUp .5s ease forwards 1.15s; }
|
|
1107
|
+
.estate-link { margin:18px 0 0; font-size:14px; font-weight:600; color:var(--accent);
|
|
1108
|
+
display:inline-flex; align-items:center; gap:6px; padding:9px 8px; border-radius:var(--radius-sm);
|
|
1109
|
+
opacity:0; animation:fadeUp .5s ease forwards 1.3s; transition:opacity .15s; }
|
|
1110
|
+
.estate-link:hover { opacity:.7; }
|
|
1111
|
+
/* intro */
|
|
1112
|
+
.draw { stroke-dasharray:100; stroke-dashoffset:100; }
|
|
1113
|
+
@keyframes draw { to { stroke-dashoffset:0; } }
|
|
1114
|
+
@keyframes leafGrow { from { opacity:0; transform:scale(.2) rotate(-10deg); } to { opacity:1; transform:scale(1) rotate(0); } }
|
|
1115
|
+
@keyframes cardUp { from { opacity:0; transform:translateY(16px) scale(.96); } to { opacity:1; transform:translateY(0) scale(1); } }
|
|
1116
|
+
@keyframes groundIn { to { opacity:.1; } }
|
|
1117
|
+
@keyframes glowIn { to { opacity:.6; } }
|
|
1118
|
+
@keyframes fadeUp { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:translateY(0); } }
|
|
1119
|
+
/* idle loops \u2014 each 0% equals the settled rest state to avoid a jump */
|
|
1120
|
+
@keyframes idleFloat { 0%,100% { transform:translateY(0); } 50% { transform:translateY(-5px); } }
|
|
1121
|
+
@keyframes plantSway { 0%,100% { transform:rotate(0deg); } 25% { transform:rotate(-1.3deg); } 75% { transform:rotate(1.3deg); } }
|
|
1122
|
+
@keyframes leafSwayA { 0%,100% { transform:rotate(0deg); } 25% { transform:rotate(-2.6deg); } 75% { transform:rotate(2.6deg); } }
|
|
1123
|
+
@keyframes leafSwayB { 0%,100% { transform:rotate(0deg); } 25% { transform:rotate(2.6deg); } 75% { transform:rotate(-2.6deg); } }
|
|
1124
|
+
@keyframes groundBreathe { 0%,100% { transform:scale(1); opacity:.10; } 50% { transform:scale(1.07); opacity:.15; } }
|
|
1125
|
+
@keyframes glowPulse { 0%,100% { transform:scale(1); opacity:.6; } 50% { transform:scale(1.1); opacity:.85; } }
|
|
1126
|
+
@keyframes particle { 0% { opacity:0; transform:translate(0,0) scale(.4); } 35% { opacity:.85; transform:translate(2px,-4px) scale(1); } 70% { opacity:0; transform:translate(4px,-9px) scale(.5); } 100% { opacity:0; transform:translate(0,0) scale(.4); } }
|
|
1127
|
+
/* wiring */
|
|
1128
|
+
.kg-ground { transform-box:fill-box; transform-origin:center; opacity:0;
|
|
1129
|
+
animation:groundIn .5s ease forwards .1s, groundBreathe 6s ease-in-out 1.6s infinite; }
|
|
1130
|
+
.kg-glow { filter:blur(9px); transform-box:fill-box; transform-origin:center; opacity:0;
|
|
1131
|
+
animation:glowIn .6s ease forwards .1s, glowPulse 4.6s ease-in-out 1.6s infinite; }
|
|
1132
|
+
.kg-cardB { transform-box:fill-box; transform-origin:center; opacity:0; animation:cardUp .55s cubic-bezier(.2,.8,.2,1) forwards .2s; }
|
|
1133
|
+
.kg-cardA { transform-box:fill-box; transform-origin:center; opacity:0; animation:cardUp .55s cubic-bezier(.2,.8,.2,1) forwards .32s; }
|
|
1134
|
+
.kg-plant { transform-box:fill-box; transform-origin:50% 100%; animation:plantSway 6s ease-in-out 1.7s infinite; }
|
|
1135
|
+
.kg-stem { animation:draw .9s ease-out forwards .4s; }
|
|
1136
|
+
.kg-leaf { transform-box:fill-box; }
|
|
1137
|
+
.kg-grow { transform-box:fill-box; opacity:0; }
|
|
1138
|
+
.kg-leaf.l1 { transform-origin:90% 100%; animation:leafSwayA 4.6s ease-in-out 1.7s infinite; }
|
|
1139
|
+
.kg-leaf.l2 { transform-origin:0% 80%; animation:leafSwayB 5.2s ease-in-out 1.8s infinite; }
|
|
1140
|
+
.kg-leaf.l3 { transform-origin:0% 100%; animation:leafSwayA 5.0s ease-in-out 1.9s infinite; }
|
|
1141
|
+
.kg-leaf.l1 .kg-grow { transform-origin:90% 100%; animation:leafGrow .52s cubic-bezier(.18,.9,.24,1.22) forwards .85s; }
|
|
1142
|
+
.kg-leaf.l2 .kg-grow { transform-origin:0% 80%; animation:leafGrow .52s cubic-bezier(.18,.9,.24,1.22) forwards 1.0s; }
|
|
1143
|
+
.kg-leaf.l3 .kg-grow { transform-origin:0% 100%; animation:leafGrow .52s cubic-bezier(.18,.9,.24,1.22) forwards 1.15s; }
|
|
1144
|
+
.kg-particle { transform-box:fill-box; transform-origin:center; opacity:0; }
|
|
1145
|
+
.kg-particle.p1 { animation:particle 4.5s ease-in-out 1.6s infinite; }
|
|
1146
|
+
.kg-particle.p2 { animation:particle 5.2s ease-in-out 2.0s infinite; }
|
|
1147
|
+
.kg-particle.p3 { animation:particle 4.8s ease-in-out 2.6s infinite; }
|
|
1148
|
+
.kg-particle.p4 { animation:particle 5.6s ease-in-out 3.1s infinite; }
|
|
1149
|
+
.kg-particle.p5 { animation:particle 4.2s ease-in-out 2.3s infinite; }
|
|
1150
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1151
|
+
.estate-art, .estate-art *, .estate-title, .estate-hint, .estate-link { animation:none !important; opacity:1 !important; transform:none !important; }
|
|
1152
|
+
.draw { stroke-dashoffset:0 !important; }
|
|
1153
|
+
.kg-ground { opacity:.1 !important; }
|
|
1154
|
+
.kg-glow { opacity:.6 !important; }
|
|
1155
|
+
.kg-particle { opacity:0 !important; }
|
|
1156
|
+
}
|
|
1050
1157
|
.error-bar { margin:12px 24px; padding:10px 14px; background:var(--danger-soft); color:var(--danger); border-radius:var(--radius-sm); font-size:13px; cursor:pointer; }
|
|
1051
1158
|
.view-placeholder { padding:40px; text-align:center; border:1px dashed var(--line-strong); border-radius:var(--radius); color:var(--muted); margin-top:8px; }
|
|
1052
1159
|
.ico.flip { transform:scaleX(-1); }
|
|
@@ -1178,6 +1285,17 @@ var HTML = `<!doctype html>
|
|
|
1178
1285
|
.qn-body .doc { max-width:none; margin:0; padding:14px 22px 30vh; }
|
|
1179
1286
|
.qn-body .doc-title { font-size:24px; margin-bottom:2px; }
|
|
1180
1287
|
.qn-body .doc-meta { display:none; }
|
|
1288
|
+
/* A native checkbox paints an opaque white box when unchecked, which breaks
|
|
1289
|
+
the window's vibrancy blur. Draw a custom box so the unchecked background
|
|
1290
|
+
stays transparent; checked falls back to the accent fill + checkmark. */
|
|
1291
|
+
body.quicknote .b-todo .marker input {
|
|
1292
|
+
appearance:none; -webkit-appearance:none; box-sizing:border-box;
|
|
1293
|
+
border:1.5px solid color-mix(in srgb, var(--fg-soft) 55%, transparent);
|
|
1294
|
+
border-radius:4px; background:transparent; }
|
|
1295
|
+
body.quicknote .b-todo .marker input:checked {
|
|
1296
|
+
border-color:var(--accent);
|
|
1297
|
+
background:var(--accent) center/10px no-repeat
|
|
1298
|
+
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12l5 5L20 7'/%3E%3C/svg%3E"); }
|
|
1181
1299
|
/* Compact menus inside the quick-note window only (dropdown + slash command
|
|
1182
1300
|
share .pop/.item/.lico with the main app; scope under body.quicknote so the
|
|
1183
1301
|
main app keeps its roomier density). */
|
|
@@ -1350,11 +1468,12 @@ CREATE TABLE IF NOT EXISTS documents (
|
|
|
1350
1468
|
-- independent CRDT register so concurrent edits to different blocks merge.
|
|
1351
1469
|
-- order_key is a fractional index (ties broken by id) giving display order.
|
|
1352
1470
|
CREATE TABLE IF NOT EXISTS doc_blocks (
|
|
1353
|
-
id
|
|
1354
|
-
doc_id
|
|
1355
|
-
text
|
|
1356
|
-
order_key
|
|
1357
|
-
|
|
1471
|
+
id TEXT PRIMARY KEY,
|
|
1472
|
+
doc_id TEXT,
|
|
1473
|
+
text TEXT,
|
|
1474
|
+
order_key TEXT,
|
|
1475
|
+
blank_after INTEGER NOT NULL DEFAULT 0, -- extra blank lines kept after this block (user spacing)
|
|
1476
|
+
__deleted INTEGER NOT NULL DEFAULT 0
|
|
1358
1477
|
);
|
|
1359
1478
|
CREATE INDEX IF NOT EXISTS idx_doc_blocks_doc ON doc_blocks(doc_id);
|
|
1360
1479
|
|
|
@@ -1426,9 +1545,20 @@ function deleteDatabase(db, id) {
|
|
|
1426
1545
|
if (!getDatabase(db, id))
|
|
1427
1546
|
return false;
|
|
1428
1547
|
emit(db, "databases", id, "__deleted", 1);
|
|
1548
|
+
const tombstone = (table) => {
|
|
1549
|
+
for (const r of liveChildren(db, table, "database_id", id))
|
|
1550
|
+
emit(db, table, r, "__deleted", 1);
|
|
1551
|
+
};
|
|
1552
|
+
tombstone("properties");
|
|
1553
|
+
tombstone("records");
|
|
1554
|
+
for (const d of liveChildren(db, "documents", "database_id", id))
|
|
1555
|
+
emit(db, "documents", d, "database_id", null);
|
|
1429
1556
|
db.query("DELETE FROM meta WHERE key = 'current_db' AND value = ?").run(id);
|
|
1430
1557
|
return true;
|
|
1431
1558
|
}
|
|
1559
|
+
function liveChildren(db, table, col, parentId) {
|
|
1560
|
+
return db.query(`SELECT id FROM ${table} WHERE ${col} = ? AND __deleted = 0`).all(parentId).map((r) => r.id);
|
|
1561
|
+
}
|
|
1432
1562
|
|
|
1433
1563
|
// src/core/properties.ts
|
|
1434
1564
|
init_ids();
|
|
@@ -1557,9 +1687,13 @@ function setPropertyWidth(db, id, width) {
|
|
|
1557
1687
|
return updateProperty(db, id, { config: { ...cur.config ?? {}, width: w } });
|
|
1558
1688
|
}
|
|
1559
1689
|
function removeProperty(db, id) {
|
|
1560
|
-
|
|
1690
|
+
const prop = getProperty(db, id);
|
|
1691
|
+
if (!prop)
|
|
1561
1692
|
return false;
|
|
1562
1693
|
emit(db, "properties", id, "__deleted", 1);
|
|
1694
|
+
const rows = db.query("SELECT id FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? IS NOT NULL").all(prop.database_id, id);
|
|
1695
|
+
for (const r of rows)
|
|
1696
|
+
emit(db, "records", r.id, id, undefined);
|
|
1563
1697
|
return true;
|
|
1564
1698
|
}
|
|
1565
1699
|
|
|
@@ -2027,6 +2161,10 @@ function migratePeers(db) {
|
|
|
2027
2161
|
db.exec(`ALTER TABLE peers ADD COLUMN ${col} ${decl}`);
|
|
2028
2162
|
}
|
|
2029
2163
|
}
|
|
2164
|
+
function migrateDocBlocks(db) {
|
|
2165
|
+
if (!hasColumn(db, "doc_blocks", "blank_after"))
|
|
2166
|
+
db.exec("ALTER TABLE doc_blocks ADD COLUMN blank_after INTEGER NOT NULL DEFAULT 0");
|
|
2167
|
+
}
|
|
2030
2168
|
function openMetahub() {
|
|
2031
2169
|
ensureDirs();
|
|
2032
2170
|
const db = new Database(dbPath(), { create: true });
|
|
@@ -2034,6 +2172,7 @@ function openMetahub() {
|
|
|
2034
2172
|
runSchema(db);
|
|
2035
2173
|
migrateRecords(db);
|
|
2036
2174
|
migratePeers(db);
|
|
2175
|
+
migrateDocBlocks(db);
|
|
2037
2176
|
return db;
|
|
2038
2177
|
}
|
|
2039
2178
|
|
|
@@ -2065,7 +2204,7 @@ function clearCurrentDatabase(db) {
|
|
|
2065
2204
|
init_ids();
|
|
2066
2205
|
init_crdt();
|
|
2067
2206
|
function liveBlocks(db, docId) {
|
|
2068
|
-
return db.query("SELECT id, text, order_key FROM doc_blocks WHERE doc_id = ? AND __deleted = 0 ORDER BY order_key, id").all(docId);
|
|
2207
|
+
return db.query("SELECT id, text, order_key, blank_after FROM doc_blocks WHERE doc_id = ? AND __deleted = 0 ORDER BY order_key, id").all(docId);
|
|
2069
2208
|
}
|
|
2070
2209
|
function makeBlockId(text) {
|
|
2071
2210
|
return newId("blk", text.split(`
|
|
@@ -2075,29 +2214,37 @@ function emitBlock(db, docId, fields) {
|
|
|
2075
2214
|
const id = makeBlockId(fields.text);
|
|
2076
2215
|
emit(db, "doc_blocks", id, "doc_id", docId);
|
|
2077
2216
|
emit(db, "doc_blocks", id, "order_key", fields.order_key);
|
|
2217
|
+
if (fields.blankAfter)
|
|
2218
|
+
emit(db, "doc_blocks", id, "blank_after", fields.blankAfter);
|
|
2078
2219
|
emit(db, "doc_blocks", id, "text", fields.text);
|
|
2079
2220
|
}
|
|
2080
|
-
function insertBlocks(db, docId,
|
|
2081
|
-
const keys = keysBetween(after, before,
|
|
2082
|
-
|
|
2221
|
+
function insertBlocks(db, docId, blocks, after, before) {
|
|
2222
|
+
const keys = keysBetween(after, before, blocks.length);
|
|
2223
|
+
blocks.forEach((b, i) => emitBlock(db, docId, { text: b.text, order_key: keys[i], blankAfter: b.blankAfter }));
|
|
2083
2224
|
}
|
|
2084
2225
|
function ensureBlocks(db, docId) {
|
|
2085
2226
|
if (db.query("SELECT 1 AS x FROM doc_blocks WHERE doc_id = ? LIMIT 1").get(docId))
|
|
2086
2227
|
return;
|
|
2087
2228
|
const row = db.query("SELECT body FROM documents WHERE id = ?").get(docId);
|
|
2088
|
-
const
|
|
2089
|
-
if (
|
|
2090
|
-
insertBlocks(db, docId,
|
|
2229
|
+
const blocks = parseDocBlocks(row?.body ?? "");
|
|
2230
|
+
if (blocks.length)
|
|
2231
|
+
insertBlocks(db, docId, blocks, null, null);
|
|
2091
2232
|
}
|
|
2092
2233
|
function reconcileBody(db, docId, body) {
|
|
2093
2234
|
const old = liveBlocks(db, docId);
|
|
2094
|
-
const
|
|
2235
|
+
const next = parseDocBlocks(body);
|
|
2236
|
+
const plan = reconcile(old.map((b) => b.text), next.map((b) => b.text));
|
|
2095
2237
|
for (const oi of plan.deleted)
|
|
2096
2238
|
emit(db, "doc_blocks", old[oi].id, "__deleted", 1);
|
|
2097
2239
|
const { items } = plan;
|
|
2098
2240
|
let i = 0;
|
|
2099
2241
|
while (i < items.length) {
|
|
2100
|
-
|
|
2242
|
+
const it = items[i];
|
|
2243
|
+
if ("keep" in it) {
|
|
2244
|
+
const oldB = old[it.keep];
|
|
2245
|
+
const want = next[i].blankAfter;
|
|
2246
|
+
if ((oldB.blank_after ?? 0) !== want)
|
|
2247
|
+
emit(db, "doc_blocks", oldB.id, "blank_after", want);
|
|
2101
2248
|
i++;
|
|
2102
2249
|
continue;
|
|
2103
2250
|
}
|
|
@@ -2106,8 +2253,7 @@ function reconcileBody(db, docId, body) {
|
|
|
2106
2253
|
j++;
|
|
2107
2254
|
const left = i > 0 ? old[items[i - 1].keep].order_key : null;
|
|
2108
2255
|
const right = j < items.length ? old[items[j].keep].order_key : null;
|
|
2109
|
-
|
|
2110
|
-
insertBlocks(db, docId, texts, left, right);
|
|
2256
|
+
insertBlocks(db, docId, next.slice(i, j), left, right);
|
|
2111
2257
|
i = j;
|
|
2112
2258
|
}
|
|
2113
2259
|
}
|
|
@@ -2126,9 +2272,9 @@ function createDocument(db, opts) {
|
|
|
2126
2272
|
if (opts.parent_id !== undefined)
|
|
2127
2273
|
emit(db, "documents", id, "parent_id", opts.parent_id);
|
|
2128
2274
|
if (opts.body !== undefined) {
|
|
2129
|
-
const
|
|
2130
|
-
if (
|
|
2131
|
-
insertBlocks(db, id,
|
|
2275
|
+
const blocks = parseDocBlocks(opts.body);
|
|
2276
|
+
if (blocks.length)
|
|
2277
|
+
insertBlocks(db, id, blocks, null, null);
|
|
2132
2278
|
}
|
|
2133
2279
|
return getDocument(db, id);
|
|
2134
2280
|
}
|
|
@@ -2174,7 +2320,7 @@ function editDocument(db, id, opts) {
|
|
|
2174
2320
|
throw new Error("--old must not be empty");
|
|
2175
2321
|
ensureBlocks(db, id);
|
|
2176
2322
|
const blocks = liveBlocks(db, id);
|
|
2177
|
-
const body =
|
|
2323
|
+
const body = serializeDocBlocks(blocks.map((b) => ({ text: b.text, blankAfter: b.blank_after })));
|
|
2178
2324
|
const count = countOccurrences(body, opts.old);
|
|
2179
2325
|
if (count === 0)
|
|
2180
2326
|
throw new Error(`anchor not found: no match for --old`);
|
|
@@ -2201,32 +2347,39 @@ function appendDocument(db, id, body) {
|
|
|
2201
2347
|
if (!getDocument(db, id))
|
|
2202
2348
|
throw new Error(`no such document: ${id}`);
|
|
2203
2349
|
ensureBlocks(db, id);
|
|
2204
|
-
const
|
|
2205
|
-
if (
|
|
2350
|
+
const next = parseDocBlocks(body);
|
|
2351
|
+
if (next.length) {
|
|
2206
2352
|
const blocks = liveBlocks(db, id);
|
|
2207
2353
|
const last = blocks.length ? blocks[blocks.length - 1].order_key : null;
|
|
2208
|
-
insertBlocks(db, id,
|
|
2354
|
+
insertBlocks(db, id, next, last, null);
|
|
2209
2355
|
}
|
|
2210
|
-
return { id, changed:
|
|
2356
|
+
return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
|
|
2211
2357
|
}
|
|
2212
2358
|
function prependDocument(db, id, body) {
|
|
2213
2359
|
if (!getDocument(db, id))
|
|
2214
2360
|
throw new Error(`no such document: ${id}`);
|
|
2215
2361
|
ensureBlocks(db, id);
|
|
2216
|
-
const
|
|
2217
|
-
if (
|
|
2362
|
+
const next = parseDocBlocks(body);
|
|
2363
|
+
if (next.length) {
|
|
2218
2364
|
const blocks = liveBlocks(db, id);
|
|
2219
2365
|
const first = blocks.length ? blocks[0].order_key : null;
|
|
2220
|
-
insertBlocks(db, id,
|
|
2366
|
+
insertBlocks(db, id, next, null, first);
|
|
2221
2367
|
}
|
|
2222
|
-
return { id, changed:
|
|
2368
|
+
return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
|
|
2223
2369
|
}
|
|
2224
2370
|
function deleteDocument(db, id) {
|
|
2225
2371
|
if (!getDocument(db, id))
|
|
2226
2372
|
return false;
|
|
2227
2373
|
emit(db, "documents", id, "__deleted", 1);
|
|
2374
|
+
for (const b of liveChildIds(db, "doc_blocks", "doc_id", id))
|
|
2375
|
+
emit(db, "doc_blocks", b, "__deleted", 1);
|
|
2376
|
+
for (const c of liveChildIds(db, "documents", "parent_id", id))
|
|
2377
|
+
emit(db, "documents", c, "parent_id", null);
|
|
2228
2378
|
return true;
|
|
2229
2379
|
}
|
|
2380
|
+
function liveChildIds(db, table, col, parentId) {
|
|
2381
|
+
return db.query(`SELECT id FROM ${table} WHERE ${col} = ? AND __deleted = 0`).all(parentId).map((r) => r.id);
|
|
2382
|
+
}
|
|
2230
2383
|
function countOccurrences(haystack, needle) {
|
|
2231
2384
|
if (needle === "")
|
|
2232
2385
|
return 0;
|
|
@@ -2239,26 +2392,112 @@ function countOccurrences(haystack, needle) {
|
|
|
2239
2392
|
return n;
|
|
2240
2393
|
}
|
|
2241
2394
|
// src/core/search.ts
|
|
2395
|
+
init_crdt();
|
|
2242
2396
|
var TEXT_TYPES = "('text','url','select','multi_select','date')";
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2397
|
+
var SEARCH_INDEX_VERSION = "1";
|
|
2398
|
+
function readMeta(db, key) {
|
|
2399
|
+
return db.query("SELECT value FROM meta WHERE key = ?").get(key)?.value ?? null;
|
|
2400
|
+
}
|
|
2401
|
+
function writeMeta(db, key, value) {
|
|
2402
|
+
db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, value);
|
|
2403
|
+
}
|
|
2404
|
+
function reindexDocument(db, id) {
|
|
2405
|
+
db.query("DELETE FROM search_fts WHERE kind = 'document' AND id = ?").run(id);
|
|
2406
|
+
const d = db.query("SELECT id, title, body, database_id FROM documents WHERE id = ? AND __deleted = 0").get(id);
|
|
2407
|
+
if (d)
|
|
2252
2408
|
db.query("INSERT INTO search_fts (kind, id, database_id, title, body) VALUES ('document', ?, ?, ?, ?)").run(d.id, d.database_id, d.title ?? "", d.body ?? "");
|
|
2253
|
-
|
|
2254
|
-
|
|
2409
|
+
}
|
|
2410
|
+
function recordBodyRows(db, where, ...args) {
|
|
2411
|
+
return db.query(`SELECT r.id AS id, r.database_id AS database_id, group_concat(je.value, ' ') AS body
|
|
2255
2412
|
FROM records r, json_each(r.data) je
|
|
2256
2413
|
JOIN properties p ON p.id = je.key AND p.__deleted = 0
|
|
2257
|
-
WHERE r.__deleted = 0 AND p.type IN ${TEXT_TYPES}
|
|
2258
|
-
GROUP BY r.id`).all()
|
|
2259
|
-
|
|
2414
|
+
WHERE ${where} AND r.__deleted = 0 AND p.type IN ${TEXT_TYPES}
|
|
2415
|
+
GROUP BY r.id`).all(...args);
|
|
2416
|
+
}
|
|
2417
|
+
function insertRecordRow(db, r) {
|
|
2418
|
+
db.query("INSERT INTO search_fts (kind, id, database_id, title, body) VALUES ('record', ?, ?, ?, ?)").run(r.id, r.database_id, r.id, r.body ?? "");
|
|
2419
|
+
}
|
|
2420
|
+
function reindexRecord(db, id) {
|
|
2421
|
+
db.query("DELETE FROM search_fts WHERE kind = 'record' AND id = ?").run(id);
|
|
2422
|
+
const r = recordBodyRows(db, "r.id = ?", id)[0];
|
|
2423
|
+
if (r)
|
|
2424
|
+
insertRecordRow(db, r);
|
|
2425
|
+
}
|
|
2426
|
+
function reindexDatabaseRecords(db, databaseId) {
|
|
2427
|
+
db.query("DELETE FROM search_fts WHERE kind = 'record' AND database_id = ?").run(databaseId);
|
|
2428
|
+
for (const r of recordBodyRows(db, "r.database_id = ?", databaseId))
|
|
2429
|
+
insertRecordRow(db, r);
|
|
2430
|
+
}
|
|
2431
|
+
function fullRebuild(db) {
|
|
2432
|
+
db.query("DELETE FROM search_fts").run();
|
|
2433
|
+
for (const d of db.query("SELECT id, title, body, database_id FROM documents WHERE __deleted = 0").all())
|
|
2434
|
+
db.query("INSERT INTO search_fts (kind, id, database_id, title, body) VALUES ('document', ?, ?, ?, ?)").run(d.id, d.database_id, d.title ?? "", d.body ?? "");
|
|
2435
|
+
for (const r of recordBodyRows(db, "1 = 1"))
|
|
2436
|
+
insertRecordRow(db, r);
|
|
2437
|
+
const max = db.query("SELECT MAX(rowid) AS m FROM crdt_changes").get().m ?? 0;
|
|
2438
|
+
writeMeta(db, "search_seq", String(max));
|
|
2439
|
+
writeMeta(db, "search_index_version", SEARCH_INDEX_VERSION);
|
|
2440
|
+
}
|
|
2441
|
+
function incrementalUpdate(db) {
|
|
2442
|
+
const cursor = Number(readMeta(db, "search_seq") ?? "0");
|
|
2443
|
+
const { changes, cursor: next } = changesAfterSeq(db, cursor);
|
|
2444
|
+
if (changes.length === 0)
|
|
2445
|
+
return;
|
|
2446
|
+
const docIds = new Set;
|
|
2447
|
+
const recordIds = new Set;
|
|
2448
|
+
const dbIds = new Set;
|
|
2449
|
+
for (const c of changes) {
|
|
2450
|
+
switch (c.dataset) {
|
|
2451
|
+
case "documents":
|
|
2452
|
+
docIds.add(c.row_id);
|
|
2453
|
+
break;
|
|
2454
|
+
case "doc_blocks": {
|
|
2455
|
+
const blk = db.query("SELECT doc_id FROM doc_blocks WHERE id = ?").get(c.row_id);
|
|
2456
|
+
if (blk?.doc_id)
|
|
2457
|
+
docIds.add(blk.doc_id);
|
|
2458
|
+
break;
|
|
2459
|
+
}
|
|
2460
|
+
case "records":
|
|
2461
|
+
recordIds.add(c.row_id);
|
|
2462
|
+
break;
|
|
2463
|
+
case "properties":
|
|
2464
|
+
if (c.col === "type" || c.col === "__deleted" || c.col === "database_id") {
|
|
2465
|
+
const p = db.query("SELECT database_id FROM properties WHERE id = ?").get(c.row_id);
|
|
2466
|
+
if (p?.database_id)
|
|
2467
|
+
dbIds.add(p.database_id);
|
|
2468
|
+
}
|
|
2469
|
+
break;
|
|
2470
|
+
}
|
|
2260
2471
|
}
|
|
2261
|
-
|
|
2472
|
+
for (const id of docIds)
|
|
2473
|
+
reindexDocument(db, id);
|
|
2474
|
+
for (const dbId of dbIds)
|
|
2475
|
+
reindexDatabaseRecords(db, dbId);
|
|
2476
|
+
for (const id of recordIds) {
|
|
2477
|
+
const r = db.query("SELECT database_id FROM records WHERE id = ?").get(id);
|
|
2478
|
+
if (r?.database_id && dbIds.has(r.database_id))
|
|
2479
|
+
continue;
|
|
2480
|
+
reindexRecord(db, id);
|
|
2481
|
+
}
|
|
2482
|
+
writeMeta(db, "search_seq", String(next));
|
|
2483
|
+
}
|
|
2484
|
+
function ensureIndex(db) {
|
|
2485
|
+
if (!ftsAvailable(db))
|
|
2486
|
+
return false;
|
|
2487
|
+
const version = readMeta(db, "search_index_version");
|
|
2488
|
+
const seq = readMeta(db, "search_seq");
|
|
2489
|
+
db.transaction(() => {
|
|
2490
|
+
if (version !== SEARCH_INDEX_VERSION || seq === null)
|
|
2491
|
+
fullRebuild(db);
|
|
2492
|
+
else
|
|
2493
|
+
incrementalUpdate(db);
|
|
2494
|
+
})();
|
|
2495
|
+
return true;
|
|
2496
|
+
}
|
|
2497
|
+
function rebuildSearchIndex(db) {
|
|
2498
|
+
if (!ftsAvailable(db))
|
|
2499
|
+
return false;
|
|
2500
|
+
db.transaction(() => fullRebuild(db))();
|
|
2262
2501
|
return true;
|
|
2263
2502
|
}
|
|
2264
2503
|
function ftsSearch(db, query, limit) {
|
|
@@ -16772,8 +17011,9 @@ function getServerConfig(db) {
|
|
|
16772
17011
|
// package.json
|
|
16773
17012
|
var package_default = {
|
|
16774
17013
|
name: "@tensorix/metahub",
|
|
16775
|
-
version: "0.1.
|
|
17014
|
+
version: "0.1.6",
|
|
16776
17015
|
description: "Metahub core library and CLI",
|
|
17016
|
+
license: "AGPL-3.0-only",
|
|
16777
17017
|
type: "module",
|
|
16778
17018
|
main: "./dist/index.js",
|
|
16779
17019
|
module: "./dist/index.js",
|
|
@@ -16791,14 +17031,17 @@ var package_default = {
|
|
|
16791
17031
|
},
|
|
16792
17032
|
files: [
|
|
16793
17033
|
"dist",
|
|
16794
|
-
"README.md"
|
|
17034
|
+
"README.md",
|
|
17035
|
+
"README.zh-CN.md",
|
|
17036
|
+
"LICENSE"
|
|
16795
17037
|
],
|
|
16796
17038
|
scripts: {
|
|
16797
17039
|
dev: "bun --hot src/cli/index.ts",
|
|
16798
17040
|
build: "bun run scripts/build.ts",
|
|
16799
17041
|
"build:binaries": "bun run scripts/compile-binaries.ts",
|
|
16800
17042
|
prepublishOnly: "bun run build",
|
|
16801
|
-
test: "bun test"
|
|
17043
|
+
test: "bun test",
|
|
17044
|
+
release: "bun run scripts/tag-release.ts"
|
|
16802
17045
|
},
|
|
16803
17046
|
dependencies: {
|
|
16804
17047
|
"@clack/prompts": "^1.5.0",
|
|
@@ -16907,7 +17150,15 @@ function handle(fn) {
|
|
|
16907
17150
|
}
|
|
16908
17151
|
};
|
|
16909
17152
|
}
|
|
17153
|
+
var VersionSchema = exports_external.object({ version: exports_external.string() });
|
|
16910
17154
|
var webuiRoutes = [
|
|
17155
|
+
{
|
|
17156
|
+
method: "GET",
|
|
17157
|
+
path: "/api/version",
|
|
17158
|
+
summary: "Version of the running core (sidecar). Used by the desktop app's settings footer.",
|
|
17159
|
+
response: VersionSchema,
|
|
17160
|
+
handler: () => Response.json({ version: package_default.version })
|
|
17161
|
+
},
|
|
16911
17162
|
{
|
|
16912
17163
|
method: "GET",
|
|
16913
17164
|
path: "/api/databases",
|
|
@@ -17635,6 +17886,16 @@ function unauthorized() {
|
|
|
17635
17886
|
}
|
|
17636
17887
|
|
|
17637
17888
|
// src/core/sync/server.ts
|
|
17889
|
+
class PortInUseError extends Error {
|
|
17890
|
+
port;
|
|
17891
|
+
host;
|
|
17892
|
+
constructor(port, host) {
|
|
17893
|
+
super(`port ${port} is already in use \u2014 another mh server may be running. ` + `Stop it, or start on a free port with --port <n>.`);
|
|
17894
|
+
this.port = port;
|
|
17895
|
+
this.host = host;
|
|
17896
|
+
this.name = "PortInUseError";
|
|
17897
|
+
}
|
|
17898
|
+
}
|
|
17638
17899
|
function scalarHtml(specUrl) {
|
|
17639
17900
|
return `<!doctype html><html><head>
|
|
17640
17901
|
<meta charset="utf-8"><title>Metahub API</title>
|
|
@@ -17659,48 +17920,60 @@ function startServer(opts = {}) {
|
|
|
17659
17920
|
ttlMs: DEFAULT_TTL_MS,
|
|
17660
17921
|
graceMs: DEFAULT_GRACE_MS
|
|
17661
17922
|
};
|
|
17662
|
-
|
|
17663
|
-
|
|
17664
|
-
|
|
17665
|
-
|
|
17666
|
-
|
|
17667
|
-
|
|
17668
|
-
|
|
17669
|
-
|
|
17670
|
-
|
|
17671
|
-
|
|
17672
|
-
|
|
17673
|
-
|
|
17923
|
+
let server;
|
|
17924
|
+
try {
|
|
17925
|
+
server = Bun.serve({
|
|
17926
|
+
port,
|
|
17927
|
+
hostname: host,
|
|
17928
|
+
async fetch(req) {
|
|
17929
|
+
const url2 = new URL(req.url);
|
|
17930
|
+
if (url2.pathname === SYNC_PATH) {
|
|
17931
|
+
if (!acceptsSyncToken(req, url2, auth, db))
|
|
17932
|
+
return unauthorized();
|
|
17933
|
+
} else {
|
|
17934
|
+
const exempt = url2.pathname === HEALTH_PATH || url2.pathname === RENEW_PATH || url2.pathname === PAIR_PATH;
|
|
17935
|
+
if (!exempt && !hasValidToken(req, url2, auth)) {
|
|
17936
|
+
return wantsHtml(req) ? new Response(unlockPage(), { headers: HTML_HEADERS }) : unauthorized();
|
|
17937
|
+
}
|
|
17674
17938
|
}
|
|
17939
|
+
if (url2.pathname === RENEW_PATH) {
|
|
17940
|
+
const r = renewToken(req, url2, auth);
|
|
17941
|
+
return r ? Response.json(r) : unauthorized();
|
|
17942
|
+
}
|
|
17943
|
+
if (req.method === "GET" && url2.pathname === "/docs.json") {
|
|
17944
|
+
return Response.json(buildOpenApi(package_default.version));
|
|
17945
|
+
}
|
|
17946
|
+
if (req.method === "GET" && url2.pathname === "/docs") {
|
|
17947
|
+
return withShim(new Response(scalarHtml("/docs.json"), { headers: HTML_HEADERS }), auth);
|
|
17948
|
+
}
|
|
17949
|
+
if (req.method === "GET" && (url2.pathname === "/" || url2.pathname === "/webui.js")) {
|
|
17950
|
+
const { serveWebui: serveWebui2 } = await Promise.resolve().then(() => (init_webui(), exports_webui));
|
|
17951
|
+
const res = await serveWebui2(req);
|
|
17952
|
+
if (res)
|
|
17953
|
+
return withShim(res, auth);
|
|
17954
|
+
}
|
|
17955
|
+
if (req.method === "GET" && url2.pathname.startsWith("/sites/")) {
|
|
17956
|
+
const { serveSite: serveSite2 } = await Promise.resolve().then(() => (init_sites_serve(), exports_sites_serve));
|
|
17957
|
+
const res = await serveSite2(req, ctx);
|
|
17958
|
+
if (res)
|
|
17959
|
+
return withShim(res, auth);
|
|
17960
|
+
}
|
|
17961
|
+
const route = routes.find((r) => r.method === req.method && r.path === url2.pathname);
|
|
17962
|
+
if (route)
|
|
17963
|
+
return route.handler(req, ctx);
|
|
17964
|
+
return new Response("not found", { status: 404 });
|
|
17675
17965
|
}
|
|
17676
|
-
|
|
17677
|
-
|
|
17678
|
-
|
|
17679
|
-
|
|
17680
|
-
|
|
17681
|
-
return Response.json(buildOpenApi(package_default.version));
|
|
17682
|
-
}
|
|
17683
|
-
if (req.method === "GET" && url2.pathname === "/docs") {
|
|
17684
|
-
return withShim(new Response(scalarHtml("/docs.json"), { headers: HTML_HEADERS }), auth);
|
|
17685
|
-
}
|
|
17686
|
-
if (req.method === "GET" && (url2.pathname === "/" || url2.pathname === "/webui.js")) {
|
|
17687
|
-
const { serveWebui: serveWebui2 } = await Promise.resolve().then(() => (init_webui(), exports_webui));
|
|
17688
|
-
const res = await serveWebui2(req);
|
|
17689
|
-
if (res)
|
|
17690
|
-
return withShim(res, auth);
|
|
17691
|
-
}
|
|
17692
|
-
if (req.method === "GET" && url2.pathname.startsWith("/sites/")) {
|
|
17693
|
-
const { serveSite: serveSite2 } = await Promise.resolve().then(() => (init_sites_serve(), exports_sites_serve));
|
|
17694
|
-
const res = await serveSite2(req, ctx);
|
|
17695
|
-
if (res)
|
|
17696
|
-
return withShim(res, auth);
|
|
17697
|
-
}
|
|
17698
|
-
const route = routes.find((r) => r.method === req.method && r.path === url2.pathname);
|
|
17699
|
-
if (route)
|
|
17700
|
-
return route.handler(req, ctx);
|
|
17701
|
-
return new Response("not found", { status: 404 });
|
|
17966
|
+
});
|
|
17967
|
+
} catch (e) {
|
|
17968
|
+
const code = e?.code;
|
|
17969
|
+
if (code === "EADDRINUSE") {
|
|
17970
|
+
throw new PortInUseError(port, host);
|
|
17702
17971
|
}
|
|
17703
|
-
|
|
17972
|
+
if (code === "EACCES") {
|
|
17973
|
+
throw new Error(`permission denied binding ${host}:${port} \u2014 ports below 1024 need elevated privileges; pick a higher port with --port`);
|
|
17974
|
+
}
|
|
17975
|
+
throw e;
|
|
17976
|
+
}
|
|
17704
17977
|
let timer = null;
|
|
17705
17978
|
if (autoSync && syncIntervalMs > 0) {
|
|
17706
17979
|
timer = setInterval(() => {
|
|
@@ -17709,12 +17982,17 @@ function startServer(opts = {}) {
|
|
|
17709
17982
|
timer.unref?.();
|
|
17710
17983
|
}
|
|
17711
17984
|
const active = activeToken(auth);
|
|
17985
|
+
const authMode = opts.debug ? "disabled" : opts.token ? "static" : "managed";
|
|
17712
17986
|
return {
|
|
17713
17987
|
server,
|
|
17714
17988
|
node,
|
|
17715
17989
|
port: server.port ?? port,
|
|
17990
|
+
host,
|
|
17716
17991
|
token: active?.token ?? null,
|
|
17717
17992
|
exp: active?.exp ?? null,
|
|
17993
|
+
authMode,
|
|
17994
|
+
autoSync: Boolean(autoSync && syncIntervalMs > 0),
|
|
17995
|
+
syncIntervalMs,
|
|
17718
17996
|
stop() {
|
|
17719
17997
|
if (timer)
|
|
17720
17998
|
clearInterval(timer);
|
|
@@ -17732,7 +18010,7 @@ export {
|
|
|
17732
18010
|
slugify,
|
|
17733
18011
|
setPropertyWidth,
|
|
17734
18012
|
setCurrentDatabase,
|
|
17735
|
-
|
|
18013
|
+
serializeDocBlocks,
|
|
17736
18014
|
search,
|
|
17737
18015
|
runSchema,
|
|
17738
18016
|
resolveSite,
|
|
@@ -17741,12 +18019,13 @@ export {
|
|
|
17741
18019
|
resolveCandidates,
|
|
17742
18020
|
removeProperty,
|
|
17743
18021
|
reconcile,
|
|
18022
|
+
rebuildSearchIndex,
|
|
17744
18023
|
randomSuffix,
|
|
17745
18024
|
putFile,
|
|
17746
18025
|
putBlob,
|
|
17747
18026
|
prependDocument,
|
|
17748
18027
|
parseHlc,
|
|
17749
|
-
|
|
18028
|
+
parseDocBlocks,
|
|
17750
18029
|
openMetahub,
|
|
17751
18030
|
observeHlc,
|
|
17752
18031
|
nextHlc,
|
|
@@ -17754,6 +18033,7 @@ export {
|
|
|
17754
18033
|
moveRecord,
|
|
17755
18034
|
migrateRecords,
|
|
17756
18035
|
migratePeers,
|
|
18036
|
+
migrateDocBlocks,
|
|
17757
18037
|
metahubHome,
|
|
17758
18038
|
maybeAutoIndex,
|
|
17759
18039
|
makeId,
|
|
@@ -17810,6 +18090,7 @@ export {
|
|
|
17810
18090
|
SyncRequestSchema,
|
|
17811
18091
|
SYNC_PATH,
|
|
17812
18092
|
RENEW_PATH,
|
|
18093
|
+
PortInUseError,
|
|
17813
18094
|
PairResponseSchema,
|
|
17814
18095
|
PairRequestSchema,
|
|
17815
18096
|
PROP_TYPES,
|
|
@@ -17819,4 +18100,4 @@ export {
|
|
|
17819
18100
|
ChangeSchema
|
|
17820
18101
|
};
|
|
17821
18102
|
|
|
17822
|
-
//# debugId=
|
|
18103
|
+
//# debugId=F7BD99C8461B833164756E2164756E21
|