@tensorix/metahub 0.1.4 → 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 +86 -166
- package/README.zh-CN.md +127 -0
- package/dist/cli.js +117 -67
- package/dist/cli.js.map +8 -8
- package/dist/core/blocks.d.ts +23 -4
- package/dist/core/blocks.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/schema.d.ts +1 -1
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/sync/webui.d.ts.map +1 -1
- package/dist/index.js +120 -69
- package/dist/index.js.map +8 -8
- package/dist/webui.js +30 -31
- package/package.json +5 -2
package/dist/core/blocks.d.ts
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* A document block plus the blank lines the user left after it. `blankAfter` is
|
|
3
|
+
* the count *beyond* the standard single-blank-line separator (so a normal
|
|
4
|
+
* paragraph break is 0); for the last block it's the trailing blank-line run.
|
|
5
|
+
* This is what lets deliberate vertical spacing survive a save/reload without
|
|
6
|
+
* representing blank lines as zero-content blocks (which would churn the
|
|
7
|
+
* text-keyed reconcile). See [[blocks]] doc-block model.
|
|
8
|
+
*/
|
|
9
|
+
export interface DocBlock {
|
|
10
|
+
text: string;
|
|
11
|
+
blankAfter: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Split markdown into doc blocks, preserving blank-line runs as `blankAfter`.
|
|
15
|
+
* Leading blank lines (before the first block) are dropped; one blank line
|
|
16
|
+
* between two blocks is the standard separator, every extra blank line is kept.
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseDocBlocks(md: string): DocBlock[];
|
|
19
|
+
/** Serialize doc blocks back to a body, re-emitting kept blank-line runs. */
|
|
20
|
+
export declare function serializeDocBlocks(blocks: readonly {
|
|
21
|
+
text: string | null;
|
|
22
|
+
blankAfter?: number;
|
|
23
|
+
}[]): string;
|
|
5
24
|
export type ReconcileItem = {
|
|
6
25
|
keep: number;
|
|
7
26
|
} | {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../src/core/blocks.ts"],"names":[],"mappings":"AAIA,
|
|
1
|
+
{"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../src/core/blocks.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,EAAE,CAmDrD;AAED,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,GAC9D,MAAM,CAUR;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAClE,MAAM,WAAW,aAAa;IAC5B,uFAAuF;IACvF,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,oDAAoD;IACpD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,aAAa,CAa/E"}
|
package/dist/core/db.d.ts
CHANGED
|
@@ -14,6 +14,12 @@ export declare function migrateRecords(db: Database): void;
|
|
|
14
14
|
* never drops the table so existing replication cursors survive.
|
|
15
15
|
*/
|
|
16
16
|
export declare function migratePeers(db: Database): void;
|
|
17
|
+
/**
|
|
18
|
+
* Add the `blank_after` column to a legacy `doc_blocks` table. Idempotent —
|
|
19
|
+
* guarded by hasColumn; existing blocks default to 0 (canonical single-blank-line
|
|
20
|
+
* separators), so the migration never changes an existing document's body.
|
|
21
|
+
*/
|
|
22
|
+
export declare function migrateDocBlocks(db: Database): void;
|
|
17
23
|
/** Open (and migrate) the on-disk metahub database for the resolved home. */
|
|
18
24
|
export declare function openMetahub(): Database;
|
|
19
25
|
//# sourceMappingURL=db.d.ts.map
|
package/dist/core/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/core/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAMtC,wBAAgB,UAAU,IAAI,IAAI,CAGjC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAO5C;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAKlD;AAeD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAuBjD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAa/C;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,IAAI,QAAQ,
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/core/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAMtC,wBAAgB,UAAU,IAAI,IAAI,CAGjC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAO5C;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAKlD;AAeD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAuBjD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAa/C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAGnD;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,IAAI,QAAQ,CAStC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../src/core/documents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAM3C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../src/core/documents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAM3C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AA6FxD,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAShE;AAID,wBAAgB,cAAc,CAC5B,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/E,WAAW,CAWb;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAMxE;AAED,wBAAgB,aAAa,CAC3B,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACtD,eAAe,EAAE,CAYnB;AAED,wBAAgB,cAAc,CAC5B,EAAE,EAAE,QAAQ,EACZ,EAAE,EAAE,MAAM,EACV,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACzF,WAAW,CAoBb;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,EAAE,EAAE,QAAQ,EACZ,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACzE,aAAa,CAmCf;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAUpF;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAUrF;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAUhE"}
|
package/dist/core/schema.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const CORE_SCHEMA = "\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT\n);\n\n-- CRDT oplog: one row per field assignment. Source of truth for sync.\nCREATE TABLE IF NOT EXISTS crdt_changes (\n hlc TEXT NOT NULL,\n node_id TEXT NOT NULL,\n dataset TEXT NOT NULL,\n row_id TEXT NOT NULL,\n col TEXT NOT NULL,\n value TEXT,\n PRIMARY KEY (dataset, row_id, col, hlc)\n);\nCREATE INDEX IF NOT EXISTS idx_changes_hlc ON crdt_changes(hlc);\n\n-- Per-peer replication cursors (SQLite rowids: monotonic in insertion order,\n-- so no change is ever skipped regardless of HLC/clock skew). Outbound side of\n-- a pairing: \"token\" is the credential the *remote* issued to us (sent as a\n-- Bearer header when we sync to them). See migratePeers in db.ts for the columns\n-- added to legacy databases.\nCREATE TABLE IF NOT EXISTS peers (\n url TEXT PRIMARY KEY,\n pull_cursor INTEGER NOT NULL DEFAULT 0,\n push_cursor INTEGER NOT NULL DEFAULT 0,\n token TEXT,\n label TEXT,\n node_id TEXT,\n enabled INTEGER NOT NULL DEFAULT 1,\n last_sync_at INTEGER,\n last_status TEXT,\n last_error TEXT\n);\n\n-- Inbound side of a pairing: credentials *we* issued to remote devices. A peer\n-- presents one of these on /sync; acceptsSyncToken() checks it alongside the\n-- managed master token. Revoke by deleting the row.\nCREATE TABLE IF NOT EXISTS peer_grants (\n token TEXT PRIMARY KEY,\n peer_url TEXT,\n node_id TEXT,\n created_at INTEGER\n);\n\n-- One-time pairing codes minted by \"mh config peer code\" / POST /api/pair/new.\n-- Short-lived and single-use: redeemed during the pairing handshake, then the\n-- two devices exchange durable peer_grants. used/exp gate redemption.\nCREATE TABLE IF NOT EXISTS pairing_codes (\n code TEXT PRIMARY KEY,\n exp INTEGER NOT NULL,\n used INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER\n);\n\nCREATE TABLE IF NOT EXISTS databases (\n id TEXT PRIMARY KEY,\n name TEXT,\n icon TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS properties (\n id TEXT PRIMARY KEY,\n database_id TEXT,\n name TEXT,\n type TEXT,\n config TEXT,\n position REAL,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_properties_db ON properties(database_id);\n\n-- One row per record. Field cells live in the data JSON object, keyed by\n-- property id (rename-safe). Each (record,property) is still an independent\n-- CRDT register in the oplog; materialize() folds the winner into data via\n-- json_set/json_remove. Hot fields get expression indexes on data->>'propid'\n-- (see indexing.ts) so filter/sort/limit push down to SQL.\nCREATE TABLE IF NOT EXISTS records (\n id TEXT PRIMARY KEY,\n database_id TEXT,\n created_hlc TEXT,\n order_key TEXT,\n data TEXT NOT NULL DEFAULT '{}',\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_records_db_hlc ON records(database_id, created_hlc);\n\nCREATE TABLE IF NOT EXISTS documents (\n id TEXT PRIMARY KEY,\n title TEXT,\n body TEXT, -- materialized cache: serialized from doc_blocks\n database_id TEXT,\n parent_id TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\n-- Document body as an ordered list of blocks; each (block,field) is an\n-- independent CRDT register so concurrent edits to different blocks merge.\n-- order_key is a fractional index (ties broken by id) giving display order.\nCREATE TABLE IF NOT EXISTS doc_blocks (\n id
|
|
1
|
+
export declare const CORE_SCHEMA = "\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT\n);\n\n-- CRDT oplog: one row per field assignment. Source of truth for sync.\nCREATE TABLE IF NOT EXISTS crdt_changes (\n hlc TEXT NOT NULL,\n node_id TEXT NOT NULL,\n dataset TEXT NOT NULL,\n row_id TEXT NOT NULL,\n col TEXT NOT NULL,\n value TEXT,\n PRIMARY KEY (dataset, row_id, col, hlc)\n);\nCREATE INDEX IF NOT EXISTS idx_changes_hlc ON crdt_changes(hlc);\n\n-- Per-peer replication cursors (SQLite rowids: monotonic in insertion order,\n-- so no change is ever skipped regardless of HLC/clock skew). Outbound side of\n-- a pairing: \"token\" is the credential the *remote* issued to us (sent as a\n-- Bearer header when we sync to them). See migratePeers in db.ts for the columns\n-- added to legacy databases.\nCREATE TABLE IF NOT EXISTS peers (\n url TEXT PRIMARY KEY,\n pull_cursor INTEGER NOT NULL DEFAULT 0,\n push_cursor INTEGER NOT NULL DEFAULT 0,\n token TEXT,\n label TEXT,\n node_id TEXT,\n enabled INTEGER NOT NULL DEFAULT 1,\n last_sync_at INTEGER,\n last_status TEXT,\n last_error TEXT\n);\n\n-- Inbound side of a pairing: credentials *we* issued to remote devices. A peer\n-- presents one of these on /sync; acceptsSyncToken() checks it alongside the\n-- managed master token. Revoke by deleting the row.\nCREATE TABLE IF NOT EXISTS peer_grants (\n token TEXT PRIMARY KEY,\n peer_url TEXT,\n node_id TEXT,\n created_at INTEGER\n);\n\n-- One-time pairing codes minted by \"mh config peer code\" / POST /api/pair/new.\n-- Short-lived and single-use: redeemed during the pairing handshake, then the\n-- two devices exchange durable peer_grants. used/exp gate redemption.\nCREATE TABLE IF NOT EXISTS pairing_codes (\n code TEXT PRIMARY KEY,\n exp INTEGER NOT NULL,\n used INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER\n);\n\nCREATE TABLE IF NOT EXISTS databases (\n id TEXT PRIMARY KEY,\n name TEXT,\n icon TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS properties (\n id TEXT PRIMARY KEY,\n database_id TEXT,\n name TEXT,\n type TEXT,\n config TEXT,\n position REAL,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_properties_db ON properties(database_id);\n\n-- One row per record. Field cells live in the data JSON object, keyed by\n-- property id (rename-safe). Each (record,property) is still an independent\n-- CRDT register in the oplog; materialize() folds the winner into data via\n-- json_set/json_remove. Hot fields get expression indexes on data->>'propid'\n-- (see indexing.ts) so filter/sort/limit push down to SQL.\nCREATE TABLE IF NOT EXISTS records (\n id TEXT PRIMARY KEY,\n database_id TEXT,\n created_hlc TEXT,\n order_key TEXT,\n data TEXT NOT NULL DEFAULT '{}',\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_records_db_hlc ON records(database_id, created_hlc);\n\nCREATE TABLE IF NOT EXISTS documents (\n id TEXT PRIMARY KEY,\n title TEXT,\n body TEXT, -- materialized cache: serialized from doc_blocks\n database_id TEXT,\n parent_id TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\n-- Document body as an ordered list of blocks; each (block,field) is an\n-- independent CRDT register so concurrent edits to different blocks merge.\n-- order_key is a fractional index (ties broken by id) giving display order.\nCREATE TABLE IF NOT EXISTS doc_blocks (\n id TEXT PRIMARY KEY,\n doc_id TEXT,\n text TEXT,\n order_key TEXT,\n blank_after INTEGER NOT NULL DEFAULT 0, -- extra blank lines kept after this block (user spacing)\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_doc_blocks_doc ON doc_blocks(doc_id);\n\n-- A named static site (Supabase-Storage-style bucket). Served at /sites/<name>/.\nCREATE TABLE IF NOT EXISTS sites (\n id TEXT PRIMARY KEY,\n name TEXT, -- URL slug; resolved by getSiteByName\n title TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\n-- One row per file in a site. content holds inline utf8/base64 text, or a blob\n-- hash (see cache.ts) when encoding = 'blob'. (site_id, path) maps to a stable\n-- id so re-uploads merge as a CRDT register instead of duplicating.\nCREATE TABLE IF NOT EXISTS site_files (\n id TEXT PRIMARY KEY,\n site_id TEXT,\n path TEXT,\n content_type TEXT,\n encoding TEXT, -- 'utf8' | 'base64' | 'blob'\n content TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_site_files_site ON site_files(site_id);\n";
|
|
2
2
|
export declare const FTS_SCHEMA = "\nCREATE VIRTUAL TABLE IF NOT EXISTS search_fts USING fts5(\n kind UNINDEXED,\n id UNINDEXED,\n database_id UNINDEXED,\n title,\n body\n);\n";
|
|
3
3
|
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,mwJAwIvB,CAAC;AAGF,eAAO,MAAM,UAAU,sJAQtB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webui.d.ts","sourceRoot":"","sources":["../../../src/core/sync/webui.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"webui.d.ts","sourceRoot":"","sources":["../../../src/core/sync/webui.ts"],"names":[],"mappings":"AAgvBA;;;8EAG8E;AAC9E,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE/C;AAoBD;;;;2EAI2E;AAC3E,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAM/C;AAED;0CAC0C;AAC1C,wBAAsB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAiBvE"}
|
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",
|
|
@@ -605,6 +611,7 @@ var HTML = `<!doctype html>
|
|
|
605
611
|
--radius:8px; --radius-sm:6px;
|
|
606
612
|
--ui:"Hanken Grotesk",-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
|
|
607
613
|
--mono:"JetBrains Mono",ui-monospace,SFMono-Regular,monospace;
|
|
614
|
+
--note-lh:1.6;
|
|
608
615
|
--code-bg:#f8f8f6;
|
|
609
616
|
--hl-cmt:#9a9aa2; --hl-key:#a626a4; --hl-str:#28823f; --hl-num:#b76a00;
|
|
610
617
|
--hl-fn:#3f51d6; --hl-type:#0a7ea4; --hl-var:#c2410c;
|
|
@@ -637,7 +644,7 @@ var HTML = `<!doctype html>
|
|
|
637
644
|
* { box-sizing: border-box; }
|
|
638
645
|
html, body { margin: 0; height: 100%; }
|
|
639
646
|
body { font-family: var(--ui); font-size: 14px; line-height: 1.55; color: var(--fg); background: var(--bg);
|
|
640
|
-
overflow: hidden;
|
|
647
|
+
overflow: hidden; text-rendering: auto; }
|
|
641
648
|
button { font: inherit; color: inherit; cursor: pointer; background: none; border: 0; padding: 0; }
|
|
642
649
|
input, textarea { font: inherit; color: inherit; }
|
|
643
650
|
.muted { color: var(--muted); }
|
|
@@ -693,6 +700,8 @@ var HTML = `<!doctype html>
|
|
|
693
700
|
.dragging { opacity:.35; }
|
|
694
701
|
.sb-footer { flex:none; padding:6px 8px; border-top:1px solid var(--line); }
|
|
695
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; }
|
|
696
705
|
|
|
697
706
|
/* settings page */
|
|
698
707
|
.set-page { max-width:680px; margin:0 auto; padding:40px 28px 64px; }
|
|
@@ -702,8 +711,19 @@ var HTML = `<!doctype html>
|
|
|
702
711
|
.set-section-head { font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.07em; color:var(--muted); margin-bottom:4px; }
|
|
703
712
|
.set-section-desc { color:var(--fg-soft); margin-bottom:16px; }
|
|
704
713
|
.set-footer { margin-top:40px; padding-top:20px; border-top:1px solid var(--line); display:flex;
|
|
705
|
-
justify-content:center; gap:
|
|
706
|
-
.set-footer-sep { opacity:.
|
|
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; } }
|
|
707
727
|
.theme-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
|
|
708
728
|
.theme-card { position:relative; display:flex; flex-direction:column; align-items:flex-start; gap:10px;
|
|
709
729
|
text-align:left; padding:16px; border:1px solid var(--line-strong); border-radius:var(--radius);
|
|
@@ -796,8 +816,10 @@ var HTML = `<!doctype html>
|
|
|
796
816
|
/* The list item's host gutter controls the whole unit, so suppress the nested
|
|
797
817
|
code block's own gutter (it would overlap the list marker on the same row). */
|
|
798
818
|
.block.list-code-host + .block-wrap.nested .gutter { display:none; }
|
|
799
|
-
.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); }
|
|
800
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:""; }
|
|
801
823
|
.b-h1 .editable { font-size:28px; font-weight:700; letter-spacing:-.02em; padding-top:12px; }
|
|
802
824
|
.b-h2 .editable { font-size:22px; font-weight:650; letter-spacing:-.01em; padding-top:8px; }
|
|
803
825
|
.b-h3 .editable { font-size:18px; font-weight:600; padding-top:4px; }
|
|
@@ -849,7 +871,8 @@ var HTML = `<!doctype html>
|
|
|
849
871
|
.hljs-emphasis { font-style:italic; }
|
|
850
872
|
.hljs-strong { font-weight:700; }
|
|
851
873
|
.marker { width:24px; flex:none; text-align:center; color:var(--fg-soft); user-select:none; padding-top:4px; }
|
|
852
|
-
.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; }
|
|
853
876
|
.b-todo .marker input { width:15px; height:15px; accent-color:var(--accent); cursor:pointer; }
|
|
854
877
|
.b-divider { padding:10px 0; }
|
|
855
878
|
.b-divider hr { border:0; border-top:1px solid var(--line-strong); margin:0; width:100%; }
|
|
@@ -1036,14 +1059,14 @@ var HTML = `<!doctype html>
|
|
|
1036
1059
|
.optrow .x:hover { background:var(--hover-2); color:var(--danger); }
|
|
1037
1060
|
|
|
1038
1061
|
/* modal */
|
|
1039
|
-
.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;
|
|
1040
1063
|
opacity:0; pointer-events:none; transition:opacity .15s; padding:20px; }
|
|
1041
1064
|
.modal-scrim.open { opacity:1; pointer-events:auto; }
|
|
1042
|
-
.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; }
|
|
1043
1066
|
.modal-head { padding:20px 22px 0; }
|
|
1044
1067
|
.modal-head h3 { margin:0; font-size:18px; font-weight:700; letter-spacing:-.01em; }
|
|
1045
1068
|
.modal-head p { margin:6px 0 0; color:var(--muted); font-size:13.5px; }
|
|
1046
|
-
.modal-body { padding:18px 22px; }
|
|
1069
|
+
.modal-body { padding:18px 22px; overflow-y:auto; }
|
|
1047
1070
|
.modal-foot { display:flex; justify-content:flex-end; gap:8px; padding:14px 22px; background:var(--surface); border-top:1px solid var(--line); }
|
|
1048
1071
|
.field-label { font-size:12px; font-weight:600; color:var(--fg-soft); margin:12px 0 5px; }
|
|
1049
1072
|
.field-label:first-child { margin-top:0; }
|
|
@@ -1262,6 +1285,17 @@ var HTML = `<!doctype html>
|
|
|
1262
1285
|
.qn-body .doc { max-width:none; margin:0; padding:14px 22px 30vh; }
|
|
1263
1286
|
.qn-body .doc-title { font-size:24px; margin-bottom:2px; }
|
|
1264
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"); }
|
|
1265
1299
|
/* Compact menus inside the quick-note window only (dropdown + slash command
|
|
1266
1300
|
share .pop/.item/.lico with the main app; scope under body.quicknote so the
|
|
1267
1301
|
main app keeps its roomier density). */
|
|
@@ -1434,11 +1468,12 @@ CREATE TABLE IF NOT EXISTS documents (
|
|
|
1434
1468
|
-- independent CRDT register so concurrent edits to different blocks merge.
|
|
1435
1469
|
-- order_key is a fractional index (ties broken by id) giving display order.
|
|
1436
1470
|
CREATE TABLE IF NOT EXISTS doc_blocks (
|
|
1437
|
-
id
|
|
1438
|
-
doc_id
|
|
1439
|
-
text
|
|
1440
|
-
order_key
|
|
1441
|
-
|
|
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
|
|
1442
1477
|
);
|
|
1443
1478
|
CREATE INDEX IF NOT EXISTS idx_doc_blocks_doc ON doc_blocks(doc_id);
|
|
1444
1479
|
|
|
@@ -2126,6 +2161,10 @@ function migratePeers(db) {
|
|
|
2126
2161
|
db.exec(`ALTER TABLE peers ADD COLUMN ${col} ${decl}`);
|
|
2127
2162
|
}
|
|
2128
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
|
+
}
|
|
2129
2168
|
function openMetahub() {
|
|
2130
2169
|
ensureDirs();
|
|
2131
2170
|
const db = new Database(dbPath(), { create: true });
|
|
@@ -2133,6 +2172,7 @@ function openMetahub() {
|
|
|
2133
2172
|
runSchema(db);
|
|
2134
2173
|
migrateRecords(db);
|
|
2135
2174
|
migratePeers(db);
|
|
2175
|
+
migrateDocBlocks(db);
|
|
2136
2176
|
return db;
|
|
2137
2177
|
}
|
|
2138
2178
|
|
|
@@ -2164,7 +2204,7 @@ function clearCurrentDatabase(db) {
|
|
|
2164
2204
|
init_ids();
|
|
2165
2205
|
init_crdt();
|
|
2166
2206
|
function liveBlocks(db, docId) {
|
|
2167
|
-
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);
|
|
2168
2208
|
}
|
|
2169
2209
|
function makeBlockId(text) {
|
|
2170
2210
|
return newId("blk", text.split(`
|
|
@@ -2174,29 +2214,37 @@ function emitBlock(db, docId, fields) {
|
|
|
2174
2214
|
const id = makeBlockId(fields.text);
|
|
2175
2215
|
emit(db, "doc_blocks", id, "doc_id", docId);
|
|
2176
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);
|
|
2177
2219
|
emit(db, "doc_blocks", id, "text", fields.text);
|
|
2178
2220
|
}
|
|
2179
|
-
function insertBlocks(db, docId,
|
|
2180
|
-
const keys = keysBetween(after, before,
|
|
2181
|
-
|
|
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 }));
|
|
2182
2224
|
}
|
|
2183
2225
|
function ensureBlocks(db, docId) {
|
|
2184
2226
|
if (db.query("SELECT 1 AS x FROM doc_blocks WHERE doc_id = ? LIMIT 1").get(docId))
|
|
2185
2227
|
return;
|
|
2186
2228
|
const row = db.query("SELECT body FROM documents WHERE id = ?").get(docId);
|
|
2187
|
-
const
|
|
2188
|
-
if (
|
|
2189
|
-
insertBlocks(db, docId,
|
|
2229
|
+
const blocks = parseDocBlocks(row?.body ?? "");
|
|
2230
|
+
if (blocks.length)
|
|
2231
|
+
insertBlocks(db, docId, blocks, null, null);
|
|
2190
2232
|
}
|
|
2191
2233
|
function reconcileBody(db, docId, body) {
|
|
2192
2234
|
const old = liveBlocks(db, docId);
|
|
2193
|
-
const
|
|
2235
|
+
const next = parseDocBlocks(body);
|
|
2236
|
+
const plan = reconcile(old.map((b) => b.text), next.map((b) => b.text));
|
|
2194
2237
|
for (const oi of plan.deleted)
|
|
2195
2238
|
emit(db, "doc_blocks", old[oi].id, "__deleted", 1);
|
|
2196
2239
|
const { items } = plan;
|
|
2197
2240
|
let i = 0;
|
|
2198
2241
|
while (i < items.length) {
|
|
2199
|
-
|
|
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);
|
|
2200
2248
|
i++;
|
|
2201
2249
|
continue;
|
|
2202
2250
|
}
|
|
@@ -2205,8 +2253,7 @@ function reconcileBody(db, docId, body) {
|
|
|
2205
2253
|
j++;
|
|
2206
2254
|
const left = i > 0 ? old[items[i - 1].keep].order_key : null;
|
|
2207
2255
|
const right = j < items.length ? old[items[j].keep].order_key : null;
|
|
2208
|
-
|
|
2209
|
-
insertBlocks(db, docId, texts, left, right);
|
|
2256
|
+
insertBlocks(db, docId, next.slice(i, j), left, right);
|
|
2210
2257
|
i = j;
|
|
2211
2258
|
}
|
|
2212
2259
|
}
|
|
@@ -2225,9 +2272,9 @@ function createDocument(db, opts) {
|
|
|
2225
2272
|
if (opts.parent_id !== undefined)
|
|
2226
2273
|
emit(db, "documents", id, "parent_id", opts.parent_id);
|
|
2227
2274
|
if (opts.body !== undefined) {
|
|
2228
|
-
const
|
|
2229
|
-
if (
|
|
2230
|
-
insertBlocks(db, id,
|
|
2275
|
+
const blocks = parseDocBlocks(opts.body);
|
|
2276
|
+
if (blocks.length)
|
|
2277
|
+
insertBlocks(db, id, blocks, null, null);
|
|
2231
2278
|
}
|
|
2232
2279
|
return getDocument(db, id);
|
|
2233
2280
|
}
|
|
@@ -2273,7 +2320,7 @@ function editDocument(db, id, opts) {
|
|
|
2273
2320
|
throw new Error("--old must not be empty");
|
|
2274
2321
|
ensureBlocks(db, id);
|
|
2275
2322
|
const blocks = liveBlocks(db, id);
|
|
2276
|
-
const body =
|
|
2323
|
+
const body = serializeDocBlocks(blocks.map((b) => ({ text: b.text, blankAfter: b.blank_after })));
|
|
2277
2324
|
const count = countOccurrences(body, opts.old);
|
|
2278
2325
|
if (count === 0)
|
|
2279
2326
|
throw new Error(`anchor not found: no match for --old`);
|
|
@@ -2300,25 +2347,25 @@ function appendDocument(db, id, body) {
|
|
|
2300
2347
|
if (!getDocument(db, id))
|
|
2301
2348
|
throw new Error(`no such document: ${id}`);
|
|
2302
2349
|
ensureBlocks(db, id);
|
|
2303
|
-
const
|
|
2304
|
-
if (
|
|
2350
|
+
const next = parseDocBlocks(body);
|
|
2351
|
+
if (next.length) {
|
|
2305
2352
|
const blocks = liveBlocks(db, id);
|
|
2306
2353
|
const last = blocks.length ? blocks[blocks.length - 1].order_key : null;
|
|
2307
|
-
insertBlocks(db, id,
|
|
2354
|
+
insertBlocks(db, id, next, last, null);
|
|
2308
2355
|
}
|
|
2309
|
-
return { id, changed:
|
|
2356
|
+
return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
|
|
2310
2357
|
}
|
|
2311
2358
|
function prependDocument(db, id, body) {
|
|
2312
2359
|
if (!getDocument(db, id))
|
|
2313
2360
|
throw new Error(`no such document: ${id}`);
|
|
2314
2361
|
ensureBlocks(db, id);
|
|
2315
|
-
const
|
|
2316
|
-
if (
|
|
2362
|
+
const next = parseDocBlocks(body);
|
|
2363
|
+
if (next.length) {
|
|
2317
2364
|
const blocks = liveBlocks(db, id);
|
|
2318
2365
|
const first = blocks.length ? blocks[0].order_key : null;
|
|
2319
|
-
insertBlocks(db, id,
|
|
2366
|
+
insertBlocks(db, id, next, null, first);
|
|
2320
2367
|
}
|
|
2321
|
-
return { id, changed:
|
|
2368
|
+
return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
|
|
2322
2369
|
}
|
|
2323
2370
|
function deleteDocument(db, id) {
|
|
2324
2371
|
if (!getDocument(db, id))
|
|
@@ -16964,8 +17011,9 @@ function getServerConfig(db) {
|
|
|
16964
17011
|
// package.json
|
|
16965
17012
|
var package_default = {
|
|
16966
17013
|
name: "@tensorix/metahub",
|
|
16967
|
-
version: "0.1.
|
|
17014
|
+
version: "0.1.6",
|
|
16968
17015
|
description: "Metahub core library and CLI",
|
|
17016
|
+
license: "AGPL-3.0-only",
|
|
16969
17017
|
type: "module",
|
|
16970
17018
|
main: "./dist/index.js",
|
|
16971
17019
|
module: "./dist/index.js",
|
|
@@ -16983,7 +17031,9 @@ var package_default = {
|
|
|
16983
17031
|
},
|
|
16984
17032
|
files: [
|
|
16985
17033
|
"dist",
|
|
16986
|
-
"README.md"
|
|
17034
|
+
"README.md",
|
|
17035
|
+
"README.zh-CN.md",
|
|
17036
|
+
"LICENSE"
|
|
16987
17037
|
],
|
|
16988
17038
|
scripts: {
|
|
16989
17039
|
dev: "bun --hot src/cli/index.ts",
|
|
@@ -17960,7 +18010,7 @@ export {
|
|
|
17960
18010
|
slugify,
|
|
17961
18011
|
setPropertyWidth,
|
|
17962
18012
|
setCurrentDatabase,
|
|
17963
|
-
|
|
18013
|
+
serializeDocBlocks,
|
|
17964
18014
|
search,
|
|
17965
18015
|
runSchema,
|
|
17966
18016
|
resolveSite,
|
|
@@ -17975,7 +18025,7 @@ export {
|
|
|
17975
18025
|
putBlob,
|
|
17976
18026
|
prependDocument,
|
|
17977
18027
|
parseHlc,
|
|
17978
|
-
|
|
18028
|
+
parseDocBlocks,
|
|
17979
18029
|
openMetahub,
|
|
17980
18030
|
observeHlc,
|
|
17981
18031
|
nextHlc,
|
|
@@ -17983,6 +18033,7 @@ export {
|
|
|
17983
18033
|
moveRecord,
|
|
17984
18034
|
migrateRecords,
|
|
17985
18035
|
migratePeers,
|
|
18036
|
+
migrateDocBlocks,
|
|
17986
18037
|
metahubHome,
|
|
17987
18038
|
maybeAutoIndex,
|
|
17988
18039
|
makeId,
|
|
@@ -18049,4 +18100,4 @@ export {
|
|
|
18049
18100
|
ChangeSchema
|
|
18050
18101
|
};
|
|
18051
18102
|
|
|
18052
|
-
//# debugId=
|
|
18103
|
+
//# debugId=F7BD99C8461B833164756E2164756E21
|