@khorsheed/dsh-reader 0.2.0
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.en.md +90 -0
- package/README.i18n.yaml +6 -0
- package/README.md +90 -0
- package/cordis.patch.yml +13 -0
- package/lib/client.js +15119 -0
- package/lib/index.js +3660 -0
- package/lib/invariant.js +38 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +1738 -0
- package/lib/typert.remote-client.d.ts +82 -0
- package/lib/typert.remote-client.js +1115 -0
- package/lib/types/arxiv.d.ts +47 -0
- package/lib/types/arxiv.js +76 -0
- package/lib/types/client/ReaderPane.d.ts +71 -0
- package/lib/types/client/ReaderPane.js +3135 -0
- package/lib/types/client/contract.d.ts +254 -0
- package/lib/types/client/contract.js +1 -0
- package/lib/types/client/definition.d.ts +27 -0
- package/lib/types/client/definition.js +32 -0
- package/lib/types/client/extract-article.d.ts +102 -0
- package/lib/types/client/extract-article.js +1024 -0
- package/lib/types/client/icons.d.ts +27 -0
- package/lib/types/client/icons.js +11 -0
- package/lib/types/client/index.d.ts +34 -0
- package/lib/types/client/index.js +222 -0
- package/lib/types/client/locales.d.ts +391 -0
- package/lib/types/client/locales.js +382 -0
- package/lib/types/client/parse-rss.d.ts +69 -0
- package/lib/types/client/parse-rss.js +656 -0
- package/lib/types/client/quote.d.ts +89 -0
- package/lib/types/client/quote.js +119 -0
- package/lib/types/client/selectors.d.ts +167 -0
- package/lib/types/client/selectors.js +413 -0
- package/lib/types/client/session.d.ts +171 -0
- package/lib/types/client/session.js +223 -0
- package/lib/types/client/store.d.ts +181 -0
- package/lib/types/client/store.js +226 -0
- package/lib/types/client/translate.d.ts +379 -0
- package/lib/types/client/translate.js +873 -0
- package/lib/types/index.d.ts +48 -0
- package/lib/types/index.js +25 -0
- package/lib/types/invariant.d.ts +29 -0
- package/lib/types/invariant.js +38 -0
- package/lib/types/link-resolvers.d.ts +107 -0
- package/lib/types/link-resolvers.js +299 -0
- package/lib/types/remote.d.ts +224 -0
- package/lib/types/remote.js +275 -0
- package/lib/types/schedule.d.ts +78 -0
- package/lib/types/schedule.js +114 -0
- package/lib/types/service.d.ts +633 -0
- package/lib/types/service.js +1697 -0
- package/lib/types/store.d.ts +224 -0
- package/lib/types/store.js +916 -0
- package/lib/types/types.d.ts +710 -0
- package/lib/types/types.js +182 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +128 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-time contract between the reader's browser half and its host half.
|
|
3
|
+
*
|
|
4
|
+
* Nothing here executes: this module holds the shapes only, so a change in the
|
|
5
|
+
* Remote surface or in the injected business face breaks compilation at the
|
|
6
|
+
* components that consume them instead of at runtime.
|
|
7
|
+
*
|
|
8
|
+
* @module @khorsheed/dsh-reader/client/contract
|
|
9
|
+
*/
|
|
10
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
11
|
+
import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
|
|
12
|
+
import type { GlobalStandardProps, InjectFace, PropsLocale, PropsStore } from '@deepseek-ai/dsh-client-ui-slots';
|
|
13
|
+
import type { ReaderAddFailure, ReaderAddOutcome, ReaderAddRefusal, ReaderAnnotationOutcome, ReaderBackfillCandidate, ReaderEntryBodyView, ReaderEntryFetchState, ReaderBody, ReaderCapabilities, ReaderEntryTranslationView, ReaderMutationOutcome, ReaderRefreshResult, ReaderRecentEntry, ReaderSentenceLearn, ReaderSourceSummary, ReaderStorageStats, ReaderTag } from '../types.ts';
|
|
14
|
+
import type { createReaderStore } from './store.ts';
|
|
15
|
+
export type { RemoteResult };
|
|
16
|
+
/** The result of extracting one article, as the detail view consumes it. */
|
|
17
|
+
export interface ReaderArticle {
|
|
18
|
+
/** Whitelisted body markup, ready to render. */
|
|
19
|
+
readonly html: string;
|
|
20
|
+
/** True when the body is known to be incomplete (size cap or partial parse). */
|
|
21
|
+
readonly truncated: boolean;
|
|
22
|
+
/** Set when nothing usable could be extracted. */
|
|
23
|
+
readonly error?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The capture package's Remote face (the ingest proposal's M1), mirrored
|
|
27
|
+
* structurally — never imported. Absent namespaces probe as undefined.
|
|
28
|
+
*/
|
|
29
|
+
export interface ReaderCaptureRemote {
|
|
30
|
+
/** Render a URL in the managed browser and hand back the serialized page. */
|
|
31
|
+
render: (request: {
|
|
32
|
+
url: string;
|
|
33
|
+
timeoutMs?: number;
|
|
34
|
+
}) => Promise<RemoteResult<{
|
|
35
|
+
html: string;
|
|
36
|
+
finalUrl?: string;
|
|
37
|
+
title?: string;
|
|
38
|
+
truncated?: boolean;
|
|
39
|
+
}>>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* What the pane's own extraction read from the article, beyond the stored body:
|
|
43
|
+
* the article's own title and a short excerpt. A saved link's card upgrades
|
|
44
|
+
* from the URL-derived label to these; a feed entry's title is already the
|
|
45
|
+
* publisher's and is never overridden.
|
|
46
|
+
*/
|
|
47
|
+
export interface ReaderExtractedMeta {
|
|
48
|
+
readonly title?: string;
|
|
49
|
+
readonly excerpt?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The business face every reader surface receives from its registration.
|
|
53
|
+
*
|
|
54
|
+
* The wire verbs come straight off the Remote; the last four are local
|
|
55
|
+
* gestures the pane cannot perform itself (they need the session scope, the
|
|
56
|
+
* clipboard, or the browser), so `client/index.ts` injects them.
|
|
57
|
+
*/
|
|
58
|
+
export interface ReaderPaneInjected {
|
|
59
|
+
/** Round-trip the capability handshake. */
|
|
60
|
+
capabilities: () => Promise<RemoteResult<ReaderCapabilities>>;
|
|
61
|
+
/** The configured sources, newest first. */
|
|
62
|
+
listSources: () => Promise<RemoteResult<{
|
|
63
|
+
sources: ReaderSourceSummary[];
|
|
64
|
+
}>>;
|
|
65
|
+
/** Add a feed or a pasted article; the host decides which by content. */
|
|
66
|
+
addSource: (url: string) => Promise<RemoteResult<ReaderAddOutcome | ReaderAddRefusal | ReaderAddFailure>>;
|
|
67
|
+
/** Change a source's label/enabled flag, or the global refresh time. */
|
|
68
|
+
updateSource: (request: {
|
|
69
|
+
id: string;
|
|
70
|
+
enabled?: boolean;
|
|
71
|
+
label?: string;
|
|
72
|
+
url?: string;
|
|
73
|
+
timeOfDay?: string;
|
|
74
|
+
}) => Promise<RemoteResult<ReaderMutationOutcome>>;
|
|
75
|
+
/** Drop a source. Its entries' translation maps go with it (the caller passes the ids it parsed). */
|
|
76
|
+
removeSource: (id: string, entryIds?: readonly string[]) => Promise<RemoteResult<ReaderMutationOutcome>>;
|
|
77
|
+
/** Fetch the named sources, or every enabled one. */
|
|
78
|
+
refresh: (ids?: string[]) => Promise<RemoteResult<{
|
|
79
|
+
results: ReaderRefreshResult[];
|
|
80
|
+
}>>;
|
|
81
|
+
/** Raw payloads, for this process to parse (the host never parses). */
|
|
82
|
+
getBodies: (ids: string[]) => Promise<RemoteResult<{
|
|
83
|
+
bodies: ReaderBody[];
|
|
84
|
+
}>>;
|
|
85
|
+
/** Offer a ref block to the side-chat service when one is composed. */
|
|
86
|
+
quoteToSideChat: (request: {
|
|
87
|
+
contextKey: string;
|
|
88
|
+
label: string;
|
|
89
|
+
text: string;
|
|
90
|
+
}) => Promise<RemoteResult<'ok' | 'unavailable'>>;
|
|
91
|
+
/** What the plugin holds per entry: a body, a stored payload, or a failure. */
|
|
92
|
+
entryFetchStates: (entryIds: readonly string[]) => Promise<RemoteResult<{
|
|
93
|
+
states: Record<string, ReaderEntryFetchState>;
|
|
94
|
+
}>>;
|
|
95
|
+
/** Cache body markup this process holds (the sweep's extractions, or a feed's own full text on open). */
|
|
96
|
+
storeEntryBody: (request: {
|
|
97
|
+
entryId: string;
|
|
98
|
+
url: string;
|
|
99
|
+
html: string;
|
|
100
|
+
truncated?: boolean;
|
|
101
|
+
scriptFigures?: number;
|
|
102
|
+
/** This body came from the capture rendered fetch; 「重新抓取」 re-renders rather than plain-fetches it. */
|
|
103
|
+
rendered?: boolean;
|
|
104
|
+
/** `translationHash(html)` — the entry's translation map dies with a body it no longer matches. */
|
|
105
|
+
bodyHash?: string;
|
|
106
|
+
/** The article's own title, as extracted — a saved link's card upgrade. */
|
|
107
|
+
title?: string;
|
|
108
|
+
/** A short excerpt (the first substantial paragraph), with the title. */
|
|
109
|
+
excerpt?: string;
|
|
110
|
+
}) => Promise<RemoteResult<ReaderEntryBodyView>>;
|
|
111
|
+
/** A payload the host stored, so this process can extract it (late or never). */
|
|
112
|
+
getRawBody: (entryId: string) => Promise<RemoteResult<{
|
|
113
|
+
entryId: string;
|
|
114
|
+
raw?: string;
|
|
115
|
+
url?: string;
|
|
116
|
+
truncated?: boolean;
|
|
117
|
+
error?: string;
|
|
118
|
+
}>>;
|
|
119
|
+
/**
|
|
120
|
+
* The entries whose full text still has to be filled in (the automatic
|
|
121
|
+
* backfill's work list; the host owns the policy, this process owns the DOM).
|
|
122
|
+
*/
|
|
123
|
+
listBackfillCandidates: (entries: readonly {
|
|
124
|
+
entryId: string;
|
|
125
|
+
url: string;
|
|
126
|
+
label: string;
|
|
127
|
+
hasBody: boolean;
|
|
128
|
+
}[]) => Promise<RemoteResult<{
|
|
129
|
+
candidates: ReaderBackfillCandidate[];
|
|
130
|
+
}>>;
|
|
131
|
+
/**
|
|
132
|
+
* What the host has for one entry: a fresh cached body, else the feed's own
|
|
133
|
+
* payload, else nothing (plus why a previous fetch failed).
|
|
134
|
+
*/
|
|
135
|
+
getEntryBody: (request: {
|
|
136
|
+
entryId: string;
|
|
137
|
+
url: string;
|
|
138
|
+
feedHtml?: string;
|
|
139
|
+
}) => Promise<RemoteResult<ReaderEntryBodyView>>;
|
|
140
|
+
/**
|
|
141
|
+
* Fetch one entry's article, extract it in THIS process (the host has no
|
|
142
|
+
* parser) and cache what came out. One call from the view's perspective; the
|
|
143
|
+
* extracted title/excerpt ride along for the card upgrade.
|
|
144
|
+
*/
|
|
145
|
+
fetchEntryBody: (entryId: string, url: string) => Promise<ReaderEntryBodyView & ReaderExtractedMeta>;
|
|
146
|
+
/** The tags on one entry. */
|
|
147
|
+
entryTags: (entryId: string) => Promise<RemoteResult<{
|
|
148
|
+
tags: ReaderTag[];
|
|
149
|
+
}>>;
|
|
150
|
+
/** The tag vocabulary, with per-tag usage counts. */
|
|
151
|
+
listTags: () => Promise<RemoteResult<{
|
|
152
|
+
tags: ReaderTag[];
|
|
153
|
+
counts: Record<string, number>;
|
|
154
|
+
}>>;
|
|
155
|
+
/** Create a tag, or return the existing one whose name matches. */
|
|
156
|
+
createTag: (name: string) => Promise<RemoteResult<ReaderTag | ReaderAnnotationOutcome>>;
|
|
157
|
+
/** Add or remove one tag on one entry. */
|
|
158
|
+
tagEntry: (entryId: string, tagId: string, on: boolean) => Promise<RemoteResult<ReaderAnnotationOutcome>>;
|
|
159
|
+
/** Rename a tag. */
|
|
160
|
+
renameTag: (id: string, name: string) => Promise<RemoteResult<ReaderAnnotationOutcome>>;
|
|
161
|
+
/** Delete a tag everywhere. */
|
|
162
|
+
deleteTag: (id: string) => Promise<RemoteResult<ReaderAnnotationOutcome>>;
|
|
163
|
+
/** Drop tags nothing references any more. */
|
|
164
|
+
pruneTags: () => Promise<RemoteResult<{
|
|
165
|
+
removed: number;
|
|
166
|
+
}>>;
|
|
167
|
+
/** Record that the reader opened one entry (the 「最近阅读」 page's write half). */
|
|
168
|
+
recordRead: (request: {
|
|
169
|
+
entryId: string;
|
|
170
|
+
sourceId: string;
|
|
171
|
+
title: string;
|
|
172
|
+
url?: string;
|
|
173
|
+
}) => Promise<RemoteResult<{
|
|
174
|
+
entries: number;
|
|
175
|
+
}>>;
|
|
176
|
+
/** The entries the reader opened, newest first. */
|
|
177
|
+
listRecent: () => Promise<RemoteResult<{
|
|
178
|
+
entries: ReaderRecentEntry[];
|
|
179
|
+
}>>;
|
|
180
|
+
/** Forget every recent entry. */
|
|
181
|
+
clearRecent: () => Promise<RemoteResult<{
|
|
182
|
+
removed: number;
|
|
183
|
+
}>>;
|
|
184
|
+
/** Read / set the article cache policy (and the translation budget). */
|
|
185
|
+
getCachePolicy: () => Promise<RemoteResult<{
|
|
186
|
+
ttlHours: number;
|
|
187
|
+
maxEntries: number;
|
|
188
|
+
translationBudgetChars: number;
|
|
189
|
+
}>>;
|
|
190
|
+
setCachePolicy: (ttlHours: number, maxEntries?: number, translationBudgetChars?: number) => Promise<RemoteResult<ReaderAnnotationOutcome>>;
|
|
191
|
+
/** One entry's exact-fit translation record, when the host holds a usable one. */
|
|
192
|
+
getEntryTranslation: (entryId: string) => Promise<RemoteResult<{
|
|
193
|
+
translation?: ReaderEntryTranslationView;
|
|
194
|
+
}>>;
|
|
195
|
+
/** Translations for exactly the asked sentence hashes of one pair (never the table). */
|
|
196
|
+
getSentenceTranslations: (request: {
|
|
197
|
+
pair: string;
|
|
198
|
+
hashes: readonly string[];
|
|
199
|
+
}) => Promise<RemoteResult<{
|
|
200
|
+
translations: Record<string, string>;
|
|
201
|
+
}>>;
|
|
202
|
+
/**
|
|
203
|
+
* Persist what one translation run learned (and refresh what it reused), in
|
|
204
|
+
* one batch — the caller batches by run, so this is never a per-sentence call.
|
|
205
|
+
*/
|
|
206
|
+
rememberSentences: (request: {
|
|
207
|
+
pair: string;
|
|
208
|
+
entries: readonly ReaderSentenceLearn[];
|
|
209
|
+
recalled?: readonly ReaderSentenceLearn[];
|
|
210
|
+
entryId?: string;
|
|
211
|
+
bodyHash?: string;
|
|
212
|
+
}) => Promise<RemoteResult<{
|
|
213
|
+
stored: number;
|
|
214
|
+
}>>;
|
|
215
|
+
/** Per-tier cache usage, aggregated on the host (the manage page's readout). */
|
|
216
|
+
getStorageStats: () => Promise<RemoteResult<ReaderStorageStats>>;
|
|
217
|
+
/** Forget every translation: the global memory and every entry map. */
|
|
218
|
+
clearTranslations: () => Promise<RemoteResult<{
|
|
219
|
+
clearedEntries: number;
|
|
220
|
+
clearedMemory: boolean;
|
|
221
|
+
}>>;
|
|
222
|
+
/** Read the current conversation draft (the merge `setDraft` needs). */
|
|
223
|
+
readDraft: () => string;
|
|
224
|
+
/** Replace the conversation draft with `merged`; a no-op without a session surface. */
|
|
225
|
+
setDraft: (merged: string) => void;
|
|
226
|
+
/** Copy text to the clipboard; resolves false when the API is unavailable. */
|
|
227
|
+
copyText: (text: string) => Promise<boolean>;
|
|
228
|
+
/** Open a URL in the user's browser; false when the platform refuses. */
|
|
229
|
+
openExternal: (url: string) => boolean;
|
|
230
|
+
/**
|
|
231
|
+
* Whether the host's in-app Sidebar Browser is mounted (probed live: the tab
|
|
232
|
+
* kind `browser` exists on host 0.1.6-alpha.2+). When it is, fetches that
|
|
233
|
+
* need a real browser — a page that draws its figures with scripts, a bot
|
|
234
|
+
* wall — get an in-app way out instead of only an external link.
|
|
235
|
+
*/
|
|
236
|
+
browserTabAvailable: () => boolean;
|
|
237
|
+
/**
|
|
238
|
+
* Open a URL in the in-app Sidebar Browser; false when the seam is absent or
|
|
239
|
+
* refuses, so the caller falls back to {@link ReaderPaneInjected.openExternal}.
|
|
240
|
+
*/
|
|
241
|
+
openBrowserTab: (url: string) => boolean;
|
|
242
|
+
/**
|
|
243
|
+
* The capture package's Remote, when one is mounted — the 「渲染抓取」 slot
|
|
244
|
+
* renders only while this answers. M0 ships no capture package (it is the
|
|
245
|
+
* ingest proposal's M1), so the probe is the whole point today: probed live,
|
|
246
|
+
* never cached, never thrown.
|
|
247
|
+
*/
|
|
248
|
+
captureRemote: () => ReaderCaptureRemote | undefined;
|
|
249
|
+
}
|
|
250
|
+
/** Full props of the reader pane: the seat's session + store + locale + face. */
|
|
251
|
+
export type ReaderPaneProps = {
|
|
252
|
+
sessionId: SessionId;
|
|
253
|
+
} & GlobalStandardProps & PropsStore<ReturnType<typeof createReaderStore>> & InjectFace<ReaderPaneInjected> & PropsLocale<'reader'>;
|
|
254
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage one of the reader's right-Sidebar registration: the tab type, the body
|
|
3
|
+
* of which is registered in `client/index.ts`.
|
|
4
|
+
*
|
|
5
|
+
* The kind is MINTED (`reader`) rather than taken over: this is a page type of
|
|
6
|
+
* its own, not a superset of an official one, so it claims no address and ships
|
|
7
|
+
* no `patterns` / `canOpen`. It joins the guide page as its own card, which is
|
|
8
|
+
* how a reader finds it before pinning the tab.
|
|
9
|
+
*
|
|
10
|
+
* @module @khorsheed/dsh-reader/client/definition
|
|
11
|
+
*/
|
|
12
|
+
import type { SidebarRightTabDefinition } from '@deepseek-ai/dsh-client-ui-sidebar-right/client';
|
|
13
|
+
import type { TranslateNS } from '@deepseek-ai/dsh-client-locale/client';
|
|
14
|
+
/** The tab kind: minted, since no official kind means "reader". */
|
|
15
|
+
export declare const READER_KIND = "reader";
|
|
16
|
+
/** This implementation's identity, and the key its body registers under. */
|
|
17
|
+
export declare const READER_TAB_ID = "@khorsheed/dsh-reader";
|
|
18
|
+
/**
|
|
19
|
+
* The reader type's registry definition. No `priority` (the default
|
|
20
|
+
* `extension` band is right for a type from outside the product) and no
|
|
21
|
+
* `patterns`/`canOpen` (a page type recognises no address).
|
|
22
|
+
*
|
|
23
|
+
* @param t - the reader namespace's translator.
|
|
24
|
+
* @returns the definition to register.
|
|
25
|
+
*/
|
|
26
|
+
export declare function readerDefinition(t: TranslateNS<'reader'>): SidebarRightTabDefinition;
|
|
27
|
+
//# sourceMappingURL=definition.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { IconGlobeOutlineMedium } from "./icons.js";
|
|
3
|
+
/** The tab kind: minted, since no official kind means "reader". */
|
|
4
|
+
export const READER_KIND = 'reader';
|
|
5
|
+
/** This implementation's identity, and the key its body registers under. */
|
|
6
|
+
export const READER_TAB_ID = '@khorsheed/dsh-reader';
|
|
7
|
+
/** The type's glyph on the guide card. */
|
|
8
|
+
function ReaderGlyph({ size, className }) {
|
|
9
|
+
return _jsx(IconGlobeOutlineMedium, { size: size, className: className });
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The reader type's registry definition. No `priority` (the default
|
|
13
|
+
* `extension` band is right for a type from outside the product) and no
|
|
14
|
+
* `patterns`/`canOpen` (a page type recognises no address).
|
|
15
|
+
*
|
|
16
|
+
* @param t - the reader namespace's translator.
|
|
17
|
+
* @returns the definition to register.
|
|
18
|
+
*/
|
|
19
|
+
export function readerDefinition(t) {
|
|
20
|
+
return {
|
|
21
|
+
id: READER_TAB_ID,
|
|
22
|
+
kind: READER_KIND,
|
|
23
|
+
title: () => t('tab.label'),
|
|
24
|
+
guide: [{
|
|
25
|
+
id: READER_KIND,
|
|
26
|
+
order: 60,
|
|
27
|
+
title: () => t('tab.label'),
|
|
28
|
+
description: () => t('guide.description'),
|
|
29
|
+
icon: ReaderGlyph,
|
|
30
|
+
}],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Article extraction and rich-text normalization for the browser half.
|
|
3
|
+
*
|
|
4
|
+
* Two exports, one for each shape of input the reader handles:
|
|
5
|
+
*
|
|
6
|
+
* - {@link extractArticle} takes a fetched HTML page and finds its body. The
|
|
7
|
+
* honest problem here is that which block is "the article" is a heuristic:
|
|
8
|
+
* this module scores candidate blocks the way readability implementations do
|
|
9
|
+
* (paragraph mass, minus link mass, with wrapper blocks demoted) and adds the
|
|
10
|
+
* one signal that a single-language scorer gets wrong — Chinese paragraphs
|
|
11
|
+
* are short by nature, so a length threshold tuned for English rejects them
|
|
12
|
+
* and the scorer falls back to the whole page container (measured: 473 <p>
|
|
13
|
+
* and 56 images instead of the article).
|
|
14
|
+
* - {@link normalizeRichText} takes markup that is ALREADY known to be a body
|
|
15
|
+
* (a feed's `content:encoded`, or the block extraction selected) and puts it
|
|
16
|
+
* through a tag whitelist, so the renderer never sees third-party scripts,
|
|
17
|
+
* styles, forms or event handlers.
|
|
18
|
+
*
|
|
19
|
+
* Everything runs in the browser because the host runtime has no DOM parser at
|
|
20
|
+
* all; nothing here touches the network.
|
|
21
|
+
*
|
|
22
|
+
* @module @khorsheed/dsh-reader/client/extract-article
|
|
23
|
+
*/
|
|
24
|
+
/** Result of extracting one article page. */
|
|
25
|
+
export type ExtractArticleResult = {
|
|
26
|
+
readonly ok: true;
|
|
27
|
+
readonly html: string;
|
|
28
|
+
readonly textLength: number;
|
|
29
|
+
/**
|
|
30
|
+
* The article's own title, when one is recoverable: the body's first
|
|
31
|
+
* heading, else the document's `<title>`. A saved link's card upgrades from
|
|
32
|
+
* the URL-derived label to this — the wall is where "which paper was this"
|
|
33
|
+
* gets answered.
|
|
34
|
+
*/
|
|
35
|
+
readonly title?: string;
|
|
36
|
+
/**
|
|
37
|
+
* A short plain-text excerpt (the first substantial paragraph, truncated at
|
|
38
|
+
* the card's measure), captured with the title.
|
|
39
|
+
*/
|
|
40
|
+
readonly excerpt?: string;
|
|
41
|
+
/**
|
|
42
|
+
* How many figures had to be dropped because the page DRAWS them.
|
|
43
|
+
*
|
|
44
|
+
* Measured on the acceptance instance's transformer-circuits.pub paper:
|
|
45
|
+
* `<figure data-fignum="2">` contains an empty `<div class='intro-structural'>`
|
|
46
|
+
* and a caption, and the illustration is rendered by the page's own scripts.
|
|
47
|
+
* A fetch runs no scripts, so what arrives is a caption describing a picture
|
|
48
|
+
* that is not there. Dropping the empty figure and saying so beats leaving a
|
|
49
|
+
* bare caption the reader will read as "the plugin lost my image".
|
|
50
|
+
*/
|
|
51
|
+
readonly scriptFigures?: number;
|
|
52
|
+
} | {
|
|
53
|
+
readonly ok: false;
|
|
54
|
+
readonly error: string;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Extract the article body from a fetched page.
|
|
58
|
+
*
|
|
59
|
+
* @param pageHtml - the raw HTML the host fetched.
|
|
60
|
+
* @param baseUrl - the page URL, used to absolutize relative links and images.
|
|
61
|
+
* @returns the normalized body HTML, or the reason nothing usable was found.
|
|
62
|
+
*/
|
|
63
|
+
export declare function extractArticle(pageHtml: string, baseUrl: string): ExtractArticleResult;
|
|
64
|
+
/**
|
|
65
|
+
* Put trusted-shape markup through the whitelist.
|
|
66
|
+
*
|
|
67
|
+
* Feed bodies take this path: a feed's `content:encoded` is markup from the
|
|
68
|
+
* same publisher as the page, so it earns the same whitelist rather than the
|
|
69
|
+
* extraction heuristic.
|
|
70
|
+
*
|
|
71
|
+
* @param html - markup from a feed or from extraction.
|
|
72
|
+
* @param baseUrl - base for relative URLs (feed bodies usually use absolute ones).
|
|
73
|
+
* @returns whitelisted HTML, or an empty string when nothing survives.
|
|
74
|
+
*/
|
|
75
|
+
export declare function normalizeRichText(html: string, baseUrl?: string): string;
|
|
76
|
+
/**
|
|
77
|
+
* The payload size that separates a real inlined figure from a 1px tracking
|
|
78
|
+
* placeholder. A transparent-gif placeholder is ~70 characters of base64; the
|
|
79
|
+
* smallest real chart is thousands — the threshold sits far from both, so no
|
|
80
|
+
* borderline page ever decides anything. (transformer-circuits.pub inlines its
|
|
81
|
+
* real figures as multi-hundred-KB data URIs; the old all-data:-is-placeholder
|
|
82
|
+
* rule deleted them.)
|
|
83
|
+
*/
|
|
84
|
+
export declare const DATA_IMAGE_MIN_PAYLOAD = 512;
|
|
85
|
+
/**
|
|
86
|
+
* A `data:` URI worth keeping as an image, or `undefined` (which reads as
|
|
87
|
+
* ABSENT — the caller falls through to the lazy-loading attributes).
|
|
88
|
+
*
|
|
89
|
+
* The gate is MIME plus payload size; base64 payloads are whitespace-stripped
|
|
90
|
+
* because pages line-wrap them (a raw newline inside an emitted attribute is
|
|
91
|
+
* legal HTML, but the stripped form is what browsers parse either way).
|
|
92
|
+
*/
|
|
93
|
+
export declare function substantiveDataImage(value: string): string | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* Turn an inline-only rendering of a node into plain text-safe markup. Feed
|
|
96
|
+
* summaries use this shape when the caller wants emphasis but no blocks.
|
|
97
|
+
*
|
|
98
|
+
* @param html - markup.
|
|
99
|
+
* @returns normalized inline markup.
|
|
100
|
+
*/
|
|
101
|
+
export declare function normalizeInline(html: string): string;
|
|
102
|
+
//# sourceMappingURL=extract-article.d.ts.map
|