@tensorix/metahub 0.1.4 → 0.1.7
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 +142 -73
- package/dist/cli.js.map +9 -9
- 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/server.d.ts.map +1 -1
- package/dist/core/sync/webui.d.ts.map +1 -1
- package/dist/index.js +129 -73
- package/dist/index.js.map +9 -9
- 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":"server.d.ts","sourceRoot":"","sources":["../../../src/core/sync/server.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,qFAAqF;IACrF,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,wFAAwF;IACxF,QAAQ,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IAC5C,8CAA8C;IAC9C,QAAQ,EAAE,OAAO,CAAC;IAClB,qEAAqE;IACrE,cAAc,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,6EAA6E;AAC7E,qBAAa,cAAe,SAAQ,KAAK;IAErC,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;gBADZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM;CAQxB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAYD,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,IAAI,GAAE,aAAkB,GAAG,aAAa,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/core/sync/server.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,qFAAqF;IACrF,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,wFAAwF;IACxF,QAAQ,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IAC5C,8CAA8C;IAC9C,QAAQ,EAAE,OAAO,CAAC;IAClB,qEAAqE;IACrE,cAAc,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,6EAA6E;AAC7E,qBAAa,cAAe,SAAQ,KAAK;IAErC,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;gBADZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM;CAQxB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAYD,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,IAAI,GAAE,aAAkB,GAAG,aAAa,CAkJnE"}
|
|
@@ -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,CAsBvE"}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
3
7
|
var __export = (target, all) => {
|
|
4
8
|
for (var name in all)
|
|
5
9
|
__defProp(target, name, {
|
|
6
10
|
get: all[name],
|
|
7
11
|
enumerable: true,
|
|
8
12
|
configurable: true,
|
|
9
|
-
set: (
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
10
14
|
});
|
|
11
15
|
};
|
|
12
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -121,7 +125,7 @@ function observeHlc(db, node, remote, now = Date.now()) {
|
|
|
121
125
|
}
|
|
122
126
|
|
|
123
127
|
// src/core/blocks.ts
|
|
124
|
-
function
|
|
128
|
+
function parseDocBlocks(md) {
|
|
125
129
|
if (!md)
|
|
126
130
|
return [];
|
|
127
131
|
const lines = md.replace(/\r\n?/g, `
|
|
@@ -131,24 +135,24 @@ function parseBlocks(md) {
|
|
|
131
135
|
let cur = [];
|
|
132
136
|
let fenceChar = null;
|
|
133
137
|
let fenceLen = 0;
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
cur.
|
|
137
|
-
|
|
138
|
-
cur
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
const closeBlock = () => {
|
|
139
|
+
if (cur.length) {
|
|
140
|
+
blocks.push({ text: cur.join(`
|
|
141
|
+
`), blankAfter: 0 });
|
|
142
|
+
cur = [];
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const onBlank = () => {
|
|
146
|
+
closeBlock();
|
|
147
|
+
if (blocks.length)
|
|
148
|
+
blocks[blocks.length - 1].blankAfter++;
|
|
143
149
|
};
|
|
144
150
|
for (const raw of lines) {
|
|
145
151
|
if (fenceChar) {
|
|
146
152
|
cur.push(raw);
|
|
147
153
|
const m = raw.match(/^\s*(`{3,}|~{3,})\s*$/);
|
|
148
154
|
if (m && m[1][0] === fenceChar && m[1].length >= fenceLen) {
|
|
149
|
-
|
|
150
|
-
`));
|
|
151
|
-
cur = [];
|
|
155
|
+
closeBlock();
|
|
152
156
|
fenceChar = null;
|
|
153
157
|
fenceLen = 0;
|
|
154
158
|
}
|
|
@@ -156,28 +160,34 @@ function parseBlocks(md) {
|
|
|
156
160
|
}
|
|
157
161
|
const open = raw.match(/^\s*(`{3,}|~{3,})/);
|
|
158
162
|
if (open) {
|
|
159
|
-
|
|
163
|
+
closeBlock();
|
|
160
164
|
fenceChar = open[1][0];
|
|
161
165
|
fenceLen = open[1].length;
|
|
162
166
|
cur.push(raw);
|
|
163
167
|
} else if (raw.trim() === "") {
|
|
164
|
-
|
|
168
|
+
onBlank();
|
|
165
169
|
} else {
|
|
166
170
|
cur.push(raw);
|
|
167
171
|
}
|
|
168
172
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
`));
|
|
173
|
-
} else {
|
|
174
|
-
flushPara();
|
|
175
|
-
}
|
|
173
|
+
closeBlock();
|
|
174
|
+
for (let i = 0;i < blocks.length - 1; i++)
|
|
175
|
+
blocks[i].blankAfter = Math.max(0, blocks[i].blankAfter - 1);
|
|
176
176
|
return blocks;
|
|
177
177
|
}
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
function serializeDocBlocks(blocks) {
|
|
179
|
+
const live = blocks.filter((b) => !!b.text && b.text.length > 0);
|
|
180
|
+
const out = [];
|
|
181
|
+
live.forEach((b, i) => {
|
|
182
|
+
out.push(...b.text.split(`
|
|
183
|
+
`));
|
|
184
|
+
const extra = Math.max(0, b.blankAfter ?? 0);
|
|
185
|
+
if (i < live.length - 1)
|
|
186
|
+
out.push("");
|
|
187
|
+
for (let k = 0;k < extra; k++)
|
|
188
|
+
out.push("");
|
|
189
|
+
});
|
|
190
|
+
return out.join(`
|
|
181
191
|
`);
|
|
182
192
|
}
|
|
183
193
|
function reconcile(oldTexts, newTexts) {
|
|
@@ -234,9 +244,9 @@ function isBlockManaged(db, docId) {
|
|
|
234
244
|
return db.query("SELECT 1 AS x FROM doc_blocks WHERE doc_id = ? LIMIT 1").get(docId) != null;
|
|
235
245
|
}
|
|
236
246
|
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);
|
|
247
|
+
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
248
|
ensureRow(db, "documents", docId);
|
|
239
|
-
db.query("UPDATE documents SET body = ? WHERE id = ?").run(
|
|
249
|
+
db.query("UPDATE documents SET body = ? WHERE id = ?").run(serializeDocBlocks(rows.map((r) => ({ text: r.text, blankAfter: r.blank_after ?? 0 }))), docId);
|
|
240
250
|
}
|
|
241
251
|
function materialize(db, dataset, rowId, col, valueJson) {
|
|
242
252
|
if (dataset === "records") {
|
|
@@ -336,7 +346,7 @@ var init_crdt = __esm(() => {
|
|
|
336
346
|
},
|
|
337
347
|
doc_blocks: {
|
|
338
348
|
table: "doc_blocks",
|
|
339
|
-
cols: new Set(["doc_id", "text", "order_key", "__deleted"])
|
|
349
|
+
cols: new Set(["doc_id", "text", "order_key", "blank_after", "__deleted"])
|
|
340
350
|
},
|
|
341
351
|
sites: {
|
|
342
352
|
table: "sites",
|
|
@@ -572,6 +582,7 @@ async function serveWebui(req) {
|
|
|
572
582
|
headers: { "content-type": "text/javascript; charset=utf-8" }
|
|
573
583
|
});
|
|
574
584
|
} catch (e) {
|
|
585
|
+
console.error("[webui] failed to serve /webui.js \u2014", e);
|
|
575
586
|
return new Response(`webui build failed: ${e}`, { status: 500 });
|
|
576
587
|
}
|
|
577
588
|
}
|
|
@@ -605,6 +616,7 @@ var HTML = `<!doctype html>
|
|
|
605
616
|
--radius:8px; --radius-sm:6px;
|
|
606
617
|
--ui:"Hanken Grotesk",-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
|
|
607
618
|
--mono:"JetBrains Mono",ui-monospace,SFMono-Regular,monospace;
|
|
619
|
+
--note-lh:1.6;
|
|
608
620
|
--code-bg:#f8f8f6;
|
|
609
621
|
--hl-cmt:#9a9aa2; --hl-key:#a626a4; --hl-str:#28823f; --hl-num:#b76a00;
|
|
610
622
|
--hl-fn:#3f51d6; --hl-type:#0a7ea4; --hl-var:#c2410c;
|
|
@@ -637,7 +649,7 @@ var HTML = `<!doctype html>
|
|
|
637
649
|
* { box-sizing: border-box; }
|
|
638
650
|
html, body { margin: 0; height: 100%; }
|
|
639
651
|
body { font-family: var(--ui); font-size: 14px; line-height: 1.55; color: var(--fg); background: var(--bg);
|
|
640
|
-
overflow: hidden;
|
|
652
|
+
overflow: hidden; text-rendering: auto; }
|
|
641
653
|
button { font: inherit; color: inherit; cursor: pointer; background: none; border: 0; padding: 0; }
|
|
642
654
|
input, textarea { font: inherit; color: inherit; }
|
|
643
655
|
.muted { color: var(--muted); }
|
|
@@ -693,6 +705,8 @@ var HTML = `<!doctype html>
|
|
|
693
705
|
.dragging { opacity:.35; }
|
|
694
706
|
.sb-footer { flex:none; padding:6px 8px; border-top:1px solid var(--line); }
|
|
695
707
|
.sb-footer .navitem { font-size:13.5px; }
|
|
708
|
+
.nav-dot { flex:none; width:7px; height:7px; border-radius:50%; background:var(--danger);
|
|
709
|
+
box-shadow:0 0 0 2px var(--surface); margin-right:2px; }
|
|
696
710
|
|
|
697
711
|
/* settings page */
|
|
698
712
|
.set-page { max-width:680px; margin:0 auto; padding:40px 28px 64px; }
|
|
@@ -702,8 +716,19 @@ var HTML = `<!doctype html>
|
|
|
702
716
|
.set-section-head { font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.07em; color:var(--muted); margin-bottom:4px; }
|
|
703
717
|
.set-section-desc { color:var(--fg-soft); margin-bottom:16px; }
|
|
704
718
|
.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:.
|
|
719
|
+
flex-wrap:wrap; justify-content:center; align-items:center; gap:7px; color:var(--muted); font-size:12px; }
|
|
720
|
+
.set-footer-sep { opacity:.45; }
|
|
721
|
+
.ver-num { font-family:var(--mono); font-size:11px; color:var(--fg-soft); letter-spacing:-.01em; }
|
|
722
|
+
.ver-up { display:inline-flex; align-items:center; gap:6px; }
|
|
723
|
+
.ver-up.err .ver-msg { color:var(--danger); }
|
|
724
|
+
.ver-act { background:none; border:0; padding:0; margin:0; font:inherit; font-size:12px; line-height:1;
|
|
725
|
+
color:var(--muted); cursor:pointer; transition:color .12s; }
|
|
726
|
+
.ver-act:hover { color:var(--accent); }
|
|
727
|
+
.ver-act.accent { color:var(--accent); font-weight:600; }
|
|
728
|
+
.ver-act[aria-disabled="true"] { opacity:.6; cursor:default; pointer-events:none; }
|
|
729
|
+
.ver-dot { width:6px; height:6px; border-radius:50%; background:var(--accent); flex:none; }
|
|
730
|
+
.ver-dot.pulse { animation:verpulse 1.8s ease-in-out infinite; }
|
|
731
|
+
@keyframes verpulse { 0%,100% { opacity:1; } 50% { opacity:.4; } }
|
|
707
732
|
.theme-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
|
|
708
733
|
.theme-card { position:relative; display:flex; flex-direction:column; align-items:flex-start; gap:10px;
|
|
709
734
|
text-align:left; padding:16px; border:1px solid var(--line-strong); border-radius:var(--radius);
|
|
@@ -796,8 +821,10 @@ var HTML = `<!doctype html>
|
|
|
796
821
|
/* The list item's host gutter controls the whole unit, so suppress the nested
|
|
797
822
|
code block's own gutter (it would overlap the list marker on the same row). */
|
|
798
823
|
.block.list-code-host + .block-wrap.nested .gutter { display:none; }
|
|
799
|
-
.editable { outline:none; flex:1; min-width:0; padding:3px 2px; line-height:
|
|
824
|
+
.editable { outline:none; flex:1; min-width:0; padding:3px 2px; line-height:var(--note-lh); }
|
|
800
825
|
.editable:empty::before { content:attr(data-ph); color:var(--muted); pointer-events:none; }
|
|
826
|
+
/* The generic "/" prompt only shows on the focused line; idle blank lines (paragraphs and list items, any depth) stay blank. */
|
|
827
|
+
.editable[data-ph-hint="slash"]:empty:not(:focus)::before { content:""; }
|
|
801
828
|
.b-h1 .editable { font-size:28px; font-weight:700; letter-spacing:-.02em; padding-top:12px; }
|
|
802
829
|
.b-h2 .editable { font-size:22px; font-weight:650; letter-spacing:-.01em; padding-top:8px; }
|
|
803
830
|
.b-h3 .editable { font-size:18px; font-weight:600; padding-top:4px; }
|
|
@@ -849,7 +876,8 @@ var HTML = `<!doctype html>
|
|
|
849
876
|
.hljs-emphasis { font-style:italic; }
|
|
850
877
|
.hljs-strong { font-weight:700; }
|
|
851
878
|
.marker { width:24px; flex:none; text-align:center; color:var(--fg-soft); user-select:none; padding-top:4px; }
|
|
852
|
-
.b-todo .marker { padding-top:
|
|
879
|
+
.b-todo .marker { box-sizing:content-box; padding-top:3px; height:calc(1em * var(--note-lh));
|
|
880
|
+
display:flex; align-items:center; justify-content:center; }
|
|
853
881
|
.b-todo .marker input { width:15px; height:15px; accent-color:var(--accent); cursor:pointer; }
|
|
854
882
|
.b-divider { padding:10px 0; }
|
|
855
883
|
.b-divider hr { border:0; border-top:1px solid var(--line-strong); margin:0; width:100%; }
|
|
@@ -1036,14 +1064,14 @@ var HTML = `<!doctype html>
|
|
|
1036
1064
|
.optrow .x:hover { background:var(--hover-2); color:var(--danger); }
|
|
1037
1065
|
|
|
1038
1066
|
/* modal */
|
|
1039
|
-
.modal-scrim { position:fixed; inset:0; background:rgba(15,15,25,.4); z-index:100; display:
|
|
1067
|
+
.modal-scrim { position:fixed; inset:0; background:rgba(15,15,25,.4); z-index:100; display:flex; align-items:center; justify-content:center;
|
|
1040
1068
|
opacity:0; pointer-events:none; transition:opacity .15s; padding:20px; }
|
|
1041
1069
|
.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; }
|
|
1070
|
+
.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
1071
|
.modal-head { padding:20px 22px 0; }
|
|
1044
1072
|
.modal-head h3 { margin:0; font-size:18px; font-weight:700; letter-spacing:-.01em; }
|
|
1045
1073
|
.modal-head p { margin:6px 0 0; color:var(--muted); font-size:13.5px; }
|
|
1046
|
-
.modal-body { padding:18px 22px; }
|
|
1074
|
+
.modal-body { padding:18px 22px; overflow-y:auto; }
|
|
1047
1075
|
.modal-foot { display:flex; justify-content:flex-end; gap:8px; padding:14px 22px; background:var(--surface); border-top:1px solid var(--line); }
|
|
1048
1076
|
.field-label { font-size:12px; font-weight:600; color:var(--fg-soft); margin:12px 0 5px; }
|
|
1049
1077
|
.field-label:first-child { margin-top:0; }
|
|
@@ -1262,6 +1290,17 @@ var HTML = `<!doctype html>
|
|
|
1262
1290
|
.qn-body .doc { max-width:none; margin:0; padding:14px 22px 30vh; }
|
|
1263
1291
|
.qn-body .doc-title { font-size:24px; margin-bottom:2px; }
|
|
1264
1292
|
.qn-body .doc-meta { display:none; }
|
|
1293
|
+
/* A native checkbox paints an opaque white box when unchecked, which breaks
|
|
1294
|
+
the window's vibrancy blur. Draw a custom box so the unchecked background
|
|
1295
|
+
stays transparent; checked falls back to the accent fill + checkmark. */
|
|
1296
|
+
body.quicknote .b-todo .marker input {
|
|
1297
|
+
appearance:none; -webkit-appearance:none; box-sizing:border-box;
|
|
1298
|
+
border:1.5px solid color-mix(in srgb, var(--fg-soft) 55%, transparent);
|
|
1299
|
+
border-radius:4px; background:transparent; }
|
|
1300
|
+
body.quicknote .b-todo .marker input:checked {
|
|
1301
|
+
border-color:var(--accent);
|
|
1302
|
+
background:var(--accent) center/10px no-repeat
|
|
1303
|
+
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
1304
|
/* Compact menus inside the quick-note window only (dropdown + slash command
|
|
1266
1305
|
share .pop/.item/.lico with the main app; scope under body.quicknote so the
|
|
1267
1306
|
main app keeps its roomier density). */
|
|
@@ -1434,11 +1473,12 @@ CREATE TABLE IF NOT EXISTS documents (
|
|
|
1434
1473
|
-- independent CRDT register so concurrent edits to different blocks merge.
|
|
1435
1474
|
-- order_key is a fractional index (ties broken by id) giving display order.
|
|
1436
1475
|
CREATE TABLE IF NOT EXISTS doc_blocks (
|
|
1437
|
-
id
|
|
1438
|
-
doc_id
|
|
1439
|
-
text
|
|
1440
|
-
order_key
|
|
1441
|
-
|
|
1476
|
+
id TEXT PRIMARY KEY,
|
|
1477
|
+
doc_id TEXT,
|
|
1478
|
+
text TEXT,
|
|
1479
|
+
order_key TEXT,
|
|
1480
|
+
blank_after INTEGER NOT NULL DEFAULT 0, -- extra blank lines kept after this block (user spacing)
|
|
1481
|
+
__deleted INTEGER NOT NULL DEFAULT 0
|
|
1442
1482
|
);
|
|
1443
1483
|
CREATE INDEX IF NOT EXISTS idx_doc_blocks_doc ON doc_blocks(doc_id);
|
|
1444
1484
|
|
|
@@ -2126,6 +2166,10 @@ function migratePeers(db) {
|
|
|
2126
2166
|
db.exec(`ALTER TABLE peers ADD COLUMN ${col} ${decl}`);
|
|
2127
2167
|
}
|
|
2128
2168
|
}
|
|
2169
|
+
function migrateDocBlocks(db) {
|
|
2170
|
+
if (!hasColumn(db, "doc_blocks", "blank_after"))
|
|
2171
|
+
db.exec("ALTER TABLE doc_blocks ADD COLUMN blank_after INTEGER NOT NULL DEFAULT 0");
|
|
2172
|
+
}
|
|
2129
2173
|
function openMetahub() {
|
|
2130
2174
|
ensureDirs();
|
|
2131
2175
|
const db = new Database(dbPath(), { create: true });
|
|
@@ -2133,6 +2177,7 @@ function openMetahub() {
|
|
|
2133
2177
|
runSchema(db);
|
|
2134
2178
|
migrateRecords(db);
|
|
2135
2179
|
migratePeers(db);
|
|
2180
|
+
migrateDocBlocks(db);
|
|
2136
2181
|
return db;
|
|
2137
2182
|
}
|
|
2138
2183
|
|
|
@@ -2164,7 +2209,7 @@ function clearCurrentDatabase(db) {
|
|
|
2164
2209
|
init_ids();
|
|
2165
2210
|
init_crdt();
|
|
2166
2211
|
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);
|
|
2212
|
+
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
2213
|
}
|
|
2169
2214
|
function makeBlockId(text) {
|
|
2170
2215
|
return newId("blk", text.split(`
|
|
@@ -2174,29 +2219,37 @@ function emitBlock(db, docId, fields) {
|
|
|
2174
2219
|
const id = makeBlockId(fields.text);
|
|
2175
2220
|
emit(db, "doc_blocks", id, "doc_id", docId);
|
|
2176
2221
|
emit(db, "doc_blocks", id, "order_key", fields.order_key);
|
|
2222
|
+
if (fields.blankAfter)
|
|
2223
|
+
emit(db, "doc_blocks", id, "blank_after", fields.blankAfter);
|
|
2177
2224
|
emit(db, "doc_blocks", id, "text", fields.text);
|
|
2178
2225
|
}
|
|
2179
|
-
function insertBlocks(db, docId,
|
|
2180
|
-
const keys = keysBetween(after, before,
|
|
2181
|
-
|
|
2226
|
+
function insertBlocks(db, docId, blocks, after, before) {
|
|
2227
|
+
const keys = keysBetween(after, before, blocks.length);
|
|
2228
|
+
blocks.forEach((b, i) => emitBlock(db, docId, { text: b.text, order_key: keys[i], blankAfter: b.blankAfter }));
|
|
2182
2229
|
}
|
|
2183
2230
|
function ensureBlocks(db, docId) {
|
|
2184
2231
|
if (db.query("SELECT 1 AS x FROM doc_blocks WHERE doc_id = ? LIMIT 1").get(docId))
|
|
2185
2232
|
return;
|
|
2186
2233
|
const row = db.query("SELECT body FROM documents WHERE id = ?").get(docId);
|
|
2187
|
-
const
|
|
2188
|
-
if (
|
|
2189
|
-
insertBlocks(db, docId,
|
|
2234
|
+
const blocks = parseDocBlocks(row?.body ?? "");
|
|
2235
|
+
if (blocks.length)
|
|
2236
|
+
insertBlocks(db, docId, blocks, null, null);
|
|
2190
2237
|
}
|
|
2191
2238
|
function reconcileBody(db, docId, body) {
|
|
2192
2239
|
const old = liveBlocks(db, docId);
|
|
2193
|
-
const
|
|
2240
|
+
const next = parseDocBlocks(body);
|
|
2241
|
+
const plan = reconcile(old.map((b) => b.text), next.map((b) => b.text));
|
|
2194
2242
|
for (const oi of plan.deleted)
|
|
2195
2243
|
emit(db, "doc_blocks", old[oi].id, "__deleted", 1);
|
|
2196
2244
|
const { items } = plan;
|
|
2197
2245
|
let i = 0;
|
|
2198
2246
|
while (i < items.length) {
|
|
2199
|
-
|
|
2247
|
+
const it = items[i];
|
|
2248
|
+
if ("keep" in it) {
|
|
2249
|
+
const oldB = old[it.keep];
|
|
2250
|
+
const want = next[i].blankAfter;
|
|
2251
|
+
if ((oldB.blank_after ?? 0) !== want)
|
|
2252
|
+
emit(db, "doc_blocks", oldB.id, "blank_after", want);
|
|
2200
2253
|
i++;
|
|
2201
2254
|
continue;
|
|
2202
2255
|
}
|
|
@@ -2205,8 +2258,7 @@ function reconcileBody(db, docId, body) {
|
|
|
2205
2258
|
j++;
|
|
2206
2259
|
const left = i > 0 ? old[items[i - 1].keep].order_key : null;
|
|
2207
2260
|
const right = j < items.length ? old[items[j].keep].order_key : null;
|
|
2208
|
-
|
|
2209
|
-
insertBlocks(db, docId, texts, left, right);
|
|
2261
|
+
insertBlocks(db, docId, next.slice(i, j), left, right);
|
|
2210
2262
|
i = j;
|
|
2211
2263
|
}
|
|
2212
2264
|
}
|
|
@@ -2225,9 +2277,9 @@ function createDocument(db, opts) {
|
|
|
2225
2277
|
if (opts.parent_id !== undefined)
|
|
2226
2278
|
emit(db, "documents", id, "parent_id", opts.parent_id);
|
|
2227
2279
|
if (opts.body !== undefined) {
|
|
2228
|
-
const
|
|
2229
|
-
if (
|
|
2230
|
-
insertBlocks(db, id,
|
|
2280
|
+
const blocks = parseDocBlocks(opts.body);
|
|
2281
|
+
if (blocks.length)
|
|
2282
|
+
insertBlocks(db, id, blocks, null, null);
|
|
2231
2283
|
}
|
|
2232
2284
|
return getDocument(db, id);
|
|
2233
2285
|
}
|
|
@@ -2273,7 +2325,7 @@ function editDocument(db, id, opts) {
|
|
|
2273
2325
|
throw new Error("--old must not be empty");
|
|
2274
2326
|
ensureBlocks(db, id);
|
|
2275
2327
|
const blocks = liveBlocks(db, id);
|
|
2276
|
-
const body =
|
|
2328
|
+
const body = serializeDocBlocks(blocks.map((b) => ({ text: b.text, blankAfter: b.blank_after })));
|
|
2277
2329
|
const count = countOccurrences(body, opts.old);
|
|
2278
2330
|
if (count === 0)
|
|
2279
2331
|
throw new Error(`anchor not found: no match for --old`);
|
|
@@ -2300,25 +2352,25 @@ function appendDocument(db, id, body) {
|
|
|
2300
2352
|
if (!getDocument(db, id))
|
|
2301
2353
|
throw new Error(`no such document: ${id}`);
|
|
2302
2354
|
ensureBlocks(db, id);
|
|
2303
|
-
const
|
|
2304
|
-
if (
|
|
2355
|
+
const next = parseDocBlocks(body);
|
|
2356
|
+
if (next.length) {
|
|
2305
2357
|
const blocks = liveBlocks(db, id);
|
|
2306
2358
|
const last = blocks.length ? blocks[blocks.length - 1].order_key : null;
|
|
2307
|
-
insertBlocks(db, id,
|
|
2359
|
+
insertBlocks(db, id, next, last, null);
|
|
2308
2360
|
}
|
|
2309
|
-
return { id, changed:
|
|
2361
|
+
return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
|
|
2310
2362
|
}
|
|
2311
2363
|
function prependDocument(db, id, body) {
|
|
2312
2364
|
if (!getDocument(db, id))
|
|
2313
2365
|
throw new Error(`no such document: ${id}`);
|
|
2314
2366
|
ensureBlocks(db, id);
|
|
2315
|
-
const
|
|
2316
|
-
if (
|
|
2367
|
+
const next = parseDocBlocks(body);
|
|
2368
|
+
if (next.length) {
|
|
2317
2369
|
const blocks = liveBlocks(db, id);
|
|
2318
2370
|
const first = blocks.length ? blocks[0].order_key : null;
|
|
2319
|
-
insertBlocks(db, id,
|
|
2371
|
+
insertBlocks(db, id, next, null, first);
|
|
2320
2372
|
}
|
|
2321
|
-
return { id, changed:
|
|
2373
|
+
return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
|
|
2322
2374
|
}
|
|
2323
2375
|
function deleteDocument(db, id) {
|
|
2324
2376
|
if (!getDocument(db, id))
|
|
@@ -13981,7 +14033,7 @@ function finalize(ctx, schema) {
|
|
|
13981
14033
|
result.$schema = "http://json-schema.org/draft-07/schema#";
|
|
13982
14034
|
} else if (ctx.target === "draft-04") {
|
|
13983
14035
|
result.$schema = "http://json-schema.org/draft-04/schema#";
|
|
13984
|
-
} else if (ctx.target === "openapi-3.0") {}
|
|
14036
|
+
} else if (ctx.target === "openapi-3.0") {}
|
|
13985
14037
|
if (ctx.external?.uri) {
|
|
13986
14038
|
const id = ctx.external.registry.get(schema)?.id;
|
|
13987
14039
|
if (!id)
|
|
@@ -14225,7 +14277,7 @@ var literalProcessor = (schema, ctx, json, _params) => {
|
|
|
14225
14277
|
if (val === undefined) {
|
|
14226
14278
|
if (ctx.unrepresentable === "throw") {
|
|
14227
14279
|
throw new Error("Literal `undefined` cannot be represented in JSON Schema");
|
|
14228
|
-
}
|
|
14280
|
+
}
|
|
14229
14281
|
} else if (typeof val === "bigint") {
|
|
14230
14282
|
if (ctx.unrepresentable === "throw") {
|
|
14231
14283
|
throw new Error("BigInt literals cannot be represented in JSON Schema");
|
|
@@ -16964,8 +17016,9 @@ function getServerConfig(db) {
|
|
|
16964
17016
|
// package.json
|
|
16965
17017
|
var package_default = {
|
|
16966
17018
|
name: "@tensorix/metahub",
|
|
16967
|
-
version: "0.1.
|
|
17019
|
+
version: "0.1.7",
|
|
16968
17020
|
description: "Metahub core library and CLI",
|
|
17021
|
+
license: "AGPL-3.0-only",
|
|
16969
17022
|
type: "module",
|
|
16970
17023
|
main: "./dist/index.js",
|
|
16971
17024
|
module: "./dist/index.js",
|
|
@@ -16983,7 +17036,9 @@ var package_default = {
|
|
|
16983
17036
|
},
|
|
16984
17037
|
files: [
|
|
16985
17038
|
"dist",
|
|
16986
|
-
"README.md"
|
|
17039
|
+
"README.md",
|
|
17040
|
+
"README.zh-CN.md",
|
|
17041
|
+
"LICENSE"
|
|
16987
17042
|
],
|
|
16988
17043
|
scripts: {
|
|
16989
17044
|
dev: "bun --hot src/cli/index.ts",
|
|
@@ -17927,7 +17982,7 @@ function startServer(opts = {}) {
|
|
|
17927
17982
|
let timer = null;
|
|
17928
17983
|
if (autoSync && syncIntervalMs > 0) {
|
|
17929
17984
|
timer = setInterval(() => {
|
|
17930
|
-
syncAllPeers(db).catch(() =>
|
|
17985
|
+
syncAllPeers(db).catch((e) => console.error("[sync] auto-sync tick failed \u2014", e));
|
|
17931
17986
|
}, syncIntervalMs);
|
|
17932
17987
|
timer.unref?.();
|
|
17933
17988
|
}
|
|
@@ -17960,7 +18015,7 @@ export {
|
|
|
17960
18015
|
slugify,
|
|
17961
18016
|
setPropertyWidth,
|
|
17962
18017
|
setCurrentDatabase,
|
|
17963
|
-
|
|
18018
|
+
serializeDocBlocks,
|
|
17964
18019
|
search,
|
|
17965
18020
|
runSchema,
|
|
17966
18021
|
resolveSite,
|
|
@@ -17975,7 +18030,7 @@ export {
|
|
|
17975
18030
|
putBlob,
|
|
17976
18031
|
prependDocument,
|
|
17977
18032
|
parseHlc,
|
|
17978
|
-
|
|
18033
|
+
parseDocBlocks,
|
|
17979
18034
|
openMetahub,
|
|
17980
18035
|
observeHlc,
|
|
17981
18036
|
nextHlc,
|
|
@@ -17983,6 +18038,7 @@ export {
|
|
|
17983
18038
|
moveRecord,
|
|
17984
18039
|
migrateRecords,
|
|
17985
18040
|
migratePeers,
|
|
18041
|
+
migrateDocBlocks,
|
|
17986
18042
|
metahubHome,
|
|
17987
18043
|
maybeAutoIndex,
|
|
17988
18044
|
makeId,
|
|
@@ -18049,4 +18105,4 @@ export {
|
|
|
18049
18105
|
ChangeSchema
|
|
18050
18106
|
};
|
|
18051
18107
|
|
|
18052
|
-
//# debugId=
|
|
18108
|
+
//# debugId=548591A808956A3F64756E2164756E21
|