@hraness/sponge 0.2.6 → 0.3.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.
- package/README.md +21 -9
- package/bin/sponge.js +8 -7
- package/dist/contracts/index.js +24 -6
- package/dist/contracts/validate.js +106 -29
- package/dist/migrations/008.d.ts +3 -0
- package/dist/migrations/008.js +5 -0
- package/dist/migrations/009.d.ts +3 -0
- package/dist/migrations/009.js +8 -0
- package/dist/migrations/010.d.ts +3 -0
- package/dist/migrations/010.js +11 -0
- package/dist/migrations/index.d.ts +15 -0
- package/dist/migrations/index.js +39 -0
- package/dist/src/acquisition/capture.js +87 -28
- package/dist/src/acquisition/cleanup.d.ts +0 -4
- package/dist/src/acquisition/cleanup.js +3 -5
- package/dist/src/acquisition/errors.js +17 -2
- package/dist/src/acquisition/files.d.ts +0 -1
- package/dist/src/acquisition/files.js +14 -11
- package/dist/src/acquisition/index.d.ts +2 -3
- package/dist/src/acquisition/index.js +1 -2
- package/dist/src/acquisition/lock.js +1 -1
- package/dist/src/acquisition/normalize.js +57 -18
- package/dist/src/acquisition/pins.js +22 -4
- package/dist/src/acquisition/process.js +67 -29
- package/dist/src/acquisition/runtime.d.ts +5 -7
- package/dist/src/acquisition/runtime.js +129 -68
- package/dist/src/acquisition/verify-helper.js +10 -3
- package/dist/src/annotations/quote.js +40 -12
- package/dist/src/annotations/store.d.ts +0 -1
- package/dist/src/annotations/store.js +66 -14
- package/dist/src/app/article.d.ts +2 -1
- package/dist/src/app/article.js +66 -70
- package/dist/src/app/bundle-files.d.ts +0 -4
- package/dist/src/app/bundle-files.js +11 -14
- package/dist/src/app/markdown.js +18 -6
- package/dist/src/app/source.d.ts +0 -4
- package/dist/src/app/source.js +18 -14
- package/dist/src/cli/arguments.d.ts +2 -8
- package/dist/src/cli/arguments.js +67 -80
- package/dist/src/cli/commands.d.ts +42 -0
- package/dist/src/cli/commands.js +795 -0
- package/dist/src/cli/config.js +12 -9
- package/dist/src/cli/digest.d.ts +1 -1
- package/dist/src/cli/digest.js +38 -16
- package/dist/src/cli/import.js +25 -4
- package/dist/src/cli/indexing.d.ts +2 -0
- package/dist/src/cli/indexing.js +26 -9
- package/dist/src/cli/main.js +47 -386
- package/dist/src/cli/portability.d.ts +0 -2
- package/dist/src/cli/portability.js +13 -8
- package/dist/src/cli/queue.js +12 -8
- package/dist/src/cli/runtimes.d.ts +1 -3
- package/dist/src/cli/runtimes.js +13 -6
- package/dist/src/cli/terminal.js +1 -1
- package/dist/src/cli/worker.d.ts +7 -0
- package/dist/src/cli/worker.js +54 -15
- package/dist/src/derivations/kinds.d.ts +34 -0
- package/dist/src/derivations/kinds.js +14 -0
- package/dist/src/derivations/types.d.ts +48 -0
- package/dist/src/derivations/types.js +1 -0
- package/dist/src/digest/derivation.d.ts +25 -0
- package/dist/src/digest/derivation.js +124 -0
- package/dist/src/digest/editorial.d.ts +9 -46
- package/dist/src/digest/editorial.js +57 -49
- package/dist/src/digest/endpoint.js +63 -27
- package/dist/src/digest/pipeline.d.ts +49 -28
- package/dist/src/digest/pipeline.js +369 -102
- package/dist/src/digest/schema.d.ts +11 -25
- package/dist/src/digest/schema.js +86 -18
- package/dist/src/errors.d.ts +46 -0
- package/dist/src/errors.js +117 -0
- package/dist/src/identity/query-policy.d.ts +0 -5
- package/dist/src/identity/query-policy.js +61 -9
- package/dist/src/identity/uri.d.ts +6 -5
- package/dist/src/identity/uri.js +37 -18
- package/dist/src/identity/visibility.d.ts +11 -1
- package/dist/src/identity/visibility.js +81 -17
- package/dist/src/importers/apply.js +104 -36
- package/dist/src/importers/custody.js +27 -9
- package/dist/src/importers/inspect.js +125 -38
- package/dist/src/importers/jungle-digest.d.ts +3 -6
- package/dist/src/importers/jungle-digest.js +196 -69
- package/dist/src/importers/jungle-store.d.ts +19 -6
- package/dist/src/importers/jungle-store.js +124 -28
- package/dist/src/importers/jungle.d.ts +14 -14
- package/dist/src/importers/jungle.js +142 -31
- package/dist/src/importers/manifest.js +57 -21
- package/dist/src/importers/verify-helper.js +18 -4
- package/dist/src/indexing/image.js +29 -10
- package/dist/src/indexing/index.d.ts +37 -6
- package/dist/src/indexing/index.js +360 -124
- package/dist/src/indexing/jobs.js +156 -47
- package/dist/src/indexing/prune-run.d.ts +2 -0
- package/dist/src/indexing/prune-run.js +69 -26
- package/dist/src/indexing/prune.d.ts +12 -3
- package/dist/src/indexing/prune.js +85 -35
- package/dist/src/indexing/runner.d.ts +2 -1
- package/dist/src/indexing/runner.js +83 -40
- package/dist/src/indexing/upgrade.d.ts +9 -0
- package/dist/src/indexing/upgrade.js +79 -0
- package/dist/src/inference/asr/bridge.js +6 -2
- package/dist/src/inference/asr/protocol.js +109 -19
- package/dist/src/inference/asr/runtime.js +58 -21
- package/dist/src/inference/asr/windows.js +9 -5
- package/dist/src/inference/embedding/index.d.ts +2 -6
- package/dist/src/inference/embedding/index.js +89 -29
- package/dist/src/ingest/attempt.d.ts +29 -0
- package/dist/src/ingest/attempt.js +55 -0
- package/dist/src/media/capture-helper.js +7 -2
- package/dist/src/media/capture.d.ts +2 -1
- package/dist/src/media/capture.js +124 -43
- package/dist/src/media/handler.d.ts +4 -2
- package/dist/src/media/handler.js +88 -70
- package/dist/src/media/transcribe.js +63 -21
- package/dist/src/metadata/article.js +49 -17
- package/dist/src/models/artifacts.d.ts +10 -8
- package/dist/src/models/artifacts.js +237 -106
- package/dist/src/models/catalog.d.ts +2 -5
- package/dist/src/models/catalog.js +94 -25
- package/dist/src/models/download.js +55 -15
- package/dist/src/models/runtime.d.ts +0 -2
- package/dist/src/models/runtime.js +69 -33
- package/dist/src/pdf/capture-helper.js +3 -1
- package/dist/src/pdf/capture.d.ts +2 -1
- package/dist/src/pdf/capture.js +95 -27
- package/dist/src/pdf/errors.js +12 -1
- package/dist/src/pdf/handler.d.ts +2 -1
- package/dist/src/pdf/handler.js +70 -71
- package/dist/src/pdf/index.d.ts +1 -3
- package/dist/src/pdf/index.js +0 -2
- package/dist/src/pdf/input.js +12 -7
- package/dist/src/pdf/normalize.js +80 -21
- package/dist/src/pdf/source-identity.js +3 -2
- package/dist/src/pdf/source.js +12 -8
- package/dist/src/pdf/verify-helper.js +15 -3
- package/dist/src/portability/backup.d.ts +2 -9
- package/dist/src/portability/backup.js +106 -22
- package/dist/src/portability/database.js +51 -17
- package/dist/src/portability/export.js +35 -9
- package/dist/src/portability/files.d.ts +1 -4
- package/dist/src/portability/files.js +127 -67
- package/dist/src/portability/gc.js +65 -22
- package/dist/src/publish/component.js +1 -1
- package/dist/src/publish/feed.d.ts +2 -2
- package/dist/src/publish/feed.js +75 -29
- package/dist/src/publish/identity.d.ts +5 -1
- package/dist/src/publish/identity.js +31 -11
- package/dist/src/publish/index.d.ts +2 -3
- package/dist/src/publish/index.js +34 -21
- package/dist/src/publish/projection.d.ts +2 -9
- package/dist/src/publish/projection.js +21 -14
- package/dist/src/search/extension.js +3 -1
- package/dist/src/search/hybrid.d.ts +3 -8
- package/dist/src/search/hybrid.js +45 -11
- package/dist/src/search/lexical.js +18 -4
- package/dist/src/search/query.js +56 -25
- package/dist/src/search/vector.js +37 -10
- package/dist/src/service/launch-agent.d.ts +5 -3
- package/dist/src/service/launch-agent.js +272 -83
- package/dist/src/service/lifetime.js +47 -14
- package/dist/src/service/loop.d.ts +3 -3
- package/dist/src/service/loop.js +47 -19
- package/dist/src/store/annotations.d.ts +32 -0
- package/dist/src/store/annotations.js +44 -0
- package/dist/src/store/backups.d.ts +18 -0
- package/dist/src/store/backups.js +110 -0
- package/dist/src/store/blobs.d.ts +24 -0
- package/dist/src/store/blobs.js +139 -0
- package/dist/src/store/changes.d.ts +5 -12
- package/dist/src/store/changes.js +108 -56
- package/dist/src/store/context.d.ts +15 -0
- package/dist/src/store/context.js +19 -0
- package/dist/src/store/derivations.d.ts +42 -0
- package/dist/src/store/derivations.js +104 -0
- package/dist/src/store/files.d.ts +0 -2
- package/dist/src/store/files.js +19 -24
- package/dist/src/store/ids.d.ts +30 -0
- package/dist/src/store/ids.js +34 -0
- package/dist/src/store/imports.d.ts +26 -0
- package/dist/src/store/imports.js +57 -0
- package/dist/src/store/index.d.ts +3 -186
- package/dist/src/store/index.js +2 -809
- package/dist/src/store/lease.d.ts +20 -0
- package/dist/src/store/lease.js +62 -0
- package/dist/src/store/library.d.ts +75 -0
- package/dist/src/store/library.js +174 -0
- package/dist/src/store/open.d.ts +7 -0
- package/dist/src/store/open.js +183 -0
- package/dist/src/store/queue.d.ts +65 -0
- package/dist/src/store/queue.js +327 -0
- package/dist/src/store/revisions.d.ts +12 -0
- package/dist/src/store/revisions.js +189 -0
- package/dist/src/store/search-index.d.ts +63 -0
- package/dist/src/store/search-index.js +190 -0
- package/dist/src/store/sync-ready.d.ts +6 -0
- package/dist/src/store/sync-ready.js +121 -0
- package/dist/src/store/types.d.ts +1 -5
- package/dist/src/store/types.js +1 -8
- package/dist/src/util/canonical.d.ts +2 -0
- package/dist/src/util/canonical.js +17 -0
- package/dist/src/util/exact.d.ts +2 -0
- package/dist/src/util/exact.js +9 -0
- package/dist/src/util/fsync.d.ts +4 -0
- package/dist/src/util/fsync.js +22 -0
- package/dist/src/util/hash.d.ts +4 -0
- package/dist/src/util/hash.js +8 -0
- package/dist/src/worker/basic.d.ts +0 -1
- package/dist/src/worker/basic.js +23 -7
- package/dist/src/worker/custody.d.ts +4 -1
- package/dist/src/worker/custody.js +111 -38
- package/dist/src/worker/engine.d.ts +2 -1
- package/dist/src/worker/engine.js +42 -20
- package/dist/src/worker/identity.js +13 -5
- package/dist/src/worker/journal.d.ts +1 -1
- package/dist/src/worker/journal.js +54 -24
- package/dist/src/worker/lock.d.ts +18 -0
- package/dist/src/worker/lock.js +196 -0
- package/dist/src/worker/runtime-gc.d.ts +4 -3
- package/dist/src/worker/runtime-gc.js +68 -41
- package/dist/src/worker/supervisor-child.js +19 -6
- package/dist/src/worker/supervisor.js +41 -8
- package/native/runtime-manifest.json +4 -4
- package/package.json +10 -2
- package/dist/src/digest/store.d.ts +0 -57
- package/dist/src/digest/store.js +0 -79
- package/dist/src/store/foundation.d.ts +0 -7
- package/dist/src/store/foundation.js +0 -7
|
@@ -6,28 +6,41 @@ import { type JungleEditorial } from './jungle-digest.js';
|
|
|
6
6
|
* The library side of `sponge import jungle`: one Jungle Reading note onto the item holding its capture.
|
|
7
7
|
* `planJungleNote` only reads; `applyJungleNote` runs the same plan inside the caller's transaction, through the logged paths.
|
|
8
8
|
*/
|
|
9
|
-
export declare const jungleNamespace = "jungle", jungleRealm = "import:jungle"
|
|
10
|
-
readonly importer: "sponge-jungle-reading";
|
|
11
|
-
readonly version: "2";
|
|
12
|
-
};
|
|
9
|
+
export declare const jungleNamespace = "jungle", jungleRealm = "import:jungle";
|
|
13
10
|
export type EditorialAction = 'new' | 'unchanged' | 'user-review-wins';
|
|
14
11
|
export type DigestAction = 'new' | 'unchanged' | 'not-convertible' | 'not-digest' | 'user-review-wins' | 'user-digest-wins';
|
|
15
12
|
export type VisibilityAction = 'set-public' | 'set-private' | 'restore-default' | 'unchanged' | 'kept-user-edit' | 'guarded' | 'default-policy';
|
|
13
|
+
/** Local captures only (no web URI of their own): `uri` records the note's `source_url`. Later notes on one capture leave it to the first (`other-note`). */
|
|
14
|
+
export type UriAction = 'new' | 'unchanged' | 'web-capture' | 'other-note' | 'no-source-url' | 'capability';
|
|
15
|
+
/**
|
|
16
|
+
* Local captures only, like `uri`: the first note's title becomes the default title (`noteTitle`) of a capture whose own
|
|
17
|
+
* title is a file name; `kept-user-edit` records it while the user's override stays shown. A web capture keeps its
|
|
18
|
+
* captured title (`web-capture`).
|
|
19
|
+
*/
|
|
20
|
+
export type TitleAction = 'set' | 'unchanged' | 'web-capture' | 'other-note' | 'kept-user-edit';
|
|
21
|
+
/** `firstNote` is false for a later note on a capture an earlier note in the same run already used; `firstNoteUri` is the URI that note records (a dry run has not written it). */
|
|
16
22
|
export interface JungleNoteInput {
|
|
17
23
|
itemId: string;
|
|
18
24
|
revisionId: string;
|
|
19
25
|
editorial: JungleEditorial;
|
|
20
26
|
digest?: DigestV2;
|
|
27
|
+
firstNote?: boolean;
|
|
28
|
+
firstNoteUri?: string;
|
|
21
29
|
}
|
|
22
30
|
export interface JunglePlan {
|
|
23
31
|
editorialId: string;
|
|
24
32
|
editorial: EditorialAction;
|
|
25
33
|
digestId?: string;
|
|
26
34
|
digest: DigestAction;
|
|
35
|
+
uri: UriAction;
|
|
36
|
+
title: TitleAction;
|
|
27
37
|
visibility: VisibilityAction;
|
|
28
38
|
}
|
|
29
|
-
/** What
|
|
30
|
-
export declare function
|
|
39
|
+
/** What a local capture's URI action would be, before or after its item exists. */
|
|
40
|
+
export declare function uriAction(local: boolean, firstNote: boolean, sourceUrl: string, recorded?: string): {
|
|
41
|
+
action: UriAction;
|
|
42
|
+
uri?: string;
|
|
43
|
+
};
|
|
31
44
|
/**
|
|
32
45
|
* Jungle's decision becomes an explicit override only while nobody else has set one: once the latest override was not
|
|
33
46
|
* written by this importer, the user's choice wins. A public decision never lifts the capability-URL or local-file guards.
|
|
@@ -1,18 +1,64 @@
|
|
|
1
|
-
import { canonicalDigest
|
|
2
|
-
import { canonicalEditorial
|
|
1
|
+
import { canonicalDigest } from '../digest/derivation.js';
|
|
2
|
+
import { canonicalEditorial } from '../digest/editorial.js';
|
|
3
|
+
import { derivationId, putDerivation, reviewDerivation } from '../store/derivations.js';
|
|
3
4
|
import { parseDigest } from '../digest/schema.js';
|
|
4
|
-
import { visibility as visibilityState, setVisibility } from '../identity/visibility.js';
|
|
5
|
-
import {
|
|
5
|
+
import { visibility as visibilityState, setVisibility, hasWebUri, metadataUri, setMetadataUri, } from '../identity/visibility.js';
|
|
6
|
+
import { isCapabilityUrl } from '../identity/query-policy.js';
|
|
7
|
+
import { isResolvable, maxUriBytes, shareableWebUri } from '../identity/uri.js';
|
|
8
|
+
import { mutate } from '../store/changes.js';
|
|
9
|
+
import { sha256 } from '../util/hash.js';
|
|
6
10
|
import { jungleTags } from './jungle-digest.js';
|
|
7
11
|
/**
|
|
8
12
|
* The library side of `sponge import jungle`: one Jungle Reading note onto the item holding its capture.
|
|
9
13
|
* `planJungleNote` only reads; `applyJungleNote` runs the same plan inside the caller's transaction, through the logged paths.
|
|
10
14
|
*/
|
|
11
|
-
export const jungleNamespace = 'jungle', jungleRealm = 'import:jungle'
|
|
15
|
+
export const jungleNamespace = 'jungle', jungleRealm = 'import:jungle';
|
|
16
|
+
const jungleProducer = { importer: 'sponge-jungle-reading', version: '2' };
|
|
12
17
|
/** Receipts of the importer's own visibility writes (local, like every import receipt), so a later user override is recognisable. */
|
|
13
18
|
const visibilityReceipts = 'jungle-visibility';
|
|
19
|
+
/**
|
|
20
|
+
* The note's `reading.source_url` as a shareable URI: http(s) only, through the same derivation as a captured locator.
|
|
21
|
+
* A capability URL (query-policy.ts) is refused outright, so its stripped form never becomes a public address either.
|
|
22
|
+
*/
|
|
23
|
+
function noteUri(sourceUrl) {
|
|
24
|
+
if (!/^https?:\/\//i.test(sourceUrl))
|
|
25
|
+
return { refused: 'no-source-url' };
|
|
26
|
+
if (isCapabilityUrl(sourceUrl))
|
|
27
|
+
return { refused: 'capability' };
|
|
28
|
+
let uri;
|
|
29
|
+
try {
|
|
30
|
+
uri = shareableWebUri(sourceUrl);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return { refused: 'no-source-url' };
|
|
34
|
+
}
|
|
35
|
+
return Buffer.byteLength(uri) <= maxUriBytes && isResolvable(uri) && !isCapabilityUrl(uri)
|
|
36
|
+
? { uri }
|
|
37
|
+
: { refused: 'no-source-url' };
|
|
38
|
+
}
|
|
39
|
+
/** What a local capture's URI action would be, before or after its item exists. */
|
|
40
|
+
export function uriAction(local, firstNote, sourceUrl, recorded) {
|
|
41
|
+
if (!firstNote)
|
|
42
|
+
return { action: 'other-note' };
|
|
43
|
+
if (!local)
|
|
44
|
+
return { action: 'web-capture' };
|
|
45
|
+
const { uri, refused } = noteUri(sourceUrl);
|
|
46
|
+
return uri ? { action: uri === recorded ? 'unchanged' : 'new', uri } : { action: refused };
|
|
47
|
+
}
|
|
48
|
+
function titleAction(db, input, local) {
|
|
49
|
+
if (input.firstNote === false)
|
|
50
|
+
return 'other-note';
|
|
51
|
+
if (!local)
|
|
52
|
+
return 'web-capture';
|
|
53
|
+
const title = input.editorial.title.trim(), item = db.prepare('SELECT title_note,title_override FROM items WHERE id=?').get(input.itemId);
|
|
54
|
+
if (item?.title_override !== null && item?.title_override !== undefined)
|
|
55
|
+
return 'kept-user-edit';
|
|
56
|
+
return !title || Buffer.byteLength(title) > 4096 || title === item?.title_note ? 'unchanged' : 'set';
|
|
57
|
+
}
|
|
14
58
|
/** What Jungle's `reading.status` asks for: `published` is Jungle's explicit public approval, `draft` stays private. */
|
|
15
|
-
|
|
59
|
+
function desiredVisibility(status) {
|
|
60
|
+
return status === 'published' ? 'public' : status === 'draft' ? 'private' : undefined;
|
|
61
|
+
}
|
|
16
62
|
/**
|
|
17
63
|
* Jungle's decision becomes an explicit override only while nobody else has set one: once the latest override was not
|
|
18
64
|
* written by this importer, the user's choice wins. A public decision never lifts the capability-URL or local-file guards.
|
|
@@ -30,45 +76,73 @@ export function visibilityAction(state, status, userOverride) {
|
|
|
30
76
|
return { action: 'unchanged' };
|
|
31
77
|
return { action: target === null ? 'restore-default' : target === 'public' ? 'set-public' : 'set-private', target };
|
|
32
78
|
}
|
|
33
|
-
const allAccepted = (o) => ({
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
79
|
+
const allAccepted = (o) => ({
|
|
80
|
+
gist: 'accepted',
|
|
81
|
+
ideas: o.ideas.map(() => 'accepted'),
|
|
82
|
+
quotes: o.ideas.map((i) => i.quotes.map(() => 'accepted')),
|
|
83
|
+
});
|
|
84
|
+
const imported = (r) => {
|
|
85
|
+
try {
|
|
86
|
+
return JSON.parse(String(r.producer_json))?.importer === jungleProducer.importer;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const latestVisibility = (db, itemId) => db
|
|
93
|
+
.prepare("SELECT id FROM changes WHERE entity='item' AND entity_id=? AND field='visibility' ORDER BY seq DESC LIMIT 1")
|
|
94
|
+
.get(itemId);
|
|
41
95
|
/** The latest logged visibility write was not this importer's: the user chose, even when the choice was `default`. */
|
|
42
96
|
function userOverride(db, itemId) {
|
|
43
97
|
const latest = latestVisibility(db, itemId);
|
|
44
98
|
if (!latest)
|
|
45
99
|
return (db.prepare('SELECT visibility FROM items WHERE id=?').get(itemId)?.visibility ?? null) !== null;
|
|
46
|
-
return !db
|
|
100
|
+
return !db
|
|
101
|
+
.prepare('SELECT 1 FROM import_receipts WHERE namespace=? AND source_key=? AND source_hash=?')
|
|
102
|
+
.get(visibilityReceipts, itemId, sha256(String(latest.id)));
|
|
47
103
|
}
|
|
48
104
|
export function planJungleNote(db, input) {
|
|
49
105
|
const { itemId, revisionId, editorial } = input, editorialId = derivationId(revisionId, 'editorial', editorial.markdownSha256, canonicalEditorial(editorial));
|
|
50
|
-
const editorials = db
|
|
51
|
-
|
|
106
|
+
const editorials = db
|
|
107
|
+
.prepare("SELECT id,review_json FROM derivations WHERE item_id=? AND kind='editorial'")
|
|
108
|
+
.all(itemId);
|
|
109
|
+
const editorialAction = editorials.some((r) => r.id === editorialId)
|
|
110
|
+
? 'unchanged'
|
|
111
|
+
: editorials.some((r) => JSON.parse(String(r.review_json)).note !== 'accepted')
|
|
112
|
+
? 'user-review-wins'
|
|
113
|
+
: 'new';
|
|
52
114
|
// Any earlier digest of the item, v1 included, takes part: an unchanged imported one is superseded by the v2 import, a user's is never.
|
|
53
115
|
let action = 'not-digest', digestId;
|
|
54
116
|
if (editorial.form === 'digest' && !input.digest)
|
|
55
117
|
action = 'not-convertible';
|
|
56
118
|
else if (input.digest) {
|
|
57
|
-
const digests = db
|
|
119
|
+
const digests = db
|
|
120
|
+
.prepare("SELECT id,producer_json,output_json,review_json FROM derivations WHERE item_id=? AND kind='digest'")
|
|
121
|
+
.all(itemId), parsed = parseDigest(input.digest).digest;
|
|
58
122
|
digestId = parsed && derivationId(revisionId, 'digest', editorialId, canonicalDigest(parsed));
|
|
59
123
|
if (!parsed || parsed.schemaVersion !== 2)
|
|
60
124
|
action = 'not-convertible';
|
|
61
|
-
else if (digests.some(r => r.id === digestId))
|
|
125
|
+
else if (digests.some((r) => r.id === digestId))
|
|
62
126
|
action = 'unchanged';
|
|
63
|
-
else if (digests.some(r => !imported(r)))
|
|
127
|
+
else if (digests.some((r) => !imported(r)))
|
|
64
128
|
action = 'user-digest-wins';
|
|
65
|
-
else if (digests.some(r => JSON.stringify(JSON.parse(String(r.review_json))) !==
|
|
129
|
+
else if (digests.some((r) => JSON.stringify(JSON.parse(String(r.review_json))) !==
|
|
130
|
+
JSON.stringify(allAccepted(JSON.parse(String(r.output_json))))))
|
|
66
131
|
action = 'user-review-wins';
|
|
67
132
|
else
|
|
68
133
|
action = editorialAction === 'user-review-wins' ? 'user-review-wins' : 'new';
|
|
69
134
|
}
|
|
70
|
-
const
|
|
71
|
-
|
|
135
|
+
const local = !hasWebUri(db, itemId), uri = uriAction(local, input.firstNote !== false, editorial.sourceUrl, metadataUri(db, itemId));
|
|
136
|
+
const visibility = visibilityAction(visibilityState(db, itemId, uri.uri ?? (input.firstNote === false ? input.firstNoteUri : undefined) ?? metadataUri(db, itemId)), editorial.status, userOverride(db, itemId)).action;
|
|
137
|
+
return {
|
|
138
|
+
editorialId,
|
|
139
|
+
editorial: editorialAction,
|
|
140
|
+
...(digestId ? { digestId } : {}),
|
|
141
|
+
digest: action,
|
|
142
|
+
uri: uri.action,
|
|
143
|
+
title: titleAction(db, input, local),
|
|
144
|
+
visibility,
|
|
145
|
+
};
|
|
72
146
|
}
|
|
73
147
|
/**
|
|
74
148
|
* Apply one note; the caller holds the write transaction. A new editorial object is accepted through `review.note` unless
|
|
@@ -78,19 +152,41 @@ export function planJungleNote(db, input) {
|
|
|
78
152
|
export function applyJungleNote(db, input) {
|
|
79
153
|
const plan = planJungleNote(db, input), producer = { ...jungleProducer, note: input.editorial.slug };
|
|
80
154
|
if (plan.editorial !== 'unchanged') {
|
|
81
|
-
|
|
155
|
+
putDerivation(db, 'editorial', {
|
|
156
|
+
itemId: input.itemId,
|
|
157
|
+
revisionId: input.revisionId,
|
|
158
|
+
producer,
|
|
159
|
+
output: input.editorial,
|
|
160
|
+
});
|
|
82
161
|
if (plan.editorial === 'new')
|
|
83
|
-
|
|
162
|
+
reviewDerivation(db, 'editorial', plan.editorialId, 'accepted');
|
|
84
163
|
}
|
|
164
|
+
// A local capture takes the note's web address and title before visibility is decided, so the published state applies to it.
|
|
165
|
+
if (plan.uri === 'new')
|
|
166
|
+
setMetadataUri(db, input.itemId, uriAction(true, true, input.editorial.sourceUrl).uri);
|
|
167
|
+
// A local capture's default title is the note's; a refresh changes only the captured title, and the user's override wins over both.
|
|
168
|
+
const title = input.editorial.title.trim();
|
|
169
|
+
if ((plan.title === 'set' || plan.title === 'kept-user-edit') &&
|
|
170
|
+
title &&
|
|
171
|
+
Buffer.byteLength(title) <= 4096 &&
|
|
172
|
+
title !== db.prepare('SELECT title_note FROM items WHERE id=?').get(input.itemId)?.title_note)
|
|
173
|
+
mutate(db, 'item', input.itemId, 'noteTitle', title);
|
|
85
174
|
if (plan.digest === 'new') {
|
|
86
|
-
const stored =
|
|
87
|
-
|
|
175
|
+
const stored = putDerivation(db, 'digest', {
|
|
176
|
+
itemId: input.itemId,
|
|
177
|
+
revisionId: input.revisionId,
|
|
178
|
+
producer: { ...producer, editorial: plan.editorialId },
|
|
179
|
+
inputHash: plan.editorialId,
|
|
180
|
+
output: input.digest,
|
|
181
|
+
options: { tags: jungleTags },
|
|
182
|
+
});
|
|
183
|
+
reviewDerivation(db, 'digest', stored.id, allAccepted(input.digest));
|
|
88
184
|
}
|
|
89
185
|
const action = visibilityAction(visibilityState(db, input.itemId), input.editorial.status, userOverride(db, input.itemId));
|
|
90
186
|
if (action.target !== undefined) {
|
|
91
187
|
setVisibility(db, input.itemId, action.target);
|
|
92
188
|
const latest = latestVisibility(db, input.itemId);
|
|
93
|
-
db.prepare('INSERT OR IGNORE INTO import_receipts VALUES(?,?,?,?,?,?)').run(visibilityReceipts, input.itemId,
|
|
189
|
+
db.prepare('INSERT OR IGNORE INTO import_receipts VALUES(?,?,?,?,?,?)').run(visibilityReceipts, input.itemId, sha256(String(latest.id)), jungleProducer.version, input.itemId, 'visibility');
|
|
94
190
|
}
|
|
95
191
|
return plan;
|
|
96
192
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { LibraryStore } from '../store/index.js';
|
|
2
|
-
import { type
|
|
3
|
-
import { type
|
|
4
|
-
import { type DigestAction, type EditorialAction, type VisibilityAction } from './jungle-store.js';
|
|
2
|
+
import { type QuoteDrop } from './jungle-digest.js';
|
|
3
|
+
import { type DigestAction, type EditorialAction, type TitleAction, type UriAction, type VisibilityAction } from './jungle-store.js';
|
|
5
4
|
import type { BundleVerifier } from './inspect.js';
|
|
6
5
|
/**
|
|
7
6
|
* `sponge import jungle DIR`: every Jungle Reading note (`kb/notes/reading/<slug>.md`) onto the item holding its capture.
|
|
8
7
|
* The capture goes through the verified bundle import (always as a private association); the note becomes an accepted
|
|
9
8
|
* editorial derivation, plus an accepted Digest v2 when it converts without altering owner text (non-verbatim, short and
|
|
10
|
-
* overflowing quotes are dropped and reported);
|
|
9
|
+
* overflowing quotes are dropped and reported); a local capture (no web URI of its own) takes the note's `source_url` and its
|
|
10
|
+
* title as the default title (a user override still wins), while a web capture keeps its captured title;
|
|
11
|
+
* `reading.status` becomes the item's visibility. Jungle is only read. A dry run reads the notes, captures and library and writes nothing.
|
|
11
12
|
*/
|
|
12
13
|
export interface JungleImportOptions {
|
|
13
14
|
verify?: BundleVerifier;
|
|
@@ -16,16 +17,16 @@ export interface JungleImportOptions {
|
|
|
16
17
|
offset?: number;
|
|
17
18
|
signal?: AbortSignal;
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
+
type JungleSkip = 'not-reading-note' | 'malformed-note' | 'no-capture' | 'capture-missing' | 'item-deleted' | 'capture-unsupported' | 'capture-failed';
|
|
20
21
|
/** Per note: quotes assigned to ideas, and each dropped quote by its 0-based position in the note's Quotes section. */
|
|
21
|
-
|
|
22
|
+
interface JungleQuoteReport {
|
|
22
23
|
assigned: number;
|
|
23
24
|
dropped: {
|
|
24
25
|
quote: number;
|
|
25
26
|
reason: QuoteDrop;
|
|
26
27
|
}[];
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
+
interface JungleEntry {
|
|
29
30
|
slug: string;
|
|
30
31
|
form?: string;
|
|
31
32
|
status?: string;
|
|
@@ -35,11 +36,13 @@ export interface JungleEntry {
|
|
|
35
36
|
digest?: DigestAction;
|
|
36
37
|
digestIssues?: string[];
|
|
37
38
|
quotes?: JungleQuoteReport;
|
|
39
|
+
uri?: UriAction;
|
|
40
|
+
title?: TitleAction;
|
|
38
41
|
visibility?: VisibilityAction;
|
|
39
42
|
skipped?: JungleSkip;
|
|
40
43
|
code?: string;
|
|
41
44
|
}
|
|
42
|
-
/** Schema 2: digest actions are Digest v2 (`digest`, `digestIssues`; counts add notes per reason and quotes assigned or dropped); schema 1 reported Digest v1 as `v1`. */
|
|
45
|
+
/** Schema 2: digest actions are Digest v2 (`digest`, `digestIssues`; counts add notes per reason and quotes assigned or dropped); schema 1 reported Digest v1 as `v1`. `uri` and `title` were added within schema 2. */
|
|
43
46
|
export interface JungleImportReport {
|
|
44
47
|
schemaVersion: 2;
|
|
45
48
|
dryRun: boolean;
|
|
@@ -55,16 +58,13 @@ export interface JungleImportReport {
|
|
|
55
58
|
assigned: number;
|
|
56
59
|
dropped: Record<string, number>;
|
|
57
60
|
};
|
|
61
|
+
uri: Record<string, number>;
|
|
62
|
+
title: Record<string, number>;
|
|
58
63
|
visibility: Record<string, number>;
|
|
59
64
|
skipped: Record<string, number>;
|
|
60
65
|
};
|
|
61
66
|
entries: JungleEntry[];
|
|
62
67
|
nextOffset: number | null;
|
|
63
68
|
}
|
|
64
|
-
/** Digest v2 when the note converts and passes every rule against this revision's Markdown; otherwise the reasons. */
|
|
65
|
-
export declare function jungleDigest(editorial: JungleEditorial, markdown: string): {
|
|
66
|
-
digest?: DigestV2;
|
|
67
|
-
issues: string[];
|
|
68
|
-
quotes?: JungleQuoteReport;
|
|
69
|
-
};
|
|
70
69
|
export declare function importJungle(store: LibraryStore, jungle: string, options?: JungleImportOptions): Promise<JungleImportReport>;
|
|
70
|
+
export {};
|
|
@@ -1,48 +1,107 @@
|
|
|
1
1
|
import { readdir, realpath } from 'node:fs/promises';
|
|
2
2
|
import { dirname, isAbsolute, join } from 'node:path';
|
|
3
|
-
import { readConfined
|
|
3
|
+
import { readConfined } from '../acquisition/files.js';
|
|
4
|
+
import { sha256 as hash } from '../util/hash.js';
|
|
4
5
|
import { object } from '../acquisition/normalize.js';
|
|
5
6
|
import { localizeMarkdown } from '../app/markdown.js';
|
|
6
7
|
import { checkDigest } from '../digest/schema.js';
|
|
7
8
|
import { accessClass, defaultVisibility } from '../identity/uri.js';
|
|
8
|
-
import { convertJungleDigest, parseJungleEditorial, jungleTags,
|
|
9
|
-
import { jungleNamespace, jungleRealm, visibilityAction } from './jungle-store.js';
|
|
9
|
+
import { convertJungleDigest, parseJungleEditorial, jungleTags, } from './jungle-digest.js';
|
|
10
|
+
import { jungleNamespace, jungleRealm, uriAction, visibilityAction, } from './jungle-store.js';
|
|
10
11
|
import { importBundleEntry, importBundleHash } from './apply.js';
|
|
12
|
+
import { SpongeError, codeOf } from '../errors.js';
|
|
11
13
|
const noteBytes = 4 * 1024 ** 2, captureBytes = 16 * 1024 ** 2;
|
|
12
|
-
const errorCode = (error) =>
|
|
14
|
+
const errorCode = (error) => codeOf(error);
|
|
13
15
|
/** A capture link is `<dir>/<name>` under `kb/`, never escaping it; the bundle is its directory. */
|
|
14
|
-
function captureLink(link) {
|
|
15
|
-
|
|
16
|
+
function captureLink(link) {
|
|
17
|
+
if (isAbsolute(link) ||
|
|
18
|
+
link.includes('\\') ||
|
|
19
|
+
link.split('/').length < 2 ||
|
|
20
|
+
link.split('/').some((p) => !p || p === '.' || p === '..'))
|
|
21
|
+
throw new SpongeError('no-capture');
|
|
22
|
+
return link;
|
|
23
|
+
}
|
|
16
24
|
/** Digest v2 when the note converts and passes every rule against this revision's Markdown; otherwise the reasons. */
|
|
17
|
-
|
|
25
|
+
function jungleDigest(editorial, markdown) {
|
|
18
26
|
if (editorial.form !== 'digest')
|
|
19
27
|
return { issues: [] };
|
|
20
|
-
const c = convertJungleDigest(editorial.markdown, markdown), check = checkDigest(c.gold, markdown, { tags: jungleTags }), issues = [...new Set([...c.issues, ...check.issues].map(i => `${i.path}:${i.code}`))];
|
|
21
|
-
const quotes = {
|
|
28
|
+
const c = convertJungleDigest(editorial.markdown, markdown), check = checkDigest(c.gold, markdown, { tags: jungleTags }), issues = [...new Set([...c.issues, ...check.issues].map((i) => `${i.path}:${i.code}`))];
|
|
29
|
+
const quotes = {
|
|
30
|
+
assigned: c.quotes.filter((q) => q.idea !== null).length,
|
|
31
|
+
dropped: c.quotes.flatMap((q, k) => (q.dropped ? [{ quote: k, reason: q.dropped }] : [])),
|
|
32
|
+
};
|
|
22
33
|
return c.digest && check.ok ? { digest: c.digest, issues: [], quotes } : { issues, quotes };
|
|
23
34
|
}
|
|
24
35
|
/** What a verified import would store, read from the bundle without the verifier: the same localization, from the manifest's asset list. */
|
|
25
36
|
async function previewCapture(kb, bundle, link) {
|
|
37
|
+
// An untrusted bundle fails as `integrity`, exactly as apply's `inspectImportBundle` reports it.
|
|
38
|
+
try {
|
|
39
|
+
return await previewBundle(kb, bundle, link);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error instanceof SpongeError)
|
|
43
|
+
throw error;
|
|
44
|
+
throw new SpongeError('integrity', { cause: error });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function previewBundle(kb, bundle, link) {
|
|
26
48
|
const manifest = object(JSON.parse((await readConfined(kb, join(bundle, 'capture.json'), captureBytes)).toString('utf8'))), pdf = manifest.kind === 'pdf';
|
|
27
|
-
const listed = (pdf
|
|
49
|
+
const listed = (pdf
|
|
50
|
+
? Array.isArray(manifest.images)
|
|
51
|
+
? manifest.images.map((i) => object(i).asset)
|
|
52
|
+
: []
|
|
53
|
+
: Array.isArray(manifest.assets)
|
|
54
|
+
? manifest.assets
|
|
55
|
+
: []).flatMap((raw) => {
|
|
56
|
+
const a = raw && typeof raw === 'object' ? raw : {};
|
|
57
|
+
return typeof a.path === 'string' && typeof a.sha256 === 'string'
|
|
58
|
+
? [{ relativePath: a.path, sha256: a.sha256, bytes: Number(a.bytes), mediaType: String(a.mimeType) }]
|
|
59
|
+
: [];
|
|
60
|
+
});
|
|
28
61
|
const text = (await readConfined(kb, link + '.md', captureBytes)).toString('utf8'), markdown = localizeMarkdown(pdf ? text.replace(/^<!-- pdf-page: ([1-9][0-9]*) -->$/gm, '## Page $1') : text, listed).markdown;
|
|
29
62
|
const locator = pdf ? String(object(manifest.source ?? {}).requestedUrl ?? '') : String(manifest.sourceUrl ?? '');
|
|
30
63
|
return { markdown, locator };
|
|
31
64
|
}
|
|
32
65
|
export async function importJungle(store, jungle, options = {}) {
|
|
33
66
|
if (!isAbsolute(jungle))
|
|
34
|
-
throw
|
|
35
|
-
const kb = await realpath(join(jungle, 'kb')), notesDir = join(kb, 'notes', 'reading'), files = (await readdir(notesDir)).filter(f => /^[a-z0-9][a-z0-9-]*\.md$/.test(f)).sort();
|
|
67
|
+
throw new SpongeError('invalid-input');
|
|
68
|
+
const kb = await realpath(join(jungle, 'kb')), notesDir = join(kb, 'notes', 'reading'), files = (await readdir(notesDir)).filter((f) => /^[a-z0-9][a-z0-9-]*\.md$/.test(f)).sort();
|
|
36
69
|
const limit = options.limit ?? 1000, offset = options.offset ?? 0, dryRun = options.dryRun !== false;
|
|
37
|
-
if (!Number.isSafeInteger(limit) ||
|
|
38
|
-
|
|
70
|
+
if (!Number.isSafeInteger(limit) ||
|
|
71
|
+
limit < 1 ||
|
|
72
|
+
limit > 1000 ||
|
|
73
|
+
!Number.isSafeInteger(offset) ||
|
|
74
|
+
offset < 0 ||
|
|
75
|
+
offset > files.length)
|
|
76
|
+
throw new SpongeError('invalid-input');
|
|
39
77
|
if (!dryRun && !options.verify)
|
|
40
|
-
throw
|
|
41
|
-
const report = {
|
|
42
|
-
|
|
78
|
+
throw new SpongeError('runtime-missing');
|
|
79
|
+
const report = {
|
|
80
|
+
schemaVersion: 2,
|
|
81
|
+
dryRun,
|
|
82
|
+
captureVerification: dryRun ? 'deferred' : 'verified',
|
|
83
|
+
notes: files.length,
|
|
84
|
+
counts: {
|
|
85
|
+
newItems: 0,
|
|
86
|
+
existingItems: 0,
|
|
87
|
+
editorial: {},
|
|
88
|
+
digest: {},
|
|
89
|
+
digestIssues: {},
|
|
90
|
+
quotes: { assigned: 0, dropped: {} },
|
|
91
|
+
uri: {},
|
|
92
|
+
title: {},
|
|
93
|
+
visibility: {},
|
|
94
|
+
skipped: {},
|
|
95
|
+
},
|
|
96
|
+
entries: [],
|
|
97
|
+
nextOffset: offset + limit < files.length ? offset + limit : null,
|
|
98
|
+
};
|
|
99
|
+
const bump = (m, k) => {
|
|
100
|
+
m[k] = (m[k] ?? 0) + 1;
|
|
101
|
+
}, seen = new Map();
|
|
43
102
|
for (const file of files.slice(offset, offset + limit)) {
|
|
44
103
|
if (options.signal?.aborted)
|
|
45
|
-
throw
|
|
104
|
+
throw new SpongeError('cancelled');
|
|
46
105
|
const slug = file.slice(0, -3);
|
|
47
106
|
let entry = { slug };
|
|
48
107
|
try {
|
|
@@ -51,8 +110,9 @@ export async function importJungle(store, jungle, options = {}) {
|
|
|
51
110
|
editorial = parseJungleEditorial((await readConfined(notesDir, file, noteBytes)).toString('utf8'));
|
|
52
111
|
}
|
|
53
112
|
catch (error) {
|
|
54
|
-
const
|
|
55
|
-
entry.skipped =
|
|
113
|
+
const raw = codeOf(error), code = raw === 'not-digest' || raw === 'no-capture' ? raw : 'malformed';
|
|
114
|
+
entry.skipped =
|
|
115
|
+
code === 'not-digest' ? 'not-reading-note' : code === 'no-capture' ? 'no-capture' : 'malformed-note';
|
|
56
116
|
throw entry;
|
|
57
117
|
}
|
|
58
118
|
entry = { slug, form: editorial.form, status: editorial.status };
|
|
@@ -72,23 +132,57 @@ export async function importJungle(store, jungle, options = {}) {
|
|
|
72
132
|
entry.skipped = 'capture-missing';
|
|
73
133
|
throw entry;
|
|
74
134
|
}
|
|
75
|
-
const prior = store.
|
|
135
|
+
const prior = store.imports.item(jungleRealm, hash(JSON.stringify([jungleNamespace, link])));
|
|
76
136
|
if (prior?.deleted) {
|
|
77
137
|
entry.skipped = 'item-deleted';
|
|
78
138
|
entry.itemId = prior.itemId;
|
|
79
139
|
throw entry;
|
|
80
140
|
}
|
|
81
141
|
// Two notes on one capture share its item, in a dry run too.
|
|
82
|
-
|
|
83
|
-
seen.
|
|
142
|
+
// The first note on a capture decides a local capture's URI and default title; later notes see the URI it records.
|
|
143
|
+
const firstNote = !seen.has(link), firstNoteUri = seen.get(link);
|
|
144
|
+
entry.item = prior || !firstNote ? 'existing' : 'new';
|
|
145
|
+
if (firstNote)
|
|
146
|
+
seen.set(link, uriAction(true, true, editorial.sourceUrl).uri);
|
|
84
147
|
if (dryRun) {
|
|
85
148
|
if (prior?.revisionId) {
|
|
86
|
-
const markdown = store.
|
|
87
|
-
|
|
149
|
+
const markdown = store.revisions.markdown(prior.revisionId), { digest, issues, quotes } = jungleDigest(editorial, markdown), plan = store.imports.planJungleNote({
|
|
150
|
+
itemId: prior.itemId,
|
|
151
|
+
revisionId: prior.revisionId,
|
|
152
|
+
editorial,
|
|
153
|
+
...(digest ? { digest } : {}),
|
|
154
|
+
firstNote,
|
|
155
|
+
...(firstNoteUri ? { firstNoteUri } : {}),
|
|
156
|
+
});
|
|
157
|
+
Object.assign(entry, {
|
|
158
|
+
itemId: prior.itemId,
|
|
159
|
+
editorial: plan.editorial,
|
|
160
|
+
digest: plan.digest,
|
|
161
|
+
...(issues.length ? { digestIssues: issues } : {}),
|
|
162
|
+
...(quotes ? { quotes } : {}),
|
|
163
|
+
uri: plan.uri,
|
|
164
|
+
title: plan.title,
|
|
165
|
+
visibility: plan.visibility,
|
|
166
|
+
});
|
|
88
167
|
}
|
|
168
|
+
// A capture without a web locator is stored as a local file; predict the URI, title and access it would get.
|
|
89
169
|
else {
|
|
90
|
-
const preview = await previewCapture(kb, bundle, link), { digest, issues, quotes } = jungleDigest(editorial, preview.markdown), access = accessClass(jungleRealm, preview.locator);
|
|
91
|
-
Object.assign(entry, {
|
|
170
|
+
const preview = await previewCapture(kb, bundle, link), { digest, issues, quotes } = jungleDigest(editorial, preview.markdown), local = !/^https?:/i.test(preview.locator), uri = uriAction(local, firstNote, editorial.sourceUrl), access = accessClass(jungleRealm, local ? 'file:' : preview.locator, uri.uri ?? firstNoteUri), noteTitle = editorial.title.trim();
|
|
171
|
+
Object.assign(entry, {
|
|
172
|
+
editorial: 'new',
|
|
173
|
+
digest: editorial.form !== 'digest' ? 'not-digest' : digest ? 'new' : 'not-convertible',
|
|
174
|
+
...(issues.length ? { digestIssues: issues } : {}),
|
|
175
|
+
...(quotes ? { quotes } : {}),
|
|
176
|
+
uri: uri.action,
|
|
177
|
+
title: !firstNote
|
|
178
|
+
? 'other-note'
|
|
179
|
+
: !local
|
|
180
|
+
? 'web-capture'
|
|
181
|
+
: noteTitle && Buffer.byteLength(noteTitle) <= 4096
|
|
182
|
+
? 'set'
|
|
183
|
+
: 'unchanged',
|
|
184
|
+
visibility: visibilityAction({ access, default: defaultVisibility(access, 'private'), override: null }, editorial.status, false).action,
|
|
185
|
+
});
|
|
92
186
|
}
|
|
93
187
|
}
|
|
94
188
|
else {
|
|
@@ -104,19 +198,36 @@ export async function importJungle(store, jungle, options = {}) {
|
|
|
104
198
|
entry.code = code;
|
|
105
199
|
throw entry;
|
|
106
200
|
}
|
|
107
|
-
const itemId = imported.itemId, revisionId = store.
|
|
108
|
-
const plan = store.applyJungleNote({
|
|
109
|
-
|
|
201
|
+
const itemId = imported.itemId, revisionId = store.imports.revision(itemId, importBundleHash(imported.sourceHash)), markdown = store.revisions.markdown(revisionId), { digest, issues, quotes } = jungleDigest(editorial, markdown);
|
|
202
|
+
const plan = store.imports.applyJungleNote({
|
|
203
|
+
itemId,
|
|
204
|
+
revisionId,
|
|
205
|
+
editorial,
|
|
206
|
+
...(digest ? { digest } : {}),
|
|
207
|
+
firstNote,
|
|
208
|
+
});
|
|
209
|
+
Object.assign(entry, {
|
|
210
|
+
itemId,
|
|
211
|
+
editorial: plan.editorial,
|
|
212
|
+
digest: plan.digest,
|
|
213
|
+
...(issues.length ? { digestIssues: issues } : {}),
|
|
214
|
+
...(quotes ? { quotes } : {}),
|
|
215
|
+
uri: plan.uri,
|
|
216
|
+
title: plan.title,
|
|
217
|
+
visibility: plan.visibility,
|
|
218
|
+
});
|
|
110
219
|
}
|
|
111
220
|
bump(report.counts.editorial, entry.editorial);
|
|
112
221
|
bump(report.counts.digest, entry.digest);
|
|
113
|
-
for (const i of new Set((entry.digestIssues ?? []).map(i => i.replace(/\.\d+/g, '.N'))))
|
|
222
|
+
for (const i of new Set((entry.digestIssues ?? []).map((i) => i.replace(/\.\d+/g, '.N'))))
|
|
114
223
|
bump(report.counts.digestIssues, i);
|
|
115
224
|
if (entry.quotes) {
|
|
116
225
|
report.counts.quotes.assigned += entry.quotes.assigned;
|
|
117
226
|
for (const d of entry.quotes.dropped)
|
|
118
227
|
bump(report.counts.quotes.dropped, d.reason);
|
|
119
228
|
}
|
|
229
|
+
bump(report.counts.uri, entry.uri);
|
|
230
|
+
bump(report.counts.title, entry.title);
|
|
120
231
|
bump(report.counts.visibility, entry.visibility);
|
|
121
232
|
if (entry.item === 'new')
|
|
122
233
|
report.counts.newItems++;
|