sensemaking 0.14.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/cjs/commands/peek.js +3 -3
  2. package/dist/cjs/commands/peek.js.map +1 -1
  3. package/dist/cjs/commands/related.js +2 -2
  4. package/dist/cjs/commands/related.js.map +1 -1
  5. package/dist/cjs/db/open.d.cts +1 -1
  6. package/dist/cjs/db/open.d.ts +1 -1
  7. package/dist/cjs/db/open.js +1 -1
  8. package/dist/cjs/db/open.js.map +1 -1
  9. package/dist/cjs/features/embed.js +4 -6
  10. package/dist/cjs/features/embed.js.map +1 -1
  11. package/dist/cjs/features/links.js +100 -16
  12. package/dist/cjs/features/links.js.map +1 -1
  13. package/dist/cjs/features/sections.js +4 -6
  14. package/dist/cjs/features/sections.js.map +1 -1
  15. package/dist/cjs/features/tags.js +129 -11
  16. package/dist/cjs/features/tags.js.map +1 -1
  17. package/dist/cjs/fences.d.cts +12 -0
  18. package/dist/cjs/fences.d.ts +12 -0
  19. package/dist/cjs/fences.js +157 -0
  20. package/dist/cjs/fences.js.map +1 -0
  21. package/dist/esm/commands/peek.js +3 -3
  22. package/dist/esm/commands/peek.js.map +1 -1
  23. package/dist/esm/commands/related.js +2 -2
  24. package/dist/esm/commands/related.js.map +1 -1
  25. package/dist/esm/db/open.d.ts +1 -1
  26. package/dist/esm/db/open.js +1 -1
  27. package/dist/esm/db/open.js.map +1 -1
  28. package/dist/esm/features/embed.js +4 -6
  29. package/dist/esm/features/embed.js.map +1 -1
  30. package/dist/esm/features/links.js +63 -15
  31. package/dist/esm/features/links.js.map +1 -1
  32. package/dist/esm/features/sections.js +4 -6
  33. package/dist/esm/features/sections.js.map +1 -1
  34. package/dist/esm/features/tags.js +127 -9
  35. package/dist/esm/features/tags.js.map +1 -1
  36. package/dist/esm/fences.d.ts +12 -0
  37. package/dist/esm/fences.js +138 -0
  38. package/dist/esm/fences.js.map +1 -0
  39. package/package.json +1 -1
  40. package/skills/sense/SKILL.md +1 -1
@@ -17,6 +17,7 @@ _export(exports, {
17
17
  }
18
18
  });
19
19
  var _posix = /*#__PURE__*/ _interop_require_default(require("node:path/posix"));
20
+ var _fencests = require("../fences.js");
20
21
  function _array_like_to_array(arr, len) {
21
22
  if (len == null || len > arr.length) len = arr.length;
22
23
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
@@ -51,8 +52,11 @@ function _unsupported_iterable_to_array(o, minLen) {
51
52
  // (indexed, for the incremental resolve below), dst the resolved path or NULL (a
52
53
  // queryable dead link), embed whether it's `![[x]]`/`![](x.md)` rather than `[[x]]`/`[](x.md)`
53
54
  // -- Obsidian's grain: a target both linked and embedded in the same note is two rows.
54
- // Wikilinks ([[target]], [[target#anchor|alias]], embeds) plus relative markdown links to .md files.
55
- function extract(_raw, body) {
55
+ // Wikilinks ([[target]], [[target#anchor|alias]], embeds), internal markdown links, and
56
+ // frontmatter values that are exactly a wikilink ("[[X]]"; mid-string and ![[...]] forms are
57
+ // not links there -- Obsidian's rule, probe-verified).
58
+ function extract(_raw, rawBody, _search, data) {
59
+ var body = /\[\[|\]\(/.test(rawBody) ? (0, _fencests.maskRegions)(rawBody) : rawBody;
56
60
  var seen = new Set();
57
61
  var results = [];
58
62
  var add = function add(target, embed) {
@@ -71,8 +75,16 @@ function extract(_raw, body) {
71
75
  for(var _iterator = body.matchAll(/\[\[(.*?)\]\]/g)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
72
76
  var m = _step.value;
73
77
  var _m_index;
74
- var target = m[1].split('|')[0].split('#')[0].trim();
75
- if (target) add(target, body[((_m_index = m.index) !== null && _m_index !== void 0 ? _m_index : 0) - 1] === '!');
78
+ var embed = body[((_m_index = m.index) !== null && _m_index !== void 0 ? _m_index : 0) - 1] === '!';
79
+ // [[#Heading]]: a same-note anchor link, which Obsidian resolves to a self-edge. Keep the
80
+ // target as written (leading #) so resolveTarget can special-case it.
81
+ var beforeAlias = m[1].split('|')[0].trim();
82
+ if (beforeAlias.startsWith('#')) {
83
+ if (beforeAlias.length > 1) add(beforeAlias, embed);
84
+ continue;
85
+ }
86
+ var target = beforeAlias.split('#')[0].trim();
87
+ if (target) add(target, embed);
76
88
  }
77
89
  } catch (err) {
78
90
  _didIteratorError = true;
@@ -90,10 +102,19 @@ function extract(_raw, body) {
90
102
  }
91
103
  var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
92
104
  try {
93
- for(var _iterator1 = body.matchAll(/(!)?\[[^\]]*\]\(([^)]+\.md)(?:#[^)]*)?\)/g)[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
105
+ // Any internal destination, not only .md-suffixed: Obsidian gives markdown links full
106
+ // linkpath resolution, so \](Zektor) resolves like [[Zektor]] and \](#anchor) is a
107
+ // self-edge. External URLs (a scheme anywhere survives the malformed double-paren case)
108
+ // and titled links (dest stops at whitespace) are skipped.
109
+ // A space in the destination is only valid ahead of a quoted title; a domain-shaped first
110
+ // segment (www.example.com/...) is external even without a scheme.
111
+ for(var _iterator1 = body.matchAll(/(!)?\[[^\]]*\]\(((?:[^()\s]|\([^()\s]*\))+)(?:\s+(?:"[^)]*"|'[^)]*'))?\)/g)[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
94
112
  var m1 = _step1.value;
95
- var target1 = m1[2].trim();
96
- if (target1 && !/^[a-z]+:\/\//i.test(target1)) add(target1, m1[1] === '!');
113
+ var dest = m1[2].trim();
114
+ if (!dest || dest.includes('://')) continue;
115
+ if (/^www\./i.test(dest)) continue;
116
+ var target1 = dest.startsWith('#') ? dest : dest.split('#')[0];
117
+ if (target1) add(target1, m1[1] === '!');
97
118
  }
98
119
  } catch (err) {
99
120
  _didIteratorError1 = true;
@@ -109,6 +130,53 @@ function extract(_raw, body) {
109
130
  }
110
131
  }
111
132
  }
133
+ var _iteratorNormalCompletion2 = true, _didIteratorError2 = false, _iteratorError2 = undefined;
134
+ try {
135
+ for(var _iterator2 = Object.values(data !== null && data !== void 0 ? data : {})[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true){
136
+ var value = _step2.value;
137
+ var _iteratorNormalCompletion3 = true, _didIteratorError3 = false, _iteratorError3 = undefined;
138
+ try {
139
+ for(var _iterator3 = (Array.isArray(value) ? value : [
140
+ value
141
+ ])[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true){
142
+ var item = _step3.value;
143
+ if (typeof item !== 'string') continue;
144
+ var m2 = /^\[\[(.*)\]\]$/.exec(item.trim());
145
+ if (!m2) continue;
146
+ var inner = m2[1].split('|')[0].trim();
147
+ var target2 = inner.split('#')[0].trim();
148
+ if (target2) add(target2, false);
149
+ else if (inner.startsWith('#') && inner.length > 1) add(inner, false);
150
+ }
151
+ } catch (err) {
152
+ _didIteratorError3 = true;
153
+ _iteratorError3 = err;
154
+ } finally{
155
+ try {
156
+ if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
157
+ _iterator3.return();
158
+ }
159
+ } finally{
160
+ if (_didIteratorError3) {
161
+ throw _iteratorError3;
162
+ }
163
+ }
164
+ }
165
+ }
166
+ } catch (err) {
167
+ _didIteratorError2 = true;
168
+ _iteratorError2 = err;
169
+ } finally{
170
+ try {
171
+ if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
172
+ _iterator2.return();
173
+ }
174
+ } finally{
175
+ if (_didIteratorError2) {
176
+ throw _iteratorError2;
177
+ }
178
+ }
179
+ }
112
180
  return results;
113
181
  }
114
182
  function cleanTarget(target) {
@@ -118,10 +186,11 @@ function baseKey(path) {
118
186
  return _posix.default.basename(path).replace(/\.md$/i, '').toLowerCase();
119
187
  }
120
188
  // Obsidian-style: exact relative path (with/without .md), path relative to the linking
121
- // note's directory, then basename match (lexicographically first on ties).
189
+ // note's directory, then basename match (shortest path wins on ties -- verified against a
190
+ // cold-loaded Obsidian vault on 6+ real collision pairs; byBase's lists are pre-sorted that way).
122
191
  function resolveTarget(src, target, pathSet, byBase) {
123
- var _ref;
124
- var _byBase_get;
192
+ // [[#Heading]]: a same-note anchor, always the note itself -- never depends on other files.
193
+ if (target.startsWith('#')) return src;
125
194
  var clean = cleanTarget(target);
126
195
  var fromSrc = _posix.default.normalize(_posix.default.join(_posix.default.dirname(src), clean));
127
196
  for(var _i = 0, _iter = [
@@ -133,15 +202,24 @@ function resolveTarget(src, target, pathSet, byBase) {
133
202
  var candidate = _iter[_i];
134
203
  if (pathSet.has(candidate)) return candidate;
135
204
  }
136
- return (_ref = (_byBase_get = byBase.get(baseKey(clean))) === null || _byBase_get === void 0 ? void 0 : _byBase_get[0]) !== null && _ref !== void 0 ? _ref : null;
205
+ var candidates = byBase.get(baseKey(clean));
206
+ if (!candidates) return null;
207
+ // The linking note itself wins a basename collision (Obsidian resolves to self before the
208
+ // shortest-path rule -- cold-load verified on the hub corpus).
209
+ return candidates.includes(src) ? src : candidates[0];
137
210
  }
211
+ // Shortest path wins a basename collision; equal lengths fall back to lexicographic, our own
212
+ // deterministic tiebreak for a case Obsidian itself leaves registration-order-dependent.
138
213
  function buildByBase(files) {
139
214
  var byBase = new Map();
215
+ var paths = files.map(function(f) {
216
+ return f.relPath;
217
+ }).sort(function(a, b) {
218
+ return a.length - b.length || (a < b ? -1 : a > b ? 1 : 0);
219
+ });
140
220
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
141
221
  try {
142
- for(var _iterator = files.map(function(f) {
143
- return f.relPath;
144
- }).sort()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
222
+ for(var _iterator = paths[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
145
223
  var path = _step.value;
146
224
  var key = baseKey(path);
147
225
  var list = byBase.get(key);
@@ -304,7 +382,10 @@ function linkEdges(db) {
304
382
  // embed whose exact (src, target) also exists as a link: that second row is new with the
305
383
  // embed grain and would double an edge that used to be one row. The NOT EXISTS probes the
306
384
  // primary key and fires only for embed rows; both branches still scan the table.
307
- var sql = "SELECT src, dst FROM links WHERE dst IS NOT NULL AND embed = 0\n UNION ALL\n SELECT src, dst FROM links l WHERE dst IS NOT NULL AND embed = 1\n AND NOT EXISTS (SELECT 1 FROM links l0 WHERE l0.src = l.src AND l0.target = l.target AND l0.embed = 0)";
385
+ // Self-edges (same-note anchors) are excluded here so PageRank mass is not self-recycled --
386
+ // Obsidian's own graph view hides self-loops too. The rows themselves stay in the table for
387
+ // backlinks/peek.
388
+ var sql = "SELECT src, dst FROM links WHERE dst IS NOT NULL AND embed = 0 AND src != dst\n UNION ALL\n SELECT src, dst FROM links l WHERE dst IS NOT NULL AND embed = 1 AND src != dst\n AND NOT EXISTS (SELECT 1 FROM links l0 WHERE l0.src = l.src AND l0.target = l.target AND l0.embed = 0)";
308
389
  return db.prepare(sql).all().map(function(r) {
309
390
  return [
310
391
  r.src,
@@ -374,9 +455,12 @@ var links = {
374
455
  var insert = db.prepare('INSERT INTO links (src, target, target_base, dst, embed) VALUES (?, ?, ?, NULL, ?) ON CONFLICT(src, target, embed) DO UPDATE SET target_base = excluded.target_base');
375
456
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
376
457
  try {
458
+ // A same-note anchor's dst is always its own src, never another file's basename, so it
459
+ // gets no target_base -- SQL's `IN` never matches NULL, keeping it out of
460
+ // resolveIncremental's cross-file collectIn('target_base', ...) entirely.
377
461
  for(var _iterator = targets[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
378
462
  var _step_value = _step.value, target = _step_value.target, embed = _step_value.embed;
379
- insert.run(path, target, baseKey(cleanTarget(target)), embed ? 1 : 0);
463
+ insert.run(path, target, target.startsWith('#') ? null : baseKey(cleanTarget(target)), embed ? 1 : 0);
380
464
  }
381
465
  } catch (err) {
382
466
  _didIteratorError = true;
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/links.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { FileStat } from '../scan.ts';\nimport type { Feature, ReconcileDelta } from './types.ts';\n\n// links(src, target, target_base, dst, embed): target as written, target_base its baseKey\n// (indexed, for the incremental resolve below), dst the resolved path or NULL (a\n// queryable dead link), embed whether it's `![[x]]`/`![](x.md)` rather than `[[x]]`/`[](x.md)`\n// -- Obsidian's grain: a target both linked and embedded in the same note is two rows.\n\n// Wikilinks ([[target]], [[target#anchor|alias]], embeds) plus relative markdown links to .md files.\nfunction extract(_raw: string, body: string): Array<{ target: string; embed: boolean }> {\n const seen = new Set<string>();\n const results: Array<{ target: string; embed: boolean }> = [];\n const add = (target: string, embed: boolean) => {\n const key = `${target}\\0${embed ? '1' : '0'}`;\n if (seen.has(key)) return;\n seen.add(key);\n results.push({ target, embed });\n };\n // To the first ]], as Obsidian parses it, so a heading or alias holding a lone ] still\n // matches; anchor and alias split off in code, since a class-based regex stops at the ].\n for (const m of body.matchAll(/\\[\\[(.*?)\\]\\]/g)) {\n const target = m[1].split('|')[0].split('#')[0].trim();\n if (target) add(target, body[(m.index ?? 0) - 1] === '!');\n }\n for (const m of body.matchAll(/(!)?\\[[^\\]]*\\]\\(([^)]+\\.md)(?:#[^)]*)?\\)/g)) {\n const target = m[2].trim();\n if (target && !/^[a-z]+:\\/\\//i.test(target)) add(target, m[1] === '!');\n }\n return results;\n}\n\nfunction cleanTarget(target: string): string {\n return target.replace(/\\\\/g, '/').replace(/^\\.\\//, '');\n}\n\nfunction baseKey(path: string): string {\n return posix.basename(path).replace(/\\.md$/i, '').toLowerCase();\n}\n\n// Obsidian-style: exact relative path (with/without .md), path relative to the linking\n// note's directory, then basename match (lexicographically first on ties).\nfunction resolveTarget(src: string, target: string, pathSet: Set<string>, byBase: Map<string, string[]>): string | null {\n const clean = cleanTarget(target);\n const fromSrc = posix.normalize(posix.join(posix.dirname(src), clean));\n for (const candidate of [clean, `${clean}.md`, fromSrc, `${fromSrc}.md`]) {\n if (pathSet.has(candidate)) return candidate;\n }\n return byBase.get(baseKey(clean))?.[0] ?? null;\n}\n\nfunction buildByBase(files: FileStat[]): Map<string, string[]> {\n const byBase = new Map<string, string[]>();\n for (const path of files.map((f) => f.relPath).sort()) {\n const key = baseKey(path);\n const list = byBase.get(key);\n if (list) list.push(path);\n else byBase.set(key, [path]);\n }\n return byBase;\n}\n\ninterface LinkRow {\n src: string;\n target: string;\n dst: string | null;\n embed: number;\n}\n\n// Applies resolveTarget to `rows`, writing only rows whose dst actually changes. The UPDATE\n// keys on (src, target): a link/embed sibling pair shares one resolution, so either row's\n// mismatch rewrites both.\nfunction resolveRows(db: DatabaseSync, rows: LinkRow[], pathSet: Set<string>, byBase: Map<string, string[]>): boolean {\n const update = db.prepare('UPDATE links SET dst = ? WHERE src = ? AND target = ?');\n let changed = false;\n for (const row of rows) {\n const dst = resolveTarget(row.src, row.target, pathSet, byBase);\n if (dst !== row.dst) {\n update.run(dst, row.src, row.target);\n changed = true;\n }\n }\n return changed;\n}\n\n// A new or deleted file can change any note's resolution, so re-resolve the whole table.\n// Fallback for cold builds and large deltas -- see afterReconcile's threshold.\nfunction resolveAll(db: DatabaseSync, files: FileStat[]): boolean {\n const pathSet = new Set(files.map((f) => f.relPath));\n const byBase = buildByBase(files);\n const rows = db.prepare('SELECT src, target, dst, embed FROM links').all() as unknown as LinkRow[];\n return resolveRows(db, rows, pathSet, byBase);\n}\n\n// Re-resolve only what this reconcile could have touched: re-stored sources, links whose\n// target basename matches an added/vanished file, and links whose dst just vanished. All\n// three are indexed lookups, so this never reads the whole table.\nfunction resolveIncremental(db: DatabaseSync, delta: ReconcileDelta): boolean {\n const pathSet = new Set(delta.files.map((f) => f.relPath));\n const byBase = buildByBase(delta.files);\n\n const changedBasenames = new Set<string>();\n for (const p of delta.added) changedBasenames.add(baseKey(p));\n for (const p of delta.vanished) changedBasenames.add(baseKey(p));\n\n // \\0 can't appear in a path or target, so the key never collides; a space can.\n const candidates = new Map<string, LinkRow>();\n const collect = (rows: LinkRow[]) => {\n for (const row of rows) candidates.set(`${row.src}\\0${row.target}\\0${row.embed}`, row);\n };\n // Chunked so a large-but-under-threshold delta never exceeds SQLite's bound-variable\n // limit (SQLITE_MAX_VARIABLE_NUMBER, 32766).\n const collectIn = (column: string, keys: string[]) => {\n for (let i = 0; i < keys.length; i += 500) {\n const chunk = keys.slice(i, i + 500);\n const placeholders = chunk.map(() => '?').join(', ');\n collect(db.prepare(`SELECT src, target, dst, embed FROM links WHERE ${column} IN (${placeholders})`).all(...chunk) as unknown as LinkRow[]);\n }\n };\n\n collectIn('src', delta.reparsed);\n collectIn('target_base', [...changedBasenames]);\n collectIn('dst', delta.vanished);\n\n return resolveRows(db, [...candidates.values()], pathSet, byBase);\n}\n\n// Resolved edges, for rank and for search's graph expansion.\nexport function linkEdges(db: DatabaseSync): [string, string][] {\n // One edge per row, as 0.12's grain always had it -- two written targets resolving to one\n // dst stay two edges (a weight the fusion evals were gated on). The only exclusion is an\n // embed whose exact (src, target) also exists as a link: that second row is new with the\n // embed grain and would double an edge that used to be one row. The NOT EXISTS probes the\n // primary key and fires only for embed rows; both branches still scan the table.\n const sql = `SELECT src, dst FROM links WHERE dst IS NOT NULL AND embed = 0\n UNION ALL\n SELECT src, dst FROM links l WHERE dst IS NOT NULL AND embed = 1\n AND NOT EXISTS (SELECT 1 FROM links l0 WHERE l0.src = l.src AND l0.target = l.target AND l0.embed = 0)`;\n return (db.prepare(sql).all() as Array<{ src: string; dst: string }>).map((r) => [r.src, r.dst]);\n}\n\n// remove() has already deleted the rows by the time afterReconcile runs, so it records here\n// whether they carried edges. Keyed on the delta object, so the state dies with the reconcile.\nconst removedWithLinks = new WeakMap<ReconcileDelta, Set<string>>();\n\nfunction recordRemoved(delta: ReconcileDelta, path: string): void {\n let set = removedWithLinks.get(delta);\n if (!set) {\n set = new Set();\n removedWithLinks.set(delta, set);\n }\n set.add(path);\n}\n\n// remove() runs per path; the vanished list is an array, so cache the Set per delta.\nconst vanishedSets = new WeakMap<ReconcileDelta, Set<string>>();\nfunction vanishedSet(delta: ReconcileDelta): Set<string> {\n let set = vanishedSets.get(delta);\n if (!set) {\n set = new Set(delta.vanished);\n vanishedSets.set(delta, set);\n }\n return set;\n}\n\nexport const links: Feature = {\n name: 'links',\n schema(db) {\n db.exec('CREATE TABLE IF NOT EXISTS links (src TEXT, target TEXT, target_base TEXT, dst TEXT, embed INTEGER, PRIMARY KEY (src, target, embed))');\n db.exec('CREATE INDEX IF NOT EXISTS links_dst ON links(dst)');\n db.exec('CREATE INDEX IF NOT EXISTS links_target_base ON links(target_base)');\n },\n extract,\n // Reparsed docs are diffed in store() below instead of wiped here: deleting and\n // re-inserting resets dst to NULL, which made every touch-only reparse read as an edge\n // change and recompute PageRank -- measured as most of the remaining update cost.\n remove(db, path, delta) {\n if (!vanishedSet(delta).has(path)) return;\n const result = db.prepare('DELETE FROM links WHERE src = ?').run(path);\n if (Number(result.changes) > 0) recordRemoved(delta, path);\n },\n store(db, path, extracted, delta) {\n const targets = extracted as Array<{ target: string; embed: boolean }>;\n // Stale rows (target/embed pairs the new parse no longer contains) are real edge removals.\n // \\0 can't appear in a target, so the composite key never collides.\n let stale: ReturnType<ReturnType<DatabaseSync['prepare']>['run']>;\n if (targets.length === 0) {\n stale = db.prepare('DELETE FROM links WHERE src = ?').run(path);\n } else {\n const placeholders = targets.map(() => '?').join(', ');\n const keys = targets.map((t) => `${t.target}\\0${t.embed ? '1' : '0'}`);\n stale = db.prepare(`DELETE FROM links WHERE src = ? AND (target || char(0) || embed) NOT IN (${placeholders})`).run(path, ...keys);\n }\n if (Number(stale.changes) > 0) recordRemoved(delta, path);\n // Upsert preserves dst on surviving rows, so an unchanged link resolves to the same\n // value and reports no change; only genuinely new rows start at NULL.\n const insert = db.prepare('INSERT INTO links (src, target, target_base, dst, embed) VALUES (?, ?, ?, NULL, ?) ON CONFLICT(src, target, embed) DO UPDATE SET target_base = excluded.target_base');\n for (const { target, embed } of targets) insert.run(path, target, baseKey(cleanTarget(target)), embed ? 1 : 0);\n },\n afterReconcile(db, delta) {\n // Any deleted rows that carried edges mean the edge set shrank -- whether the file\n // vanished or was reparsed down to fewer/no links. dstChanged alone misses the\n // reparsed-to-zero-links case (no surviving rows to re-resolve).\n const removeHadLinks = (removedWithLinks.get(delta)?.size ?? 0) > 0;\n\n const churn = delta.reparsed.length + delta.vanished.length;\n // Past this share of the tree, a full pass is the only one guaranteed to match\n // resolveTarget's ambiguity rules. A cold build clears the threshold on its own.\n const large = delta.files.length === 0 || churn > 0.2 * delta.files.length;\n\n const dstChanged = large ? resolveAll(db, delta.files) : resolveIncremental(db, delta);\n delta.linksChanged = dstChanged || removeHadLinks;\n },\n};\n"],"names":["linkEdges","links","extract","_raw","body","seen","Set","results","add","target","embed","key","has","push","matchAll","m","split","trim","index","test","cleanTarget","replace","baseKey","path","posix","basename","toLowerCase","resolveTarget","src","pathSet","byBase","clean","fromSrc","normalize","join","dirname","candidate","get","buildByBase","files","Map","map","f","relPath","sort","list","set","resolveRows","db","rows","update","prepare","changed","row","dst","run","resolveAll","all","resolveIncremental","delta","changedBasenames","added","p","vanished","candidates","collect","collectIn","column","keys","i","length","chunk","slice","placeholders","reparsed","values","sql","r","removedWithLinks","WeakMap","recordRemoved","vanishedSets","vanishedSet","name","schema","exec","remove","result","Number","changes","store","extracted","targets","stale","t","insert","afterReconcile","removeHadLinks","size","churn","large","dstChanged","linksChanged"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/links.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport { maskRegions } from '../fences.ts';\nimport type { FileStat } from '../scan.ts';\nimport type { Feature, ReconcileDelta } from './types.ts';\n\n// links(src, target, target_base, dst, embed): target as written, target_base its baseKey\n// (indexed, for the incremental resolve below), dst the resolved path or NULL (a\n// queryable dead link), embed whether it's `![[x]]`/`![](x.md)` rather than `[[x]]`/`[](x.md)`\n// -- Obsidian's grain: a target both linked and embedded in the same note is two rows.\n\n// Wikilinks ([[target]], [[target#anchor|alias]], embeds), internal markdown links, and\n// frontmatter values that are exactly a wikilink (\"[[X]]\"; mid-string and ![[...]] forms are\n// not links there -- Obsidian's rule, probe-verified).\nfunction extract(_raw: string, rawBody: string, _search?: { title: string; summary: string }, data?: Record<string, unknown>): Array<{ target: string; embed: boolean }> {\n const body = /\\[\\[|\\]\\(/.test(rawBody) ? maskRegions(rawBody) : rawBody;\n const seen = new Set<string>();\n const results: Array<{ target: string; embed: boolean }> = [];\n const add = (target: string, embed: boolean) => {\n const key = `${target}\\0${embed ? '1' : '0'}`;\n if (seen.has(key)) return;\n seen.add(key);\n results.push({ target, embed });\n };\n // To the first ]], as Obsidian parses it, so a heading or alias holding a lone ] still\n // matches; anchor and alias split off in code, since a class-based regex stops at the ].\n for (const m of body.matchAll(/\\[\\[(.*?)\\]\\]/g)) {\n const embed = body[(m.index ?? 0) - 1] === '!';\n // [[#Heading]]: a same-note anchor link, which Obsidian resolves to a self-edge. Keep the\n // target as written (leading #) so resolveTarget can special-case it.\n const beforeAlias = m[1].split('|')[0].trim();\n if (beforeAlias.startsWith('#')) {\n if (beforeAlias.length > 1) add(beforeAlias, embed);\n continue;\n }\n const target = beforeAlias.split('#')[0].trim();\n if (target) add(target, embed);\n }\n // Any internal destination, not only .md-suffixed: Obsidian gives markdown links full\n // linkpath resolution, so \\](Zektor) resolves like [[Zektor]] and \\](#anchor) is a\n // self-edge. External URLs (a scheme anywhere survives the malformed double-paren case)\n // and titled links (dest stops at whitespace) are skipped.\n // A space in the destination is only valid ahead of a quoted title; a domain-shaped first\n // segment (www.example.com/...) is external even without a scheme.\n for (const m of body.matchAll(/(!)?\\[[^\\]]*\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+(?:\"[^)]*\"|'[^)]*'))?\\)/g)) {\n const dest = m[2].trim();\n if (!dest || dest.includes('://')) continue;\n if (/^www\\./i.test(dest)) continue;\n const target = dest.startsWith('#') ? dest : dest.split('#')[0];\n if (target) add(target, m[1] === '!');\n }\n for (const value of Object.values(data ?? {})) {\n for (const item of Array.isArray(value) ? value : [value]) {\n if (typeof item !== 'string') continue;\n const m = /^\\[\\[(.*)\\]\\]$/.exec(item.trim());\n if (!m) continue;\n const inner = m[1].split('|')[0].trim();\n const target = inner.split('#')[0].trim();\n if (target) add(target, false);\n else if (inner.startsWith('#') && inner.length > 1) add(inner, false);\n }\n }\n return results;\n}\n\nfunction cleanTarget(target: string): string {\n return target.replace(/\\\\/g, '/').replace(/^\\.\\//, '');\n}\n\nfunction baseKey(path: string): string {\n return posix.basename(path).replace(/\\.md$/i, '').toLowerCase();\n}\n\n// Obsidian-style: exact relative path (with/without .md), path relative to the linking\n// note's directory, then basename match (shortest path wins on ties -- verified against a\n// cold-loaded Obsidian vault on 6+ real collision pairs; byBase's lists are pre-sorted that way).\nfunction resolveTarget(src: string, target: string, pathSet: Set<string>, byBase: Map<string, string[]>): string | null {\n // [[#Heading]]: a same-note anchor, always the note itself -- never depends on other files.\n if (target.startsWith('#')) return src;\n const clean = cleanTarget(target);\n const fromSrc = posix.normalize(posix.join(posix.dirname(src), clean));\n for (const candidate of [clean, `${clean}.md`, fromSrc, `${fromSrc}.md`]) {\n if (pathSet.has(candidate)) return candidate;\n }\n const candidates = byBase.get(baseKey(clean));\n if (!candidates) return null;\n // The linking note itself wins a basename collision (Obsidian resolves to self before the\n // shortest-path rule -- cold-load verified on the hub corpus).\n return candidates.includes(src) ? src : candidates[0];\n}\n\n// Shortest path wins a basename collision; equal lengths fall back to lexicographic, our own\n// deterministic tiebreak for a case Obsidian itself leaves registration-order-dependent.\nfunction buildByBase(files: FileStat[]): Map<string, string[]> {\n const byBase = new Map<string, string[]>();\n const paths = files.map((f) => f.relPath).sort((a, b) => a.length - b.length || (a < b ? -1 : a > b ? 1 : 0));\n for (const path of paths) {\n const key = baseKey(path);\n const list = byBase.get(key);\n if (list) list.push(path);\n else byBase.set(key, [path]);\n }\n return byBase;\n}\n\ninterface LinkRow {\n src: string;\n target: string;\n dst: string | null;\n embed: number;\n}\n\n// Applies resolveTarget to `rows`, writing only rows whose dst actually changes. The UPDATE\n// keys on (src, target): a link/embed sibling pair shares one resolution, so either row's\n// mismatch rewrites both.\nfunction resolveRows(db: DatabaseSync, rows: LinkRow[], pathSet: Set<string>, byBase: Map<string, string[]>): boolean {\n const update = db.prepare('UPDATE links SET dst = ? WHERE src = ? AND target = ?');\n let changed = false;\n for (const row of rows) {\n const dst = resolveTarget(row.src, row.target, pathSet, byBase);\n if (dst !== row.dst) {\n update.run(dst, row.src, row.target);\n changed = true;\n }\n }\n return changed;\n}\n\n// A new or deleted file can change any note's resolution, so re-resolve the whole table.\n// Fallback for cold builds and large deltas -- see afterReconcile's threshold.\nfunction resolveAll(db: DatabaseSync, files: FileStat[]): boolean {\n const pathSet = new Set(files.map((f) => f.relPath));\n const byBase = buildByBase(files);\n const rows = db.prepare('SELECT src, target, dst, embed FROM links').all() as unknown as LinkRow[];\n return resolveRows(db, rows, pathSet, byBase);\n}\n\n// Re-resolve only what this reconcile could have touched: re-stored sources, links whose\n// target basename matches an added/vanished file, and links whose dst just vanished. All\n// three are indexed lookups, so this never reads the whole table.\nfunction resolveIncremental(db: DatabaseSync, delta: ReconcileDelta): boolean {\n const pathSet = new Set(delta.files.map((f) => f.relPath));\n const byBase = buildByBase(delta.files);\n\n const changedBasenames = new Set<string>();\n for (const p of delta.added) changedBasenames.add(baseKey(p));\n for (const p of delta.vanished) changedBasenames.add(baseKey(p));\n\n // \\0 can't appear in a path or target, so the key never collides; a space can.\n const candidates = new Map<string, LinkRow>();\n const collect = (rows: LinkRow[]) => {\n for (const row of rows) candidates.set(`${row.src}\\0${row.target}\\0${row.embed}`, row);\n };\n // Chunked so a large-but-under-threshold delta never exceeds SQLite's bound-variable\n // limit (SQLITE_MAX_VARIABLE_NUMBER, 32766).\n const collectIn = (column: string, keys: string[]) => {\n for (let i = 0; i < keys.length; i += 500) {\n const chunk = keys.slice(i, i + 500);\n const placeholders = chunk.map(() => '?').join(', ');\n collect(db.prepare(`SELECT src, target, dst, embed FROM links WHERE ${column} IN (${placeholders})`).all(...chunk) as unknown as LinkRow[]);\n }\n };\n\n collectIn('src', delta.reparsed);\n collectIn('target_base', [...changedBasenames]);\n collectIn('dst', delta.vanished);\n\n return resolveRows(db, [...candidates.values()], pathSet, byBase);\n}\n\n// Resolved edges, for rank and for search's graph expansion.\nexport function linkEdges(db: DatabaseSync): [string, string][] {\n // One edge per row, as 0.12's grain always had it -- two written targets resolving to one\n // dst stay two edges (a weight the fusion evals were gated on). The only exclusion is an\n // embed whose exact (src, target) also exists as a link: that second row is new with the\n // embed grain and would double an edge that used to be one row. The NOT EXISTS probes the\n // primary key and fires only for embed rows; both branches still scan the table.\n // Self-edges (same-note anchors) are excluded here so PageRank mass is not self-recycled --\n // Obsidian's own graph view hides self-loops too. The rows themselves stay in the table for\n // backlinks/peek.\n const sql = `SELECT src, dst FROM links WHERE dst IS NOT NULL AND embed = 0 AND src != dst\n UNION ALL\n SELECT src, dst FROM links l WHERE dst IS NOT NULL AND embed = 1 AND src != dst\n AND NOT EXISTS (SELECT 1 FROM links l0 WHERE l0.src = l.src AND l0.target = l.target AND l0.embed = 0)`;\n return (db.prepare(sql).all() as Array<{ src: string; dst: string }>).map((r) => [r.src, r.dst]);\n}\n\n// remove() has already deleted the rows by the time afterReconcile runs, so it records here\n// whether they carried edges. Keyed on the delta object, so the state dies with the reconcile.\nconst removedWithLinks = new WeakMap<ReconcileDelta, Set<string>>();\n\nfunction recordRemoved(delta: ReconcileDelta, path: string): void {\n let set = removedWithLinks.get(delta);\n if (!set) {\n set = new Set();\n removedWithLinks.set(delta, set);\n }\n set.add(path);\n}\n\n// remove() runs per path; the vanished list is an array, so cache the Set per delta.\nconst vanishedSets = new WeakMap<ReconcileDelta, Set<string>>();\nfunction vanishedSet(delta: ReconcileDelta): Set<string> {\n let set = vanishedSets.get(delta);\n if (!set) {\n set = new Set(delta.vanished);\n vanishedSets.set(delta, set);\n }\n return set;\n}\n\nexport const links: Feature = {\n name: 'links',\n schema(db) {\n db.exec('CREATE TABLE IF NOT EXISTS links (src TEXT, target TEXT, target_base TEXT, dst TEXT, embed INTEGER, PRIMARY KEY (src, target, embed))');\n db.exec('CREATE INDEX IF NOT EXISTS links_dst ON links(dst)');\n db.exec('CREATE INDEX IF NOT EXISTS links_target_base ON links(target_base)');\n },\n extract,\n // Reparsed docs are diffed in store() below instead of wiped here: deleting and\n // re-inserting resets dst to NULL, which made every touch-only reparse read as an edge\n // change and recompute PageRank -- measured as most of the remaining update cost.\n remove(db, path, delta) {\n if (!vanishedSet(delta).has(path)) return;\n const result = db.prepare('DELETE FROM links WHERE src = ?').run(path);\n if (Number(result.changes) > 0) recordRemoved(delta, path);\n },\n store(db, path, extracted, delta) {\n const targets = extracted as Array<{ target: string; embed: boolean }>;\n // Stale rows (target/embed pairs the new parse no longer contains) are real edge removals.\n // \\0 can't appear in a target, so the composite key never collides.\n let stale: ReturnType<ReturnType<DatabaseSync['prepare']>['run']>;\n if (targets.length === 0) {\n stale = db.prepare('DELETE FROM links WHERE src = ?').run(path);\n } else {\n const placeholders = targets.map(() => '?').join(', ');\n const keys = targets.map((t) => `${t.target}\\0${t.embed ? '1' : '0'}`);\n stale = db.prepare(`DELETE FROM links WHERE src = ? AND (target || char(0) || embed) NOT IN (${placeholders})`).run(path, ...keys);\n }\n if (Number(stale.changes) > 0) recordRemoved(delta, path);\n // Upsert preserves dst on surviving rows, so an unchanged link resolves to the same\n // value and reports no change; only genuinely new rows start at NULL.\n const insert = db.prepare('INSERT INTO links (src, target, target_base, dst, embed) VALUES (?, ?, ?, NULL, ?) ON CONFLICT(src, target, embed) DO UPDATE SET target_base = excluded.target_base');\n // A same-note anchor's dst is always its own src, never another file's basename, so it\n // gets no target_base -- SQL's `IN` never matches NULL, keeping it out of\n // resolveIncremental's cross-file collectIn('target_base', ...) entirely.\n for (const { target, embed } of targets) insert.run(path, target, target.startsWith('#') ? null : baseKey(cleanTarget(target)), embed ? 1 : 0);\n },\n afterReconcile(db, delta) {\n // Any deleted rows that carried edges mean the edge set shrank -- whether the file\n // vanished or was reparsed down to fewer/no links. dstChanged alone misses the\n // reparsed-to-zero-links case (no surviving rows to re-resolve).\n const removeHadLinks = (removedWithLinks.get(delta)?.size ?? 0) > 0;\n\n const churn = delta.reparsed.length + delta.vanished.length;\n // Past this share of the tree, a full pass is the only one guaranteed to match\n // resolveTarget's ambiguity rules. A cold build clears the threshold on its own.\n const large = delta.files.length === 0 || churn > 0.2 * delta.files.length;\n\n const dstChanged = large ? resolveAll(db, delta.files) : resolveIncremental(db, delta);\n delta.linksChanged = dstChanged || removeHadLinks;\n },\n};\n"],"names":["linkEdges","links","extract","_raw","rawBody","_search","data","body","test","maskRegions","seen","Set","results","add","target","embed","key","has","push","matchAll","m","index","beforeAlias","split","trim","startsWith","length","dest","includes","Object","values","value","Array","isArray","item","exec","inner","cleanTarget","replace","baseKey","path","posix","basename","toLowerCase","resolveTarget","src","pathSet","byBase","clean","fromSrc","normalize","join","dirname","candidate","candidates","get","buildByBase","files","Map","paths","map","f","relPath","sort","a","b","list","set","resolveRows","db","rows","update","prepare","changed","row","dst","run","resolveAll","all","resolveIncremental","delta","changedBasenames","added","p","vanished","collect","collectIn","column","keys","i","chunk","slice","placeholders","reparsed","sql","r","removedWithLinks","WeakMap","recordRemoved","vanishedSets","vanishedSet","name","schema","remove","result","Number","changes","store","extracted","targets","stale","t","insert","afterReconcile","removeHadLinks","size","churn","large","dstChanged","linksChanged"],"mappings":"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"}
@@ -8,17 +8,15 @@ Object.defineProperty(exports, "sections", {
8
8
  return sections;
9
9
  }
10
10
  });
11
+ var _fencests = require("../fences.js");
11
12
  // Headings outside fenced code blocks.
12
13
  function extract(raw) {
13
14
  var lines = raw.split('\n');
14
15
  var found = [];
15
- var inFence = false;
16
+ var fence = (0, _fencests.fenceTracker)();
16
17
  for(var i = 0; i < lines.length; i++){
17
- if (/^(```|~~~)/.test(lines[i])) {
18
- inFence = !inFence;
19
- continue;
20
- }
21
- if (inFence) continue;
18
+ if (fence.feed(lines[i])) continue;
19
+ if (fence.inFence) continue;
22
20
  var m = lines[i].match(/^(#{1,6}) +(.*)/);
23
21
  if (m) found.push({
24
22
  level: m[1].length,
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/sections.ts"],"sourcesContent":["import type { Feature } from './types.ts';\n\n// sections(path, idx, level, heading, start_line, end_line, tokens): the heading outline,\n// 1-indexed over the raw file so a row is a direct Read range; tokens is a chars/4 estimate.\n\nexport interface Section {\n level: number;\n heading: string;\n startLine: number;\n endLine: number;\n tokens: number;\n}\n\n// Headings outside fenced code blocks.\nfunction extract(raw: string): Section[] {\n const lines = raw.split('\\n');\n const found: Section[] = [];\n let inFence = false;\n for (let i = 0; i < lines.length; i++) {\n if (/^(```|~~~)/.test(lines[i])) {\n inFence = !inFence;\n continue;\n }\n if (inFence) continue;\n const m = lines[i].match(/^(#{1,6}) +(.*)/);\n if (m) found.push({ level: m[1].length, heading: m[2].trim(), startLine: i + 1, endLine: lines.length, tokens: 0 });\n }\n for (let s = 0; s < found.length; s++) {\n if (s + 1 < found.length) found[s].endLine = found[s + 1].startLine - 1;\n const chars = lines.slice(found[s].startLine - 1, found[s].endLine).join('\\n').length;\n found[s].tokens = Math.ceil(chars / 4);\n }\n return found;\n}\n\nexport const sections: Feature = {\n name: 'sections',\n schema(db) {\n db.exec(`CREATE TABLE IF NOT EXISTS sections (\"path\" TEXT, idx INTEGER, level INTEGER, heading TEXT, start_line INTEGER, end_line INTEGER, tokens INTEGER, PRIMARY KEY (\"path\", idx))`);\n },\n extract,\n remove(db, path) {\n db.prepare('DELETE FROM sections WHERE \"path\" = ?').run(path);\n },\n store(db, path, extracted) {\n const insert = db.prepare('INSERT INTO sections (\"path\", idx, level, heading, start_line, end_line, tokens) VALUES (?, ?, ?, ?, ?, ?, ?)');\n (extracted as Section[]).forEach((s, idx) => insert.run(path, idx, s.level, s.heading, s.startLine, s.endLine, s.tokens));\n },\n};\n"],"names":["sections","extract","raw","lines","split","found","inFence","i","length","test","m","match","push","level","heading","trim","startLine","endLine","tokens","s","chars","slice","join","Math","ceil","name","schema","db","exec","remove","path","prepare","run","store","extracted","insert","forEach","idx"],"mappings":";;;;+BAmCaA;;;eAAAA;;;AAtBb,uCAAuC;AACvC,SAASC,QAAQC,GAAW;IAC1B,IAAMC,QAAQD,IAAIE,KAAK,CAAC;IACxB,IAAMC,QAAmB,EAAE;IAC3B,IAAIC,UAAU;IACd,IAAK,IAAIC,IAAI,GAAGA,IAAIJ,MAAMK,MAAM,EAAED,IAAK;QACrC,IAAI,aAAaE,IAAI,CAACN,KAAK,CAACI,EAAE,GAAG;YAC/BD,UAAU,CAACA;YACX;QACF;QACA,IAAIA,SAAS;QACb,IAAMI,IAAIP,KAAK,CAACI,EAAE,CAACI,KAAK,CAAC;QACzB,IAAID,GAAGL,MAAMO,IAAI,CAAC;YAAEC,OAAOH,CAAC,CAAC,EAAE,CAACF,MAAM;YAAEM,SAASJ,CAAC,CAAC,EAAE,CAACK,IAAI;YAAIC,WAAWT,IAAI;YAAGU,SAASd,MAAMK,MAAM;YAAEU,QAAQ;QAAE;IACnH;IACA,IAAK,IAAIC,IAAI,GAAGA,IAAId,MAAMG,MAAM,EAAEW,IAAK;QACrC,IAAIA,IAAI,IAAId,MAAMG,MAAM,EAAEH,KAAK,CAACc,EAAE,CAACF,OAAO,GAAGZ,KAAK,CAACc,IAAI,EAAE,CAACH,SAAS,GAAG;QACtE,IAAMI,QAAQjB,MAAMkB,KAAK,CAAChB,KAAK,CAACc,EAAE,CAACH,SAAS,GAAG,GAAGX,KAAK,CAACc,EAAE,CAACF,OAAO,EAAEK,IAAI,CAAC,MAAMd,MAAM;QACrFH,KAAK,CAACc,EAAE,CAACD,MAAM,GAAGK,KAAKC,IAAI,CAACJ,QAAQ;IACtC;IACA,OAAOf;AACT;AAEO,IAAML,WAAoB;IAC/ByB,MAAM;IACNC,QAAAA,SAAAA,OAAOC,EAAE;QACPA,GAAGC,IAAI,CAAC;IACV;IACA3B,SAAAA;IACA4B,QAAAA,SAAAA,OAAOF,EAAE,EAAEG,IAAI;QACbH,GAAGI,OAAO,CAAC,yCAAyCC,GAAG,CAACF;IAC1D;IACAG,OAAAA,SAAAA,MAAMN,EAAE,EAAEG,IAAI,EAAEI,SAAS;QACvB,IAAMC,SAASR,GAAGI,OAAO,CAAC;QACzBG,UAAwBE,OAAO,CAAC,SAACjB,GAAGkB;mBAAQF,OAAOH,GAAG,CAACF,MAAMO,KAAKlB,EAAEN,KAAK,EAAEM,EAAEL,OAAO,EAAEK,EAAEH,SAAS,EAAEG,EAAEF,OAAO,EAAEE,EAAED,MAAM;;IACzH;AACF"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/sections.ts"],"sourcesContent":["import { fenceTracker } from '../fences.ts';\nimport type { Feature } from './types.ts';\n\n// sections(path, idx, level, heading, start_line, end_line, tokens): the heading outline,\n// 1-indexed over the raw file so a row is a direct Read range; tokens is a chars/4 estimate.\n\nexport interface Section {\n level: number;\n heading: string;\n startLine: number;\n endLine: number;\n tokens: number;\n}\n\n// Headings outside fenced code blocks.\nfunction extract(raw: string): Section[] {\n const lines = raw.split('\\n');\n const found: Section[] = [];\n const fence = fenceTracker();\n for (let i = 0; i < lines.length; i++) {\n if (fence.feed(lines[i])) continue;\n if (fence.inFence) continue;\n const m = lines[i].match(/^(#{1,6}) +(.*)/);\n if (m) found.push({ level: m[1].length, heading: m[2].trim(), startLine: i + 1, endLine: lines.length, tokens: 0 });\n }\n for (let s = 0; s < found.length; s++) {\n if (s + 1 < found.length) found[s].endLine = found[s + 1].startLine - 1;\n const chars = lines.slice(found[s].startLine - 1, found[s].endLine).join('\\n').length;\n found[s].tokens = Math.ceil(chars / 4);\n }\n return found;\n}\n\nexport const sections: Feature = {\n name: 'sections',\n schema(db) {\n db.exec(`CREATE TABLE IF NOT EXISTS sections (\"path\" TEXT, idx INTEGER, level INTEGER, heading TEXT, start_line INTEGER, end_line INTEGER, tokens INTEGER, PRIMARY KEY (\"path\", idx))`);\n },\n extract,\n remove(db, path) {\n db.prepare('DELETE FROM sections WHERE \"path\" = ?').run(path);\n },\n store(db, path, extracted) {\n const insert = db.prepare('INSERT INTO sections (\"path\", idx, level, heading, start_line, end_line, tokens) VALUES (?, ?, ?, ?, ?, ?, ?)');\n (extracted as Section[]).forEach((s, idx) => insert.run(path, idx, s.level, s.heading, s.startLine, s.endLine, s.tokens));\n },\n};\n"],"names":["sections","extract","raw","lines","split","found","fence","fenceTracker","i","length","feed","inFence","m","match","push","level","heading","trim","startLine","endLine","tokens","s","chars","slice","join","Math","ceil","name","schema","db","exec","remove","path","prepare","run","store","extracted","insert","forEach","idx"],"mappings":";;;;+BAiCaA;;;eAAAA;;;wBAjCgB;AAc7B,uCAAuC;AACvC,SAASC,QAAQC,GAAW;IAC1B,IAAMC,QAAQD,IAAIE,KAAK,CAAC;IACxB,IAAMC,QAAmB,EAAE;IAC3B,IAAMC,QAAQC,IAAAA,sBAAY;IAC1B,IAAK,IAAIC,IAAI,GAAGA,IAAIL,MAAMM,MAAM,EAAED,IAAK;QACrC,IAAIF,MAAMI,IAAI,CAACP,KAAK,CAACK,EAAE,GAAG;QAC1B,IAAIF,MAAMK,OAAO,EAAE;QACnB,IAAMC,IAAIT,KAAK,CAACK,EAAE,CAACK,KAAK,CAAC;QACzB,IAAID,GAAGP,MAAMS,IAAI,CAAC;YAAEC,OAAOH,CAAC,CAAC,EAAE,CAACH,MAAM;YAAEO,SAASJ,CAAC,CAAC,EAAE,CAACK,IAAI;YAAIC,WAAWV,IAAI;YAAGW,SAAShB,MAAMM,MAAM;YAAEW,QAAQ;QAAE;IACnH;IACA,IAAK,IAAIC,IAAI,GAAGA,IAAIhB,MAAMI,MAAM,EAAEY,IAAK;QACrC,IAAIA,IAAI,IAAIhB,MAAMI,MAAM,EAAEJ,KAAK,CAACgB,EAAE,CAACF,OAAO,GAAGd,KAAK,CAACgB,IAAI,EAAE,CAACH,SAAS,GAAG;QACtE,IAAMI,QAAQnB,MAAMoB,KAAK,CAAClB,KAAK,CAACgB,EAAE,CAACH,SAAS,GAAG,GAAGb,KAAK,CAACgB,EAAE,CAACF,OAAO,EAAEK,IAAI,CAAC,MAAMf,MAAM;QACrFJ,KAAK,CAACgB,EAAE,CAACD,MAAM,GAAGK,KAAKC,IAAI,CAACJ,QAAQ;IACtC;IACA,OAAOjB;AACT;AAEO,IAAML,WAAoB;IAC/B2B,MAAM;IACNC,QAAAA,SAAAA,OAAOC,EAAE;QACPA,GAAGC,IAAI,CAAC;IACV;IACA7B,SAAAA;IACA8B,QAAAA,SAAAA,OAAOF,EAAE,EAAEG,IAAI;QACbH,GAAGI,OAAO,CAAC,yCAAyCC,GAAG,CAACF;IAC1D;IACAG,OAAAA,SAAAA,MAAMN,EAAE,EAAEG,IAAI,EAAEI,SAAS;QACvB,IAAMC,SAASR,GAAGI,OAAO,CAAC;QACzBG,UAAwBE,OAAO,CAAC,SAACjB,GAAGkB;mBAAQF,OAAOH,GAAG,CAACF,MAAMO,KAAKlB,EAAEN,KAAK,EAAEM,EAAEL,OAAO,EAAEK,EAAEH,SAAS,EAAEG,EAAEF,OAAO,EAAEE,EAAED,MAAM;;IACzH;AACF"}
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "tags", {
8
8
  return tags;
9
9
  }
10
10
  });
11
+ var _fencests = require("../fences.js");
11
12
  function _array_like_to_array(arr, len) {
12
13
  if (len == null || len > arr.length) len = arr.length;
13
14
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
@@ -36,14 +37,91 @@ function _unsupported_iterable_to_array(o, minLen) {
36
37
  // tags(path, tag): Obsidian's file.tags grain -- frontmatter list/string tags plus inline
37
38
  // #tags from the prose, deduplicated, source not distinguished. Nested tags store full
38
39
  // (book/scifi); `tag = 'book' OR tag LIKE 'book/%'` is how a caller matches the parent too.
39
- var FENCE_RE = /^(```|~~~)/;
40
- var INLINE_CODE_RE = /`[^`]*`/g;
41
40
  // Obsidian treats [[#Heading]] as a same-note link, not a tag.
42
41
  var WIKILINK_RE = /\[\[.*?\]\]/g; // to the first ]], so a heading holding a lone ] still masks
43
42
  // Obsidian doesn't read tags inside HTML markup.
44
43
  var HTML_TAG_RE = /<\/?[a-zA-Z][^>]*>/g; // tag-shaped only: a comparison's `< 5` must not open a span
45
44
  // Anchors on start-of-line or a preceding whitespace/(/[ so `a#b` and URL fragments don't count.
46
45
  var INLINE_TAG_RE = RegExp("(?:^|[\\s([])#([\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376-\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0560-\\u0588\\u05D0-\\u05EA\\u05EF-\\u05F2\\u0620-\\u064A\\u066E-\\u066F\\u0671-\\u06D3\\u06D5\\u06E5-\\u06E6\\u06EE-\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4-\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u0860-\\u086A\\u0870-\\u0887\\u0889-\\u088F\\u08A0-\\u08C9\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F-\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC-\\u09DD\\u09DF-\\u09E1\\u09F0-\\u09F1\\u09FC\\u0A05-\\u0A0A\\u0A0F-\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32-\\u0A33\\u0A35-\\u0A36\\u0A38-\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2-\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0-\\u0AE1\\u0AF9\\u0B05-\\u0B0C\\u0B0F-\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32-\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C-\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99-\\u0B9A\\u0B9C\\u0B9E-\\u0B9F\\u0BA3-\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58-\\u0C5A\\u0C5C-\\u0C5D\\u0C60-\\u0C61\\u0C80\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDC-\\u0CDE\\u0CE0-\\u0CE1\\u0CF1-\\u0CF2\\u0D04-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D54-\\u0D56\\u0D5F-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32-\\u0E33\\u0E40-\\u0E46\\u0E81-\\u0E82\\u0E84\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EB0\\u0EB2-\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065-\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16F1-\\u16F8\\u1700-\\u1711\\u171F-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1878\\u1880-\\u1884\\u1887-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19B0-\\u19C9\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4C\\u1B83-\\u1BA0\\u1BAE-\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1C80-\\u1C8A\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1CE9-\\u1CEC\\u1CEE-\\u1CF3\\u1CF5-\\u1CF6\\u1CFA\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183-\\u2184\\u2C00-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2-\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3006\\u3031-\\u3035\\u303B-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312F\\u3131-\\u318E\\u31A0-\\u31BF\\u31F0-\\u31FF\\u3400-\\u4DBF\\u4E00-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A-\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA7DC\\uA7F1-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA8FD-\\uA8FE\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA7E-\\uAAAF\\uAAB1\\uAAB5-\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB69\\uAB70-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40-\\uFB41\\uFB43-\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC\\u{10000}-\\u{1000B}\\u{1000D}-\\u{10026}\\u{10028}-\\u{1003A}\\u{1003C}-\\u{1003D}\\u{1003F}-\\u{1004D}\\u{10050}-\\u{1005D}\\u{10080}-\\u{100FA}\\u{10280}-\\u{1029C}\\u{102A0}-\\u{102D0}\\u{10300}-\\u{1031F}\\u{1032D}-\\u{10340}\\u{10342}-\\u{10349}\\u{10350}-\\u{10375}\\u{10380}-\\u{1039D}\\u{103A0}-\\u{103C3}\\u{103C8}-\\u{103CF}\\u{10400}-\\u{1049D}\\u{104B0}-\\u{104D3}\\u{104D8}-\\u{104FB}\\u{10500}-\\u{10527}\\u{10530}-\\u{10563}\\u{10570}-\\u{1057A}\\u{1057C}-\\u{1058A}\\u{1058C}-\\u{10592}\\u{10594}-\\u{10595}\\u{10597}-\\u{105A1}\\u{105A3}-\\u{105B1}\\u{105B3}-\\u{105B9}\\u{105BB}-\\u{105BC}\\u{105C0}-\\u{105F3}\\u{10600}-\\u{10736}\\u{10740}-\\u{10755}\\u{10760}-\\u{10767}\\u{10780}-\\u{10785}\\u{10787}-\\u{107B0}\\u{107B2}-\\u{107BA}\\u{10800}-\\u{10805}\\u{10808}\\u{1080A}-\\u{10835}\\u{10837}-\\u{10838}\\u{1083C}\\u{1083F}-\\u{10855}\\u{10860}-\\u{10876}\\u{10880}-\\u{1089E}\\u{108E0}-\\u{108F2}\\u{108F4}-\\u{108F5}\\u{10900}-\\u{10915}\\u{10920}-\\u{10939}\\u{10940}-\\u{10959}\\u{10980}-\\u{109B7}\\u{109BE}-\\u{109BF}\\u{10A00}\\u{10A10}-\\u{10A13}\\u{10A15}-\\u{10A17}\\u{10A19}-\\u{10A35}\\u{10A60}-\\u{10A7C}\\u{10A80}-\\u{10A9C}\\u{10AC0}-\\u{10AC7}\\u{10AC9}-\\u{10AE4}\\u{10B00}-\\u{10B35}\\u{10B40}-\\u{10B55}\\u{10B60}-\\u{10B72}\\u{10B80}-\\u{10B91}\\u{10C00}-\\u{10C48}\\u{10C80}-\\u{10CB2}\\u{10CC0}-\\u{10CF2}\\u{10D00}-\\u{10D23}\\u{10D4A}-\\u{10D65}\\u{10D6F}-\\u{10D85}\\u{10E80}-\\u{10EA9}\\u{10EB0}-\\u{10EB1}\\u{10EC2}-\\u{10EC7}\\u{10F00}-\\u{10F1C}\\u{10F27}\\u{10F30}-\\u{10F45}\\u{10F70}-\\u{10F81}\\u{10FB0}-\\u{10FC4}\\u{10FE0}-\\u{10FF6}\\u{11003}-\\u{11037}\\u{11071}-\\u{11072}\\u{11075}\\u{11083}-\\u{110AF}\\u{110D0}-\\u{110E8}\\u{11103}-\\u{11126}\\u{11144}\\u{11147}\\u{11150}-\\u{11172}\\u{11176}\\u{11183}-\\u{111B2}\\u{111C1}-\\u{111C4}\\u{111DA}\\u{111DC}\\u{11200}-\\u{11211}\\u{11213}-\\u{1122B}\\u{1123F}-\\u{11240}\\u{11280}-\\u{11286}\\u{11288}\\u{1128A}-\\u{1128D}\\u{1128F}-\\u{1129D}\\u{1129F}-\\u{112A8}\\u{112B0}-\\u{112DE}\\u{11305}-\\u{1130C}\\u{1130F}-\\u{11310}\\u{11313}-\\u{11328}\\u{1132A}-\\u{11330}\\u{11332}-\\u{11333}\\u{11335}-\\u{11339}\\u{1133D}\\u{11350}\\u{1135D}-\\u{11361}\\u{11380}-\\u{11389}\\u{1138B}\\u{1138E}\\u{11390}-\\u{113B5}\\u{113B7}\\u{113D1}\\u{113D3}\\u{11400}-\\u{11434}\\u{11447}-\\u{1144A}\\u{1145F}-\\u{11461}\\u{11480}-\\u{114AF}\\u{114C4}-\\u{114C5}\\u{114C7}\\u{11580}-\\u{115AE}\\u{115D8}-\\u{115DB}\\u{11600}-\\u{1162F}\\u{11644}\\u{11680}-\\u{116AA}\\u{116B8}\\u{11700}-\\u{1171A}\\u{11740}-\\u{11746}\\u{11800}-\\u{1182B}\\u{118A0}-\\u{118DF}\\u{118FF}-\\u{11906}\\u{11909}\\u{1190C}-\\u{11913}\\u{11915}-\\u{11916}\\u{11918}-\\u{1192F}\\u{1193F}\\u{11941}\\u{119A0}-\\u{119A7}\\u{119AA}-\\u{119D0}\\u{119E1}\\u{119E3}\\u{11A00}\\u{11A0B}-\\u{11A32}\\u{11A3A}\\u{11A50}\\u{11A5C}-\\u{11A89}\\u{11A9D}\\u{11AB0}-\\u{11AF8}\\u{11BC0}-\\u{11BE0}\\u{11C00}-\\u{11C08}\\u{11C0A}-\\u{11C2E}\\u{11C40}\\u{11C72}-\\u{11C8F}\\u{11D00}-\\u{11D06}\\u{11D08}-\\u{11D09}\\u{11D0B}-\\u{11D30}\\u{11D46}\\u{11D60}-\\u{11D65}\\u{11D67}-\\u{11D68}\\u{11D6A}-\\u{11D89}\\u{11D98}\\u{11DB0}-\\u{11DDB}\\u{11EE0}-\\u{11EF2}\\u{11F02}\\u{11F04}-\\u{11F10}\\u{11F12}-\\u{11F33}\\u{11FB0}\\u{12000}-\\u{12399}\\u{12480}-\\u{12543}\\u{12F90}-\\u{12FF0}\\u{13000}-\\u{1342F}\\u{13441}-\\u{13446}\\u{13460}-\\u{143FA}\\u{14400}-\\u{14646}\\u{16100}-\\u{1611D}\\u{16800}-\\u{16A38}\\u{16A40}-\\u{16A5E}\\u{16A70}-\\u{16ABE}\\u{16AD0}-\\u{16AED}\\u{16B00}-\\u{16B2F}\\u{16B40}-\\u{16B43}\\u{16B63}-\\u{16B77}\\u{16B7D}-\\u{16B8F}\\u{16D40}-\\u{16D6C}\\u{16E40}-\\u{16E7F}\\u{16EA0}-\\u{16EB8}\\u{16EBB}-\\u{16ED3}\\u{16F00}-\\u{16F4A}\\u{16F50}\\u{16F93}-\\u{16F9F}\\u{16FE0}-\\u{16FE1}\\u{16FE3}\\u{16FF2}-\\u{16FF3}\\u{17000}-\\u{18CD5}\\u{18CFF}-\\u{18D1E}\\u{18D80}-\\u{18DF2}\\u{1AFF0}-\\u{1AFF3}\\u{1AFF5}-\\u{1AFFB}\\u{1AFFD}-\\u{1AFFE}\\u{1B000}-\\u{1B122}\\u{1B132}\\u{1B150}-\\u{1B152}\\u{1B155}\\u{1B164}-\\u{1B167}\\u{1B170}-\\u{1B2FB}\\u{1BC00}-\\u{1BC6A}\\u{1BC70}-\\u{1BC7C}\\u{1BC80}-\\u{1BC88}\\u{1BC90}-\\u{1BC99}\\u{1D400}-\\u{1D454}\\u{1D456}-\\u{1D49C}\\u{1D49E}-\\u{1D49F}\\u{1D4A2}\\u{1D4A5}-\\u{1D4A6}\\u{1D4A9}-\\u{1D4AC}\\u{1D4AE}-\\u{1D4B9}\\u{1D4BB}\\u{1D4BD}-\\u{1D4C3}\\u{1D4C5}-\\u{1D505}\\u{1D507}-\\u{1D50A}\\u{1D50D}-\\u{1D514}\\u{1D516}-\\u{1D51C}\\u{1D51E}-\\u{1D539}\\u{1D53B}-\\u{1D53E}\\u{1D540}-\\u{1D544}\\u{1D546}\\u{1D54A}-\\u{1D550}\\u{1D552}-\\u{1D6A5}\\u{1D6A8}-\\u{1D6C0}\\u{1D6C2}-\\u{1D6DA}\\u{1D6DC}-\\u{1D6FA}\\u{1D6FC}-\\u{1D714}\\u{1D716}-\\u{1D734}\\u{1D736}-\\u{1D74E}\\u{1D750}-\\u{1D76E}\\u{1D770}-\\u{1D788}\\u{1D78A}-\\u{1D7A8}\\u{1D7AA}-\\u{1D7C2}\\u{1D7C4}-\\u{1D7CB}\\u{1DF00}-\\u{1DF1E}\\u{1DF25}-\\u{1DF2A}\\u{1E030}-\\u{1E06D}\\u{1E100}-\\u{1E12C}\\u{1E137}-\\u{1E13D}\\u{1E14E}\\u{1E290}-\\u{1E2AD}\\u{1E2C0}-\\u{1E2EB}\\u{1E4D0}-\\u{1E4EB}\\u{1E5D0}-\\u{1E5ED}\\u{1E5F0}\\u{1E6C0}-\\u{1E6DE}\\u{1E6E0}-\\u{1E6E2}\\u{1E6E4}-\\u{1E6E5}\\u{1E6E7}-\\u{1E6ED}\\u{1E6F0}-\\u{1E6F4}\\u{1E6FE}-\\u{1E6FF}\\u{1E7E0}-\\u{1E7E6}\\u{1E7E8}-\\u{1E7EB}\\u{1E7ED}-\\u{1E7EE}\\u{1E7F0}-\\u{1E7FE}\\u{1E800}-\\u{1E8C4}\\u{1E900}-\\u{1E943}\\u{1E94B}\\u{1EE00}-\\u{1EE03}\\u{1EE05}-\\u{1EE1F}\\u{1EE21}-\\u{1EE22}\\u{1EE24}\\u{1EE27}\\u{1EE29}-\\u{1EE32}\\u{1EE34}-\\u{1EE37}\\u{1EE39}\\u{1EE3B}\\u{1EE42}\\u{1EE47}\\u{1EE49}\\u{1EE4B}\\u{1EE4D}-\\u{1EE4F}\\u{1EE51}-\\u{1EE52}\\u{1EE54}\\u{1EE57}\\u{1EE59}\\u{1EE5B}\\u{1EE5D}\\u{1EE5F}\\u{1EE61}-\\u{1EE62}\\u{1EE64}\\u{1EE67}-\\u{1EE6A}\\u{1EE6C}-\\u{1EE72}\\u{1EE74}-\\u{1EE77}\\u{1EE79}-\\u{1EE7C}\\u{1EE7E}\\u{1EE80}-\\u{1EE89}\\u{1EE8B}-\\u{1EE9B}\\u{1EEA1}-\\u{1EEA3}\\u{1EEA5}-\\u{1EEA9}\\u{1EEAB}-\\u{1EEBB}\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B81D}\\u{2B820}-\\u{2CEAD}\\u{2CEB0}-\\u{2EBE0}\\u{2EBF0}-\\u{2EE5D}\\u{2F800}-\\u{2FA1D}\\u{30000}-\\u{3134A}\\u{31350}-\\u{33479}\\u0030-\\u0039\\u00B2-\\u00B3\\u00B9\\u00BC-\\u00BE\\u0660-\\u0669\\u06F0-\\u06F9\\u07C0-\\u07C9\\u0966-\\u096F\\u09E6-\\u09EF\\u09F4-\\u09F9\\u0A66-\\u0A6F\\u0AE6-\\u0AEF\\u0B66-\\u0B6F\\u0B72-\\u0B77\\u0BE6-\\u0BF2\\u0C66-\\u0C6F\\u0C78-\\u0C7E\\u0CE6-\\u0CEF\\u0D58-\\u0D5E\\u0D66-\\u0D78\\u0DE6-\\u0DEF\\u0E50-\\u0E59\\u0ED0-\\u0ED9\\u0F20-\\u0F33\\u1040-\\u1049\\u1090-\\u1099\\u1369-\\u137C\\u16EE-\\u16F0\\u17E0-\\u17E9\\u17F0-\\u17F9\\u1810-\\u1819\\u1946-\\u194F\\u19D0-\\u19DA\\u1A80-\\u1A89\\u1A90-\\u1A99\\u1B50-\\u1B59\\u1BB0-\\u1BB9\\u1C40-\\u1C49\\u1C50-\\u1C59\\u2070\\u2074-\\u2079\\u2080-\\u2089\\u2150-\\u2182\\u2185-\\u2189\\u2460-\\u249B\\u24EA-\\u24FF\\u2776-\\u2793\\u2CFD\\u3007\\u3021-\\u3029\\u3038-\\u303A\\u3192-\\u3195\\u3220-\\u3229\\u3248-\\u324F\\u3251-\\u325F\\u3280-\\u3289\\u32B1-\\u32BF\\uA620-\\uA629\\uA6E6-\\uA6EF\\uA830-\\uA835\\uA8D0-\\uA8D9\\uA900-\\uA909\\uA9D0-\\uA9D9\\uA9F0-\\uA9F9\\uAA50-\\uAA59\\uABF0-\\uABF9\\uFF10-\\uFF19\\u{10107}-\\u{10133}\\u{10140}-\\u{10178}\\u{1018A}-\\u{1018B}\\u{102E1}-\\u{102FB}\\u{10320}-\\u{10323}\\u{10341}\\u{1034A}\\u{103D1}-\\u{103D5}\\u{104A0}-\\u{104A9}\\u{10858}-\\u{1085F}\\u{10879}-\\u{1087F}\\u{108A7}-\\u{108AF}\\u{108FB}-\\u{108FF}\\u{10916}-\\u{1091B}\\u{109BC}-\\u{109BD}\\u{109C0}-\\u{109CF}\\u{109D2}-\\u{109FF}\\u{10A40}-\\u{10A48}\\u{10A7D}-\\u{10A7E}\\u{10A9D}-\\u{10A9F}\\u{10AEB}-\\u{10AEF}\\u{10B58}-\\u{10B5F}\\u{10B78}-\\u{10B7F}\\u{10BA9}-\\u{10BAF}\\u{10CFA}-\\u{10CFF}\\u{10D30}-\\u{10D39}\\u{10D40}-\\u{10D49}\\u{10E60}-\\u{10E7E}\\u{10F1D}-\\u{10F26}\\u{10F51}-\\u{10F54}\\u{10FC5}-\\u{10FCB}\\u{11052}-\\u{1106F}\\u{110F0}-\\u{110F9}\\u{11136}-\\u{1113F}\\u{111D0}-\\u{111D9}\\u{111E1}-\\u{111F4}\\u{112F0}-\\u{112F9}\\u{11450}-\\u{11459}\\u{114D0}-\\u{114D9}\\u{11650}-\\u{11659}\\u{116C0}-\\u{116C9}\\u{116D0}-\\u{116E3}\\u{11730}-\\u{1173B}\\u{118E0}-\\u{118F2}\\u{11950}-\\u{11959}\\u{11BF0}-\\u{11BF9}\\u{11C50}-\\u{11C6C}\\u{11D50}-\\u{11D59}\\u{11DA0}-\\u{11DA9}\\u{11DE0}-\\u{11DE9}\\u{11F50}-\\u{11F59}\\u{11FC0}-\\u{11FD4}\\u{12400}-\\u{1246E}\\u{16130}-\\u{16139}\\u{16A60}-\\u{16A69}\\u{16AC0}-\\u{16AC9}\\u{16B50}-\\u{16B59}\\u{16B5B}-\\u{16B61}\\u{16D70}-\\u{16D79}\\u{16E80}-\\u{16E96}\\u{16FF4}-\\u{16FF6}\\u{1CCF0}-\\u{1CCF9}\\u{1D2C0}-\\u{1D2D3}\\u{1D2E0}-\\u{1D2F3}\\u{1D360}-\\u{1D378}\\u{1D7CE}-\\u{1D7FF}\\u{1E140}-\\u{1E149}\\u{1E2F0}-\\u{1E2F9}\\u{1E4F0}-\\u{1E4F9}\\u{1E5F1}-\\u{1E5FA}\\u{1E8C7}-\\u{1E8CF}\\u{1E950}-\\u{1E959}\\u{1EC71}-\\u{1ECAB}\\u{1ECAD}-\\u{1ECAF}\\u{1ECB1}-\\u{1ECB4}\\u{1ED01}-\\u{1ED2D}\\u{1ED2F}-\\u{1ED3D}\\u{1F100}-\\u{1F10C}\\u{1FBF0}-\\u{1FBF9}_/-]+)", "gu");
46
+ // A markdown link destination `](...)` -- `[text](#anchor)` is a same-page link, not a tag.
47
+ var LINK_DEST_RE = /\]\((?:[^()]|\([^()]*\))*\)/g; // one paren-nesting level, as CommonMark destinations allow: (https://x/a_(b)#frag)
48
+ // CommonMark's HTML-block type-6 list (fixed by the spec, not a drifting enumeration): a line
49
+ // starting with an open or close tag of one of these, at column 0, opens a block that swallows
50
+ // following lines -- including any #tag in them -- until a blank line closes it.
51
+ var HTML_BLOCK_TAGS = new Set([
52
+ 'address',
53
+ 'article',
54
+ 'aside',
55
+ 'base',
56
+ 'basefont',
57
+ 'blockquote',
58
+ 'body',
59
+ 'caption',
60
+ 'center',
61
+ 'col',
62
+ 'colgroup',
63
+ 'dd',
64
+ 'details',
65
+ 'dialog',
66
+ 'dir',
67
+ 'div',
68
+ 'dl',
69
+ 'dt',
70
+ 'fieldset',
71
+ 'figcaption',
72
+ 'figure',
73
+ 'footer',
74
+ 'form',
75
+ 'frame',
76
+ 'frameset',
77
+ 'h1',
78
+ 'h2',
79
+ 'h3',
80
+ 'h4',
81
+ 'h5',
82
+ 'h6',
83
+ 'head',
84
+ 'header',
85
+ 'hr',
86
+ 'html',
87
+ 'iframe',
88
+ 'legend',
89
+ 'li',
90
+ 'link',
91
+ 'main',
92
+ 'menu',
93
+ 'menuitem',
94
+ 'nav',
95
+ 'noframes',
96
+ 'ol',
97
+ 'optgroup',
98
+ 'option',
99
+ 'p',
100
+ 'param',
101
+ 'search',
102
+ 'section',
103
+ 'summary',
104
+ 'table',
105
+ 'tbody',
106
+ 'td',
107
+ 'tfoot',
108
+ 'th',
109
+ 'thead',
110
+ 'title',
111
+ 'tr',
112
+ 'track',
113
+ 'ul'
114
+ ]);
115
+ // Type-1 blocks (script/pre/style/textarea): closes on the line holding the matching end tag,
116
+ // not on a blank line, and that line is the last one skipped.
117
+ var HTML_PRE_TAGS = new Set([
118
+ 'script',
119
+ 'pre',
120
+ 'style',
121
+ 'textarea'
122
+ ]);
123
+ // An opening or closing tag at column 0, tag name captured for the lookups above.
124
+ var HTML_BLOCK_OPEN_RE = /^<\/?([a-zA-Z][a-zA-Z0-9]*)(?:[ \t]|\/?>|$)/;
47
125
  // Strips a leading # (frontmatter entries may carry one) and a trailing /; rejects an
48
126
  // all-digit result -- a tag needs at least one non-digit character.
49
127
  function normalizeTag(raw) {
@@ -83,26 +161,66 @@ function frontmatterTags(data) {
83
161
  }
84
162
  return found;
85
163
  }
86
- // #tag tokens outside fenced code blocks, inline code spans, wikilinks, and HTML tags.
164
+ // #tag tokens outside fenced code blocks, inline code spans, wikilinks, HTML tags, HTML blocks,
165
+ // <!-- --> comments, and link destinations.
87
166
  function inlineTags(body) {
88
167
  var found = [];
89
- var inFence = false;
168
+ var fence = (0, _fencests.fenceTracker)();
169
+ var comment = (0, _fencests.commentTracker)();
170
+ var inHtmlBlock = false;
171
+ var htmlBlockClose = null; // set while inside a type-1 (script/pre/style/textarea) block
90
172
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
91
173
  try {
92
174
  for(var _iterator = body.split('\n')[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
93
175
  var line = _step.value;
94
- if (FENCE_RE.test(line)) {
95
- inFence = !inFence;
176
+ if (inHtmlBlock) {
177
+ // A fence-like line here is still HTML-block content -- the block wins until it closes.
178
+ if (htmlBlockClose) {
179
+ if (htmlBlockClose.test(line)) {
180
+ inHtmlBlock = false;
181
+ htmlBlockClose = null;
182
+ }
183
+ } else if (/^[ \t>]*$/.test(line)) {
184
+ inHtmlBlock = false;
185
+ }
96
186
  continue;
97
187
  }
98
- if (inFence) continue;
99
- if (!line.includes('#')) continue; // most lines; skip the regex work
100
- var cleaned = line.includes('`') ? line.replace(INLINE_CODE_RE, function(m) {
101
- return ' '.repeat(m.length);
102
- }) : line;
188
+ if (!comment.inComment) {
189
+ if (fence.feed(line)) continue;
190
+ if (fence.inFence) continue;
191
+ }
192
+ // Obsidian parses nothing inside a <!-- --> comment; mask its span (which may open, close,
193
+ // or run the whole line) before any other check sees this line's text. Skipped when there
194
+ // is no comment marker anywhere near this line -- most lines, so worth the branch.
195
+ var masked = comment.inComment || line.includes('<!--') ? comment.mask(line) : line;
196
+ // Indented or blockquoted HTML blocks still swallow their content in Obsidian, so the
197
+ // opener test runs after stripping leading whitespace and > markers.
198
+ var stripped = masked.replace(/^[ \t>]*/, '');
199
+ if (stripped[0] === '<') {
200
+ var openMatch = HTML_BLOCK_OPEN_RE.exec(stripped);
201
+ if (openMatch) {
202
+ var tagName = openMatch[1].toLowerCase();
203
+ var isClosingTag = stripped[1] === '/';
204
+ if (!isClosingTag && HTML_PRE_TAGS.has(tagName)) {
205
+ inHtmlBlock = true;
206
+ // Any of the four type-1 closers ends the block, not only the tag that opened it.
207
+ htmlBlockClose = /<\/(?:script|pre|style|textarea)>/i;
208
+ continue;
209
+ }
210
+ if (HTML_BLOCK_TAGS.has(tagName)) {
211
+ inHtmlBlock = true;
212
+ continue;
213
+ }
214
+ }
215
+ }
216
+ if (!masked.includes('#')) continue; // most lines; skip the regex work
217
+ var cleaned = masked.includes('`') ? (0, _fencests.maskCodeSpans)(masked) : masked;
103
218
  if (cleaned.includes('[[')) cleaned = cleaned.replace(WIKILINK_RE, function(m) {
104
219
  return ' '.repeat(m.length);
105
220
  });
221
+ if (cleaned.includes('](')) cleaned = cleaned.replace(LINK_DEST_RE, function(m) {
222
+ return ' '.repeat(m.length);
223
+ });
106
224
  if (cleaned.includes('<')) cleaned = cleaned.replace(HTML_TAG_RE, function(m) {
107
225
  return ' '.repeat(m.length);
108
226
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/tags.ts"],"sourcesContent":["import type { Feature } from './types.ts';\n\n// tags(path, tag): Obsidian's file.tags grain -- frontmatter list/string tags plus inline\n// #tags from the prose, deduplicated, source not distinguished. Nested tags store full\n// (book/scifi); `tag = 'book' OR tag LIKE 'book/%'` is how a caller matches the parent too.\n\nconst FENCE_RE = /^(```|~~~)/;\nconst INLINE_CODE_RE = /`[^`]*`/g;\n// Obsidian treats [[#Heading]] as a same-note link, not a tag.\nconst WIKILINK_RE = /\\[\\[.*?\\]\\]/g; // to the first ]], so a heading holding a lone ] still masks\n// Obsidian doesn't read tags inside HTML markup.\nconst HTML_TAG_RE = /<\\/?[a-zA-Z][^>]*>/g; // tag-shaped only: a comparison's `< 5` must not open a span\n// Anchors on start-of-line or a preceding whitespace/(/[ so `a#b` and URL fragments don't count.\nconst INLINE_TAG_RE = /(?:^|[\\s([])#([\\p{L}\\p{N}_/-]+)/gu;\n\n// Strips a leading # (frontmatter entries may carry one) and a trailing /; rejects an\n// all-digit result -- a tag needs at least one non-digit character.\nfunction normalizeTag(raw: string): string | null {\n const stripped = raw.replace(/^#/, '').replace(/\\/+$/, '');\n if (!stripped || /^\\d+$/.test(stripped)) return null;\n return stripped;\n}\n\n// data.tags: a YAML list (Obsidian also accepts a bare string). Null members and non-string\n// members are skipped rather than throwing -- `tags:\\n -` parses to [null].\nfunction frontmatterTags(data?: Record<string, unknown>): string[] {\n const raw = data?.tags;\n const list = Array.isArray(raw) ? raw : typeof raw === 'string' ? [raw] : [];\n const found: string[] = [];\n for (const item of list) {\n if (typeof item !== 'string') continue;\n const tag = normalizeTag(item);\n if (tag) found.push(tag);\n }\n return found;\n}\n\n// #tag tokens outside fenced code blocks, inline code spans, wikilinks, and HTML tags.\nfunction inlineTags(body: string): string[] {\n const found: string[] = [];\n let inFence = false;\n for (const line of body.split('\\n')) {\n if (FENCE_RE.test(line)) {\n inFence = !inFence;\n continue;\n }\n if (inFence) continue;\n if (!line.includes('#')) continue; // most lines; skip the regex work\n let cleaned = line.includes('`') ? line.replace(INLINE_CODE_RE, (m) => ' '.repeat(m.length)) : line;\n if (cleaned.includes('[[')) cleaned = cleaned.replace(WIKILINK_RE, (m) => ' '.repeat(m.length));\n if (cleaned.includes('<')) cleaned = cleaned.replace(HTML_TAG_RE, (m) => ' '.repeat(m.length));\n for (const m of cleaned.matchAll(INLINE_TAG_RE)) {\n const tag = normalizeTag(m[1]);\n if (tag) found.push(tag);\n }\n }\n return found;\n}\n\nfunction extract(_raw: string, body: string, _search?: { title: string; summary: string }, data?: Record<string, unknown>): string[] {\n return [...new Set([...frontmatterTags(data), ...inlineTags(body)])].sort();\n}\n\nexport const tags: Feature = {\n name: 'tags',\n schema(db) {\n db.exec('CREATE TABLE IF NOT EXISTS tags (\"path\" TEXT, tag TEXT, PRIMARY KEY (\"path\", tag))');\n db.exec('CREATE INDEX IF NOT EXISTS tags_tag ON tags(tag)');\n },\n extract,\n // Per-file rows with nothing else to resolve, so only a vanished file needs a delete here;\n // store() below handles a reparse's stale rows itself.\n remove(db, path, delta) {\n if (!delta.vanished.includes(path)) return;\n db.prepare('DELETE FROM tags WHERE \"path\" = ?').run(path);\n },\n store(db, path, extracted) {\n const found = extracted as string[];\n if (found.length === 0) {\n db.prepare('DELETE FROM tags WHERE \"path\" = ?').run(path);\n } else {\n const placeholders = found.map(() => '?').join(', ');\n db.prepare(`DELETE FROM tags WHERE \"path\" = ? AND tag NOT IN (${placeholders})`).run(path, ...found);\n }\n const insert = db.prepare('INSERT OR IGNORE INTO tags (\"path\", tag) VALUES (?, ?)');\n for (const tag of found) insert.run(path, tag);\n },\n};\n"],"names":["tags","FENCE_RE","INLINE_CODE_RE","WIKILINK_RE","HTML_TAG_RE","INLINE_TAG_RE","normalizeTag","raw","stripped","replace","test","frontmatterTags","data","list","Array","isArray","found","item","tag","push","inlineTags","body","inFence","split","line","includes","cleaned","m","repeat","length","matchAll","extract","_raw","_search","Set","sort","name","schema","db","exec","remove","path","delta","vanished","prepare","run","store","extracted","placeholders","map","join","insert"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/tags.ts"],"sourcesContent":["import { commentTracker, fenceTracker, maskCodeSpans } from '../fences.ts';\nimport type { Feature } from './types.ts';\n\n// tags(path, tag): Obsidian's file.tags grain -- frontmatter list/string tags plus inline\n// #tags from the prose, deduplicated, source not distinguished. Nested tags store full\n// (book/scifi); `tag = 'book' OR tag LIKE 'book/%'` is how a caller matches the parent too.\n\n// Obsidian treats [[#Heading]] as a same-note link, not a tag.\nconst WIKILINK_RE = /\\[\\[.*?\\]\\]/g; // to the first ]], so a heading holding a lone ] still masks\n// Obsidian doesn't read tags inside HTML markup.\nconst HTML_TAG_RE = /<\\/?[a-zA-Z][^>]*>/g; // tag-shaped only: a comparison's `< 5` must not open a span\n// Anchors on start-of-line or a preceding whitespace/(/[ so `a#b` and URL fragments don't count.\nconst INLINE_TAG_RE = /(?:^|[\\s([])#([\\p{L}\\p{N}_/-]+)/gu;\n// A markdown link destination `](...)` -- `[text](#anchor)` is a same-page link, not a tag.\nconst LINK_DEST_RE = /\\]\\((?:[^()]|\\([^()]*\\))*\\)/g; // one paren-nesting level, as CommonMark destinations allow: (https://x/a_(b)#frag)\n\n// CommonMark's HTML-block type-6 list (fixed by the spec, not a drifting enumeration): a line\n// starting with an open or close tag of one of these, at column 0, opens a block that swallows\n// following lines -- including any #tag in them -- until a blank line closes it.\nconst HTML_BLOCK_TAGS = new Set([\n 'address',\n 'article',\n 'aside',\n 'base',\n 'basefont',\n 'blockquote',\n 'body',\n 'caption',\n 'center',\n 'col',\n 'colgroup',\n 'dd',\n 'details',\n 'dialog',\n 'dir',\n 'div',\n 'dl',\n 'dt',\n 'fieldset',\n 'figcaption',\n 'figure',\n 'footer',\n 'form',\n 'frame',\n 'frameset',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'head',\n 'header',\n 'hr',\n 'html',\n 'iframe',\n 'legend',\n 'li',\n 'link',\n 'main',\n 'menu',\n 'menuitem',\n 'nav',\n 'noframes',\n 'ol',\n 'optgroup',\n 'option',\n 'p',\n 'param',\n 'search',\n 'section',\n 'summary',\n 'table',\n 'tbody',\n 'td',\n 'tfoot',\n 'th',\n 'thead',\n 'title',\n 'tr',\n 'track',\n 'ul',\n]);\n// Type-1 blocks (script/pre/style/textarea): closes on the line holding the matching end tag,\n// not on a blank line, and that line is the last one skipped.\nconst HTML_PRE_TAGS = new Set(['script', 'pre', 'style', 'textarea']);\n// An opening or closing tag at column 0, tag name captured for the lookups above.\nconst HTML_BLOCK_OPEN_RE = /^<\\/?([a-zA-Z][a-zA-Z0-9]*)(?:[ \\t]|\\/?>|$)/;\n\n// Strips a leading # (frontmatter entries may carry one) and a trailing /; rejects an\n// all-digit result -- a tag needs at least one non-digit character.\nfunction normalizeTag(raw: string): string | null {\n const stripped = raw.replace(/^#/, '').replace(/\\/+$/, '');\n if (!stripped || /^\\d+$/.test(stripped)) return null;\n return stripped;\n}\n\n// data.tags: a YAML list (Obsidian also accepts a bare string). Null members and non-string\n// members are skipped rather than throwing -- `tags:\\n -` parses to [null].\nfunction frontmatterTags(data?: Record<string, unknown>): string[] {\n const raw = data?.tags;\n const list = Array.isArray(raw) ? raw : typeof raw === 'string' ? [raw] : [];\n const found: string[] = [];\n for (const item of list) {\n if (typeof item !== 'string') continue;\n const tag = normalizeTag(item);\n if (tag) found.push(tag);\n }\n return found;\n}\n\n// #tag tokens outside fenced code blocks, inline code spans, wikilinks, HTML tags, HTML blocks,\n// <!-- --> comments, and link destinations.\nfunction inlineTags(body: string): string[] {\n const found: string[] = [];\n const fence = fenceTracker();\n const comment = commentTracker();\n let inHtmlBlock = false;\n let htmlBlockClose: RegExp | null = null; // set while inside a type-1 (script/pre/style/textarea) block\n for (const line of body.split('\\n')) {\n if (inHtmlBlock) {\n // A fence-like line here is still HTML-block content -- the block wins until it closes.\n if (htmlBlockClose) {\n if (htmlBlockClose.test(line)) {\n inHtmlBlock = false;\n htmlBlockClose = null;\n }\n } else if (/^[ \\t>]*$/.test(line)) {\n inHtmlBlock = false;\n }\n continue;\n }\n if (!comment.inComment) {\n if (fence.feed(line)) continue;\n if (fence.inFence) continue;\n }\n // Obsidian parses nothing inside a <!-- --> comment; mask its span (which may open, close,\n // or run the whole line) before any other check sees this line's text. Skipped when there\n // is no comment marker anywhere near this line -- most lines, so worth the branch.\n const masked = comment.inComment || line.includes('<!--') ? comment.mask(line) : line;\n // Indented or blockquoted HTML blocks still swallow their content in Obsidian, so the\n // opener test runs after stripping leading whitespace and > markers.\n const stripped = masked.replace(/^[ \\t>]*/, '');\n if (stripped[0] === '<') {\n const openMatch = HTML_BLOCK_OPEN_RE.exec(stripped);\n if (openMatch) {\n const tagName = openMatch[1].toLowerCase();\n const isClosingTag = stripped[1] === '/';\n if (!isClosingTag && HTML_PRE_TAGS.has(tagName)) {\n inHtmlBlock = true;\n // Any of the four type-1 closers ends the block, not only the tag that opened it.\n htmlBlockClose = /<\\/(?:script|pre|style|textarea)>/i;\n continue;\n }\n if (HTML_BLOCK_TAGS.has(tagName)) {\n inHtmlBlock = true;\n continue;\n }\n }\n }\n if (!masked.includes('#')) continue; // most lines; skip the regex work\n let cleaned = masked.includes('`') ? maskCodeSpans(masked) : masked;\n if (cleaned.includes('[[')) cleaned = cleaned.replace(WIKILINK_RE, (m) => ' '.repeat(m.length));\n if (cleaned.includes('](')) cleaned = cleaned.replace(LINK_DEST_RE, (m) => ' '.repeat(m.length));\n if (cleaned.includes('<')) cleaned = cleaned.replace(HTML_TAG_RE, (m) => ' '.repeat(m.length));\n for (const m of cleaned.matchAll(INLINE_TAG_RE)) {\n const tag = normalizeTag(m[1]);\n if (tag) found.push(tag);\n }\n }\n return found;\n}\n\nfunction extract(_raw: string, body: string, _search?: { title: string; summary: string }, data?: Record<string, unknown>): string[] {\n return [...new Set([...frontmatterTags(data), ...inlineTags(body)])].sort();\n}\n\nexport const tags: Feature = {\n name: 'tags',\n schema(db) {\n db.exec('CREATE TABLE IF NOT EXISTS tags (\"path\" TEXT, tag TEXT, PRIMARY KEY (\"path\", tag))');\n db.exec('CREATE INDEX IF NOT EXISTS tags_tag ON tags(tag)');\n },\n extract,\n // Per-file rows with nothing else to resolve, so only a vanished file needs a delete here;\n // store() below handles a reparse's stale rows itself.\n remove(db, path, delta) {\n if (!delta.vanished.includes(path)) return;\n db.prepare('DELETE FROM tags WHERE \"path\" = ?').run(path);\n },\n store(db, path, extracted) {\n const found = extracted as string[];\n if (found.length === 0) {\n db.prepare('DELETE FROM tags WHERE \"path\" = ?').run(path);\n } else {\n const placeholders = found.map(() => '?').join(', ');\n db.prepare(`DELETE FROM tags WHERE \"path\" = ? AND tag NOT IN (${placeholders})`).run(path, ...found);\n }\n const insert = db.prepare('INSERT OR IGNORE INTO tags (\"path\", tag) VALUES (?, ?)');\n for (const tag of found) insert.run(path, tag);\n },\n};\n"],"names":["tags","WIKILINK_RE","HTML_TAG_RE","INLINE_TAG_RE","LINK_DEST_RE","HTML_BLOCK_TAGS","Set","HTML_PRE_TAGS","HTML_BLOCK_OPEN_RE","normalizeTag","raw","stripped","replace","test","frontmatterTags","data","list","Array","isArray","found","item","tag","push","inlineTags","body","fence","fenceTracker","comment","commentTracker","inHtmlBlock","htmlBlockClose","split","line","inComment","feed","inFence","masked","includes","mask","openMatch","exec","tagName","toLowerCase","isClosingTag","has","cleaned","maskCodeSpans","m","repeat","length","matchAll","extract","_raw","_search","sort","name","schema","db","remove","path","delta","vanished","prepare","run","store","extracted","placeholders","map","join","insert"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface FenceTracker {
2
+ feed(line: string): boolean;
3
+ readonly inFence: boolean;
4
+ }
5
+ export declare function fenceTracker(): FenceTracker;
6
+ export interface CommentTracker {
7
+ mask(line: string): string;
8
+ readonly inComment: boolean;
9
+ }
10
+ export declare function commentTracker(): CommentTracker;
11
+ export declare function maskRegions(body: string): string;
12
+ export declare function maskCodeSpans(line: string): string;