@mnstry/atelier 0.2.0-alpha.10 → 0.2.0-alpha.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +385 -0
- package/README.md +10 -6
- package/contracts/atelier-obsidian-generation-manifest.v2.schema.json +372 -0
- package/contracts/compat-baseline.json +1 -1
- package/docs/assurance-controls.md +3 -2
- package/docs/install.md +3 -3
- package/docs/local-services.md +62 -11
- package/docs/obsidian-contract.md +681 -39
- package/docs/obsidian-plugin.md +585 -0
- package/docs/obsidian.md +292 -48
- package/docs/release-engineering.md +7 -1
- package/docs/upgrade.md +32 -3
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/case-only-collision.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/flat-note-path.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/hidden-folder.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/identity-in-the-body.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/link-breaking-name.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-identity-region.v2.json +156 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-layout-version.v2.json +159 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/note-named-like-a-folder.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/unknown-region-kind.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/v1-contract-version.v2.json +161 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/valid/three-notes.v2.json +160 -0
- package/fixtures/obsidian/edits/workspace.json +1 -1
- package/fixtures/obsidian/materialization/expected-full.json +21 -21
- package/fixtures/obsidian/materialization/expected-full.layout-1.json +99 -0
- package/fixtures/obsidian/materialization/expected-scoped.json +7 -7
- package/fixtures/obsidian/materialization/expected-scoped.layout-1.json +39 -0
- package/package.json +7 -3
- package/plugins/obsidian/main.js +444 -0
- package/plugins/obsidian/manifest.json +9 -0
- package/plugins/obsidian/styles.css +22 -0
- package/src/commands/obsidian.mjs +89 -10
- package/src/contracts/corpus.mjs +6 -0
- package/src/egress/forbidden-egress.mjs +2 -0
- package/src/project/private-state.mjs +10 -1
- package/src/projection/obsidian/contracts.mjs +224 -18
- package/src/projection/obsidian/edits/apply.mjs +12 -7
- package/src/projection/obsidian/edits/regions.mjs +137 -42
- package/src/projection/obsidian/materialize/index.mjs +6 -3
- package/src/projection/obsidian/materialize/path-registry.mjs +443 -57
- package/src/projection/obsidian/materialize/prepare-view.mjs +382 -131
- package/src/projection/obsidian/materialize/redaction.mjs +327 -0
- package/src/projection/obsidian/materialize/settings.mjs +113 -34
- package/src/projection/obsidian/plugin-bridge/bundle.mjs +50 -0
- package/src/projection/obsidian/plugin-bridge/channel.mjs +174 -0
- package/src/projection/obsidian/plugin-bridge/index.mjs +10 -0
- package/src/projection/obsidian/proposals/observation.mjs +10 -5
- package/src/projection/obsidian/publication/bridge-script.mjs +11 -5
- package/src/projection/obsidian/publication/index.mjs +2 -1
- package/src/projection/obsidian/publication/publisher.mjs +173 -26
- package/src/projection/obsidian/publication/transport.mjs +124 -17
- package/src/projection/obsidian/publication/vault-list.mjs +199 -0
- package/src/projection/obsidian/recovery/restart.mjs +3 -1
- package/src/projection/obsidian/recovery/store.mjs +11 -4
- package/src/projection/obsidian/selection-ui/focus.mjs +14 -7
- package/src/projection/obsidian/selection-ui/selection-state.mjs +2 -2
- package/src/projection/obsidian/selection-ui/selection.mjs +8 -3
- package/src/runtime/obsidian/app-capability.mjs +115 -14
- package/src/runtime/obsidian/app-production-seams.mjs +126 -37
- package/src/runtime/obsidian/app-registration.mjs +166 -0
- package/src/runtime/obsidian/engine.mjs +127 -11
- package/src/runtime/obsidian/extension-points.mjs +1 -1
- package/src/runtime/obsidian/index.mjs +5 -1
- package/src/runtime/obsidian/launch-plan.mjs +88 -0
- package/src/runtime/obsidian/lifecycle.mjs +105 -14
- package/src/runtime/obsidian/opening.mjs +240 -51
- package/src/runtime/obsidian/pending-edits.mjs +10 -0
- package/src/runtime/obsidian/plugin-channel.mjs +281 -0
- package/src/runtime/obsidian/plugin-choice.mjs +170 -0
- package/src/runtime/obsidian/plugin-drift.mjs +76 -0
- package/src/runtime/obsidian/plugin-presence.mjs +48 -0
- package/src/runtime/obsidian/service-main.mjs +19 -4
- package/src/runtime/obsidian/service-record.mjs +36 -0
- package/src/runtime/obsidian/service-server.mjs +50 -12
- package/src/runtime/obsidian/service.mjs +100 -6
- package/src/runtime/obsidian/state-store.mjs +13 -1
- package/src/server/local-sidecar.mjs +24 -5
- package/src/server/server.mjs +18 -5
- package/src/upgrade/upgrade.mjs +1 -1
- package/templates/external-project-workspace/pack-lifecycle.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { scanMarkdownLinks } from '../../../graph/knowledge-graph.mjs'
|
|
2
|
-
import { OBSIDIAN_EXT_KEY, ObsidianContractRefusal } from '../contracts.mjs'
|
|
2
|
+
import { OBSIDIAN_EXT_KEY, ObsidianContractRefusal, manifestLayoutVersion } from '../contracts.mjs'
|
|
3
3
|
import { readMarkdownLens, sha256Digest } from '../materialize/byte-lens.mjs'
|
|
4
4
|
import { alignBodies, commonPrefixLength, commonSubsequenceLength, commonSuffixLength } from './align.mjs'
|
|
5
5
|
|
|
@@ -13,7 +13,8 @@ import { alignBodies, commonPrefixLength, commonSubsequenceLength, commonSuffixL
|
|
|
13
13
|
// A note is three byte classes, in order:
|
|
14
14
|
//
|
|
15
15
|
// prefix bytes before regions.body.start: the source's byte order prefix
|
|
16
|
-
// or front matter, verbatim
|
|
16
|
+
// or front matter, verbatim, and in layout 2 the generated
|
|
17
|
+
// identity lines (regions.identity) inside or as that front matter
|
|
17
18
|
// authored the body, with canonical link and embed targets rewritten; each
|
|
18
19
|
// rewrite is recorded as an inversion
|
|
19
20
|
// generated relations, outside-selection rows and, before them, the closing
|
|
@@ -221,26 +222,41 @@ export function splitGeneratedTail({ noteEntry, publishedNoteBytes, editedNoteBy
|
|
|
221
222
|
// Prefix
|
|
222
223
|
// ---------------------------------------------------------------------------
|
|
223
224
|
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const prefix = publishedNoteBytes.subarray(0,
|
|
225
|
+
// The note's prefix as the source holds it: the published prefix without the
|
|
226
|
+
// generated identity lines, and whether the source has front matter of its own.
|
|
227
|
+
export function sourcePrefixOf({ noteEntry, publishedNoteBytes }) {
|
|
228
|
+
const bodyStart = noteEntry.regions.body.start
|
|
229
|
+
const prefix = publishedNoteBytes.subarray(0, bodyStart)
|
|
230
|
+
const identity = noteEntry.regions.identity ?? null
|
|
229
231
|
const frontmatter = noteEntry.regions.frontmatter ?? null
|
|
232
|
+
if (identity === null || identity.end > bodyStart) return { prefix, sourcePrefix: prefix, hasFrontmatter: frontmatter !== null }
|
|
233
|
+
const sourcePrefix = Buffer.concat([prefix.subarray(0, identity.start), prefix.subarray(identity.end)])
|
|
234
|
+
// A front matter that is the identity block and nothing else was generated for a source that has none.
|
|
235
|
+
const generatedWhole = frontmatter !== null && frontmatter.start === identity.start && frontmatter.end === identity.end
|
|
236
|
+
return { prefix, sourcePrefix, hasFrontmatter: frontmatter !== null && !generatedWhole }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Returns the edited authored BODY. The prefix must be byte-identical, the
|
|
240
|
+
// identity lines included: a change to them, as to any front matter, is not a
|
|
241
|
+
// body replacement. A byte order prefix is the one exception: it belongs to
|
|
242
|
+
// the source and is kept from the source whether or not the editor kept it in
|
|
243
|
+
// the note.
|
|
244
|
+
export function splitPrefix({ noteEntry, publishedNoteBytes, editedAuthored }) {
|
|
245
|
+
const { prefix, sourcePrefix, hasFrontmatter } = sourcePrefixOf({ noteEntry, publishedNoteBytes })
|
|
230
246
|
let body
|
|
231
|
-
if (frontmatter ===
|
|
247
|
+
if (noteEntry.regions.frontmatter === undefined) {
|
|
232
248
|
body = startsWith(editedAuthored, UTF8_BOM) ? editedAuthored.subarray(UTF8_BOM.length) : editedAuthored
|
|
233
249
|
} else {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
body
|
|
250
|
+
const withoutBom = startsWith(prefix, UTF8_BOM) ? prefix.subarray(UTF8_BOM.length) : null
|
|
251
|
+
if (startsWith(editedAuthored, prefix)) body = editedAuthored.subarray(prefix.length)
|
|
252
|
+
else if (withoutBom !== null && startsWith(editedAuthored, withoutBom)) body = editedAuthored.subarray(withoutBom.length)
|
|
253
|
+
else refuse('unsupported-frontmatter-edit', 'the front matter of the note was edited; only the body can be replaced', { firstDifference: commonPrefixLength(editedAuthored, prefix) })
|
|
238
254
|
}
|
|
239
255
|
// The body must not read as front matter of its own, and the front matter
|
|
240
256
|
// must still end where it did, as the source lens reads the result.
|
|
241
257
|
let lens = null
|
|
242
|
-
try { lens = readMarkdownLens(Buffer.concat([
|
|
243
|
-
const expectedEnd =
|
|
258
|
+
try { lens = readMarkdownLens(Buffer.concat([hasFrontmatter ? sourcePrefix : Buffer.alloc(0), body])) } catch { /* refused below */ }
|
|
259
|
+
const expectedEnd = hasFrontmatter ? sourcePrefix.length : null
|
|
244
260
|
if (lens === null || (lens.frontmatter?.end ?? null) !== expectedEnd) {
|
|
245
261
|
refuse('unsupported-frontmatter-edit', 'the edit changes where the front matter of the note begins or ends; only the body can be replaced')
|
|
246
262
|
}
|
|
@@ -404,11 +420,53 @@ function assertCoherentPlacements({ units, placements, publishedBody, editedBody
|
|
|
404
420
|
// The final invariant, independent of how the alignment went: every rewrite
|
|
405
421
|
// of the published body is either substituted (intact) or gone (deleted), and
|
|
406
422
|
// what is left of the edited body, which becomes source bytes verbatim, holds
|
|
407
|
-
// no vault identity that the published authored text did not already hold
|
|
408
|
-
//
|
|
409
|
-
//
|
|
423
|
+
// no vault identity that the published authored text did not already hold,
|
|
424
|
+
// wherever it appears: in a link, in code or in plain prose. In layout 1 a
|
|
425
|
+
// vault identity is the `--<hex>` suffix of a note or attachment path of this
|
|
426
|
+
// view; in layout 2, where names are titles, it is the full vault path of one,
|
|
427
|
+
// as written or percent-encoded, with or without `.md`.
|
|
410
428
|
const IDENTITY = /--[0-9a-f]{12,64}(?![0-9a-f])/g
|
|
411
429
|
|
|
430
|
+
const encodeSegment = (segment) => encodeURIComponent(segment).replace(/[()']/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`)
|
|
431
|
+
const latin1Of = (text) => Buffer.from(text, 'utf8').toString('latin1')
|
|
432
|
+
const vaultFilesOf = (manifest) => [...manifest.notes.map((note) => note.path), ...manifest.attachments.map((attachment) => attachment.path)]
|
|
433
|
+
|
|
434
|
+
// Returns (latin1 text) => [{ identity, index, length }].
|
|
435
|
+
function vaultIdentityFinder(manifest) {
|
|
436
|
+
if (manifestLayoutVersion(manifest) === 1) {
|
|
437
|
+
const known = new Set()
|
|
438
|
+
for (const filePath of vaultFilesOf(manifest)) for (const match of filePath.matchAll(IDENTITY)) known.add(match[0])
|
|
439
|
+
return (text) => [...text.matchAll(IDENTITY)].filter((match) => known.has(match[0])).map((match) => ({ identity: match[0], index: match.index, length: match[0].length }))
|
|
440
|
+
}
|
|
441
|
+
// Spellings in latin1, so an index is a byte offset. Every path starts with a
|
|
442
|
+
// repository folder, so only those need to be looked for.
|
|
443
|
+
const spellings = new Set()
|
|
444
|
+
const starts = new Set()
|
|
445
|
+
let longest = 0
|
|
446
|
+
for (const filePath of vaultFilesOf(manifest)) {
|
|
447
|
+
const forms = [filePath, filePath.split('/').map(encodeSegment).join('/')]
|
|
448
|
+
for (const form of forms) {
|
|
449
|
+
for (const spelling of form.endsWith('.md') ? [form, form.slice(0, -'.md'.length)] : [form]) {
|
|
450
|
+
const bytes = latin1Of(spelling)
|
|
451
|
+
spellings.add(bytes)
|
|
452
|
+
longest = Math.max(longest, bytes.length)
|
|
453
|
+
}
|
|
454
|
+
starts.add(latin1Of(`${form.split('/')[0]}/`))
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return (text) => {
|
|
458
|
+
const found = []
|
|
459
|
+
for (const start of starts) {
|
|
460
|
+
for (let at = text.indexOf(start); at !== -1; at = text.indexOf(start, at + 1)) {
|
|
461
|
+
let match = null
|
|
462
|
+
for (let end = at + start.length + 1; end <= Math.min(text.length, at + longest); end += 1) if (spellings.has(text.slice(at, end))) match = end
|
|
463
|
+
if (match !== null) found.push({ identity: text.slice(at, match), index: at, length: match - at })
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return found
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
412
470
|
export function assertRewritesAccounted({ manifest, units, placements, publishedBody, editedBody }) {
|
|
413
471
|
if (placements.length !== units.length || placements.some((placement) => placement.state !== 'intact' && placement.state !== 'deleted')) {
|
|
414
472
|
refuse('unsupported-structural-edit', 'a rewritten link is neither kept nor deleted', { reason: 'rewrite-unaccounted' })
|
|
@@ -418,13 +476,12 @@ export function assertRewritesAccounted({ manifest, units, placements, published
|
|
|
418
476
|
refuse('unsupported-structural-edit', 'a rewritten link is not where it was placed', { reason: 'rewrite-unaccounted' })
|
|
419
477
|
}
|
|
420
478
|
})
|
|
421
|
-
const
|
|
422
|
-
for (const item of [...manifest.notes, ...manifest.attachments]) for (const match of item.path.matchAll(IDENTITY)) known.add(match[0])
|
|
479
|
+
const find = vaultIdentityFinder(manifest)
|
|
423
480
|
const identities = (body, holes) => {
|
|
424
481
|
const found = []
|
|
425
482
|
let cursor = 0
|
|
426
483
|
for (const [start, end] of [...holes, [body.length, body.length]]) {
|
|
427
|
-
for (const
|
|
484
|
+
for (const item of find(body.toString('latin1', cursor, start))) found.push({ identity: item.identity, start: cursor + item.index, end: cursor + item.index + item.length })
|
|
428
485
|
cursor = end
|
|
429
486
|
}
|
|
430
487
|
return found
|
|
@@ -440,29 +497,64 @@ export function assertRewritesAccounted({ manifest, units, placements, published
|
|
|
440
497
|
}
|
|
441
498
|
|
|
442
499
|
const folded = (value) => value.normalize('NFC').toLowerCase()
|
|
500
|
+
const parentFolder = (filePath) => (filePath.includes('/') ? filePath.slice(0, filePath.lastIndexOf('/')) : '')
|
|
443
501
|
|
|
444
502
|
// Every spelling under which the view's notes and attachments can be linked
|
|
445
|
-
// from inside the vault.
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
503
|
+
// from inside the vault, as a predicate over a link target.
|
|
504
|
+
//
|
|
505
|
+
// Layout 1: a full path or a basename, with or without `.md`, or anything
|
|
506
|
+
// under `notes/` or `attachments/`.
|
|
507
|
+
//
|
|
508
|
+
// Layout 2 asks what the app would resolve: a target starting `./` or `../`
|
|
509
|
+
// is resolved against the folder of the note; then a vault file of the same
|
|
510
|
+
// name whose path is the target or ends with it, with or without `.md` (the
|
|
511
|
+
// app's exact and suffix matches). A target written from the vault root under
|
|
512
|
+
// a repository folder, where every file of the view is, names the vault even
|
|
513
|
+
// when no file answers it; a relative one that no file answers is an ordinary
|
|
514
|
+
// relative link, which means the same in the source.
|
|
515
|
+
function vaultTargets(manifest, notePath) {
|
|
516
|
+
const files = vaultFilesOf(manifest).map(folded)
|
|
517
|
+
if (manifestLayoutVersion(manifest) === 1) {
|
|
518
|
+
const targets = new Set()
|
|
519
|
+
for (const filePath of files) {
|
|
520
|
+
for (const spelling of [filePath, filePath.split('/').at(-1)]) {
|
|
521
|
+
targets.add(spelling)
|
|
522
|
+
if (spelling.endsWith('.md')) targets.add(spelling.slice(0, -'.md'.length))
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return (written) => {
|
|
526
|
+
const target = folded(written.replace(/^(?:\.\/)+/, '').trim())
|
|
527
|
+
return targets.has(target) || target.startsWith('notes/') || target.startsWith('attachments/')
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const byName = new Map()
|
|
531
|
+
for (const filePath of files) {
|
|
532
|
+
const name = filePath.split('/').at(-1)
|
|
533
|
+
if (!byName.has(name)) byName.set(name, [])
|
|
534
|
+
byName.get(name).push(filePath)
|
|
535
|
+
}
|
|
536
|
+
const topFolders = new Set(files.map((filePath) => filePath.split('/')[0]))
|
|
537
|
+
const folder = folded(parentFolder(notePath ?? ''))
|
|
538
|
+
return (written) => {
|
|
539
|
+
let target = folded(written.trim())
|
|
540
|
+
const relative = target.startsWith('./') || target.startsWith('../')
|
|
541
|
+
if (relative) {
|
|
542
|
+
let base = folder
|
|
543
|
+
if (target.startsWith('./../')) target = target.slice(2)
|
|
544
|
+
if (target.startsWith('./')) target = target.slice(2)
|
|
545
|
+
else while (target.startsWith('../')) { target = target.slice(3); base = parentFolder(base) }
|
|
546
|
+
target = base === '' ? target : `${base}/${target}`
|
|
454
547
|
}
|
|
548
|
+
target = target.replace(/^\/+/, '')
|
|
549
|
+
if (!relative && target.includes('/') && topFolders.has(target.split('/')[0])) return true
|
|
550
|
+
return [target, `${target}.md`].some((spelling) => (byName.get(spelling.split('/').at(-1)) ?? []).some((filePath) => filePath.endsWith(spelling)))
|
|
455
551
|
}
|
|
456
|
-
return targets
|
|
457
552
|
}
|
|
458
553
|
|
|
459
|
-
function namesVaultFile(href,
|
|
554
|
+
function namesVaultFile(href, isVaultTarget) {
|
|
460
555
|
const spellings = [href]
|
|
461
556
|
try { spellings.push(decodeURIComponent(href)) } catch { /* not percent-encoded */ }
|
|
462
|
-
return spellings.some((spelling) =>
|
|
463
|
-
const target = folded(spelling.replace(/^<|>$/g, '').replace(/^(?:\.\/)+/, '').trim())
|
|
464
|
-
return targets.has(target) || target.startsWith('notes/') || target.startsWith('attachments/')
|
|
465
|
-
})
|
|
557
|
+
return spellings.some((spelling) => isVaultTarget(spelling.replace(/^<|>$/g, '')))
|
|
466
558
|
}
|
|
467
559
|
|
|
468
560
|
// Links and embeds of `bytes` (a whole authored note, so front matter is
|
|
@@ -497,8 +589,8 @@ function vaultLinkOccurrences(bytes, bodyStart, targets) {
|
|
|
497
589
|
// link in the published body must still be the whole target of a link, and an
|
|
498
590
|
// appended alias must still be what closes its link. Text typed against
|
|
499
591
|
// either one changes what the link points at once the rewrite is inverted.
|
|
500
|
-
export function findStructuralLinks({ manifest, notePrefix, publishedBody, editedBody, units, placements }) {
|
|
501
|
-
const targets = vaultTargets(manifest)
|
|
592
|
+
export function findStructuralLinks({ manifest, notePath = null, notePrefix, publishedBody, editedBody, units, placements }) {
|
|
593
|
+
const targets = vaultTargets(manifest, notePath)
|
|
502
594
|
const within = (occurrence, start, end) => start >= occurrence.targetStart && end <= occurrence.targetEnd
|
|
503
595
|
const publishedLinks = vaultLinkOccurrences(Buffer.concat([notePrefix, publishedBody]), notePrefix.length, targets)
|
|
504
596
|
const editedLinks = vaultLinkOccurrences(Buffer.concat([notePrefix, editedBody]), notePrefix.length, targets)
|
|
@@ -558,7 +650,10 @@ function lensWith(primitives, { manifest, repoId, nodeId, publishedNoteBytes, ed
|
|
|
558
650
|
const bodyStart = noteEntry.regions.body.start
|
|
559
651
|
const notePrefix = publishedNoteBytes.subarray(0, bodyStart)
|
|
560
652
|
const sourceLens = readMarkdownLens(baseSourceBytes)
|
|
561
|
-
|
|
653
|
+
// The note's prefix is the source's, with the generated identity lines where the manifest records them.
|
|
654
|
+
const { sourcePrefix } = sourcePrefixOf({ noteEntry, publishedNoteBytes })
|
|
655
|
+
const sourceBodyStart = sourceLens.body.start
|
|
656
|
+
if (sourceBodyStart !== sourcePrefix.length || sourceLens.body.end !== baseSourceBytes.length || !baseSourceBytes.subarray(0, sourceBodyStart).equals(sourcePrefix)) {
|
|
562
657
|
refuse('manifest-mismatch', 'the recorded body does not begin where the body of the base source begins')
|
|
563
658
|
}
|
|
564
659
|
const units = inversionUnits({ manifest, noteEntry, publishedNoteBytes, baseSourceBytes })
|
|
@@ -571,7 +666,7 @@ function lensWith(primitives, { manifest, repoId, nodeId, publishedNoteBytes, ed
|
|
|
571
666
|
assertCoherentPlacements({ units, placements, publishedBody, editedBody })
|
|
572
667
|
// Offsets in a refusal are offsets into the edited note.
|
|
573
668
|
const shift = tail.authored.length - editedBody.length
|
|
574
|
-
const structural = primitives.findStructuralLinks({ manifest, notePrefix: noteEntry.regions.frontmatter ? notePrefix : Buffer.alloc(0), publishedBody, editedBody, units, placements })
|
|
669
|
+
const structural = primitives.findStructuralLinks({ manifest, notePath: noteEntry.path, notePrefix: noteEntry.regions.frontmatter ? notePrefix : Buffer.alloc(0), publishedBody, editedBody, units, placements })
|
|
575
670
|
const aliasTouched = structural.find((item) => item.alias)
|
|
576
671
|
if (aliasTouched) {
|
|
577
672
|
refuse('link-rewrite-edited', 'text was added to the alias the emitter appended to a link', { noteStart: aliasTouched.bodyStart + shift, noteEnd: aliasTouched.bodyEnd + shift })
|
|
@@ -590,10 +685,10 @@ function lensWith(primitives, { manifest, repoId, nodeId, publishedNoteBytes, ed
|
|
|
590
685
|
}
|
|
591
686
|
|
|
592
687
|
// Between two surviving rewrites the edited bytes ARE the new source bytes.
|
|
593
|
-
const parts = [baseSourceBytes.subarray(0,
|
|
688
|
+
const parts = [baseSourceBytes.subarray(0, sourceBodyStart)]
|
|
594
689
|
const changedRanges = []
|
|
595
|
-
let written =
|
|
596
|
-
let sourceCursor =
|
|
690
|
+
let written = sourceBodyStart
|
|
691
|
+
let sourceCursor = sourceBodyStart
|
|
597
692
|
let editedCursor = 0
|
|
598
693
|
const segment = (editedTo, sourceTo) => {
|
|
599
694
|
const next = editedBody.subarray(editedCursor, editedTo)
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// Materialization: the pure preparation layer for an Obsidian view. Nothing
|
|
2
2
|
// here writes into a vault; publication is a separate protocol.
|
|
3
|
-
export { EMITTER_VERSION, createPreparationCache, prepareView, withEligibility } from './prepare-view.mjs'
|
|
3
|
+
export { CURRENT_VAULT_LAYOUT, EMITTER_VERSION, createPreparationCache, prepareView, withEligibility } from './prepare-view.mjs'
|
|
4
4
|
export { readMarkdownLens, assertStrictUtf8, sha256Digest } from './byte-lens.mjs'
|
|
5
|
-
export { PATH_REGISTRY_SCHEMA, allocateWorkspacePaths, collisionKey, emptyPathRegistry } from './path-registry.mjs'
|
|
6
|
-
export {
|
|
5
|
+
export { PATH_REGISTRY_SCHEMA, allocateViewPaths, allocateWorkspacePaths, collisionKey, emptyPathRegistry } from './path-registry.mjs'
|
|
6
|
+
export {
|
|
7
|
+
COMMUNITY_PLUGINS_PATH, PLUGIN_OWNED_PATHS, POLICY_DISABLED_CORE_PLUGINS, POLICY_ENABLED_COMMUNITY_PLUGINS, POLICY_SETTINGS_PATH, POLICY_SETTINGS_PATHS, SETTINGS_ROOT,
|
|
8
|
+
isPluginOwnedPath, isPolicySettingsPath, isUserOwnedSettingsPath, preparePolicySettingsFile, prepareSettings,
|
|
9
|
+
} from './settings.mjs'
|
|
7
10
|
export { stagePreparedView } from './stage.mjs'
|