@aglyn/plugins-ai 1.0.0-beta.288 → 1.0.0-beta.290
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/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.d.ts +3 -2
- package/src/lib/activity/ai-activity-actions.js +3 -0
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +2 -0
- package/src/lib/components/ai-job-requests.js +2 -0
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +4 -0
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-import-job.component.d.ts +6 -1
- package/src/lib/components/ai-site-import-job.component.js +124 -17
- package/src/lib/components/ai-site-import-job.component.js.map +1 -1
- package/src/lib/components/ai-site-import-start.component.d.ts +6 -0
- package/src/lib/components/ai-site-import-start.component.js +18 -4
- package/src/lib/components/ai-site-import-start.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +2 -1
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/jobs/ai-image-reads.d.ts +20 -2
- package/src/lib/jobs/ai-image-reads.js +47 -0
- package/src/lib/jobs/ai-image-reads.js.map +1 -1
- package/src/lib/jobs/ai-job-import-step.d.ts +53 -3
- package/src/lib/jobs/ai-job-import-step.js +386 -64
- package/src/lib/jobs/ai-job-import-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +33 -0
- package/src/lib/jobs/ai-job-layout-language.js +75 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +8 -3
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +29 -0
- package/src/lib/jobs/ai-job-page-language.js +178 -2
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +3 -0
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-site-import-document.d.ts +117 -0
- package/src/lib/jobs/ai-site-import-document.js +710 -0
- package/src/lib/jobs/ai-site-import-document.js.map +1 -0
- package/src/lib/jobs/ai-site-import-look.d.ts +112 -0
- package/src/lib/jobs/ai-site-import-look.js +402 -0
- package/src/lib/jobs/ai-site-import-look.js.map +1 -0
- package/src/lib/jobs/ai-site-import-publish.d.ts +41 -0
- package/src/lib/jobs/ai-site-import-publish.js +92 -0
- package/src/lib/jobs/ai-site-import-publish.js.map +1 -0
- package/src/lib/jobs/ai-theme-brand-inputs.d.ts +27 -2
- package/src/lib/jobs/ai-theme-brand-inputs.js +37 -8
- package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.d.ts +17 -0
- package/src/lib/layout-language/ai-layout-frame.js +73 -2
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.d.ts +43 -0
- package/src/lib/layout-language/ai-layout-language.js +199 -0
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +15 -0
- package/src/lib/model/ai-credit-estimate.js +14 -1
- package/src/lib/model/ai-credit-estimate.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +13 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-import-view.d.ts +47 -3
- package/src/lib/model/ai-site-import-view.js +122 -20
- package/src/lib/model/ai-site-import-view.js.map +1 -1
- package/src/lib/model/ai-site-import.d.ts +232 -7
- package/src/lib/model/ai-site-import.js +365 -35
- package/src/lib/model/ai-site-import.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +18 -0
- package/src/lib/runtime/ai-doctrine-validators.js +16 -5
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +31 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/server/ai-image-reads-route.js +24 -1
- package/src/lib/server/ai-image-reads-route.js.map +1 -1
- package/src/lib/server/ai-jobs-resume.js +7 -1
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/site-import/fixtures/aglyn-com-recordings.d.ts +57 -0
- package/src/lib/site-import/fixtures/aglyn-com-recordings.js +172 -0
- package/src/lib/site-import/fixtures/aglyn-com-recordings.js.map +1 -0
- package/src/lib/site-import/site-import-crawl.d.ts +32 -7
- package/src/lib/site-import/site-import-crawl.js +133 -44
- package/src/lib/site-import/site-import-crawl.js.map +1 -1
- package/src/lib/site-import/site-import-edge-server.d.ts +31 -0
- package/src/lib/site-import/site-import-edge-server.js +31 -0
- package/src/lib/site-import/site-import-edge-server.js.map +1 -0
- package/src/lib/site-import/site-import-edge.d.ts +44 -0
- package/src/lib/site-import/site-import-edge.js +108 -0
- package/src/lib/site-import/site-import-edge.js.map +1 -0
- package/src/lib/site-import/site-import-html.d.ts +73 -3
- package/src/lib/site-import/site-import-html.js +332 -45
- package/src/lib/site-import/site-import-html.js.map +1 -1
- package/src/lib/site-import/site-import-images.d.ts +22 -0
- package/src/lib/site-import/site-import-images.js +78 -2
- package/src/lib/site-import/site-import-images.js.map +1 -1
- package/src/lib/site-import/site-import-page.d.ts +44 -2
- package/src/lib/site-import/site-import-page.js +116 -16
- package/src/lib/site-import/site-import-page.js.map +1 -1
|
@@ -32,15 +32,25 @@
|
|
|
32
32
|
*
|
|
33
33
|
* The output is DATA. Text from a page reaches a model only fenced as
|
|
34
34
|
* untrusted (`fenceUntrustedText`), never as instructions.
|
|
35
|
+
*
|
|
36
|
+
* What a page is made of is kept as the page shows it (AGL-4083): words that
|
|
37
|
+
* sit in tags side by side stay apart ("Sign in" and "Start free", never
|
|
38
|
+
* "Sign inStart free"); a link is a block of its own, `mailto:` and `tel:`
|
|
39
|
+
* ones too, so an address or a phone number survives; a menu entry is named
|
|
40
|
+
* by its first line, not by its mega-menu description; a `<picture>`'s
|
|
41
|
+
* source stands in for an `<img>` with no address; and the head's share card,
|
|
42
|
+
* icons, site name and Organization structured data come along for the
|
|
43
|
+
* header, the footer and the site's own facts.
|
|
35
44
|
*=========================================*/ /** One run of a page's content, in the order the page draws it. */ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
36
45
|
/** Ceilings that keep one odd page from becoming the whole inventory. */ export const SITE_IMPORT_PAGE_LIMITS = {
|
|
37
|
-
blocks:
|
|
38
|
-
blockChars:
|
|
46
|
+
/** A pricing page's plan table runs past 400 (AGL-4083); a wave's read keeps fewer, under its own budget. */ blocks: 2000,
|
|
47
|
+
/** A legal page's paragraph runs past 2,000 characters. */ blockChars: 10000,
|
|
39
48
|
images: 100,
|
|
40
49
|
links: 300,
|
|
41
|
-
navLinks:
|
|
50
|
+
/** A mega-menu's entries and the footer's columns together. */ navLinks: 150,
|
|
42
51
|
forms: 10,
|
|
43
|
-
fieldsPerForm: 30
|
|
52
|
+
fieldsPerForm: 30,
|
|
53
|
+
icons: 12
|
|
44
54
|
};
|
|
45
55
|
/** Elements removed with everything inside them. */ const REMOVED_WITH_CONTENT = [
|
|
46
56
|
'script',
|
|
@@ -105,6 +115,52 @@ const BLOCK_TAGS = {
|
|
|
105
115
|
'br',
|
|
106
116
|
'body'
|
|
107
117
|
]);
|
|
118
|
+
/**
|
|
119
|
+
* Tags a browser lays out apart from the words beside them (AGL-4083): two
|
|
120
|
+
* links, or a link and a picture, side by side are two runs of words, never
|
|
121
|
+
* one ("Sign in" and "Start free", not "Sign inStart free"). Every other tag
|
|
122
|
+
* inside a block styles words where they are (a `<span>` or a `<strong>`
|
|
123
|
+
* mid-sentence) and adds no space; block-level tags already end their block.
|
|
124
|
+
*/ const RUN_BOUNDARY = new Set([
|
|
125
|
+
'a',
|
|
126
|
+
'img',
|
|
127
|
+
'button',
|
|
128
|
+
'input',
|
|
129
|
+
'select',
|
|
130
|
+
'textarea',
|
|
131
|
+
'picture',
|
|
132
|
+
'video',
|
|
133
|
+
'audio',
|
|
134
|
+
'iframe',
|
|
135
|
+
'object',
|
|
136
|
+
'embed',
|
|
137
|
+
'canvas',
|
|
138
|
+
'svg',
|
|
139
|
+
'meter',
|
|
140
|
+
'progress',
|
|
141
|
+
'output'
|
|
142
|
+
]);
|
|
143
|
+
/** Where one run of words ends and the next begins, resolved when a block is written. */ const RUN_BREAK = '\u0001';
|
|
144
|
+
/**
|
|
145
|
+
* The runs of a block joined as a reader sees them: a space between two
|
|
146
|
+
* runs, except before closing punctuation or after an opening one, where
|
|
147
|
+
* the page wrote none ("<span>Austin</span>, Texas" is "Austin, Texas").
|
|
148
|
+
*/ const RUN_BREAKS = new RegExp(`[\\s${RUN_BREAK}]*${RUN_BREAK}[\\s${RUN_BREAK}]*`, 'g');
|
|
149
|
+
function joinRuns(text) {
|
|
150
|
+
if (!text.includes(RUN_BREAK)) return text;
|
|
151
|
+
return text.replace(RUN_BREAKS, (match, offset, whole)=>{
|
|
152
|
+
var _whole_, _whole_1, _whole_2, _whole_3;
|
|
153
|
+
if (/\s/.test(match)) return ' ';
|
|
154
|
+
const before = (_whole_ = whole[offset - 1]) != null ? _whole_ : '';
|
|
155
|
+
const after = (_whole_1 = whole[offset + match.length]) != null ? _whole_1 : '';
|
|
156
|
+
if (!before || !after) return '';
|
|
157
|
+
if (/[.,;:!?%)\]}\u2019\u201d\u00bb]/.test(after) || /[([{\u2018\u201c\u00ab]/.test(before)) return '';
|
|
158
|
+
// A straight quote opens after a space or a bracket, and closes before one.
|
|
159
|
+
if (/["']/.test(before) && /^$|[\s([{]/.test((_whole_2 = whole[offset - 2]) != null ? _whole_2 : '')) return '';
|
|
160
|
+
if (/["']/.test(after) && /^$|[\s.,;:!?)\]}]/.test((_whole_3 = whole[offset + match.length + 1]) != null ? _whole_3 : '')) return '';
|
|
161
|
+
return ' ';
|
|
162
|
+
});
|
|
163
|
+
}
|
|
108
164
|
const NAMED_ENTITIES = {
|
|
109
165
|
amp: '&',
|
|
110
166
|
lt: '<',
|
|
@@ -176,7 +232,7 @@ const TAG = /<(\/?)([a-zA-Z][\w:-]*)((?:[^>"']|"[^"]*"|'[^']*')*)>/g;
|
|
|
176
232
|
}).filter(Boolean);
|
|
177
233
|
}
|
|
178
234
|
function collapse(text) {
|
|
179
|
-
return text.replace(/\s+/g, ' ').trim();
|
|
235
|
+
return joinRuns(text).replace(/\s+/g, ' ').trim();
|
|
180
236
|
}
|
|
181
237
|
function clip(text, max) {
|
|
182
238
|
return text.length > max ? `${text.slice(0, max - 1).trimEnd()}…` : text;
|
|
@@ -203,9 +259,12 @@ function removeElements(html, tags) {
|
|
|
203
259
|
if (kept.length < SITE_IMPORT_PAGE_LIMITS.forms) {
|
|
204
260
|
const fields = [];
|
|
205
261
|
for (const match of form.matchAll(/<(input|textarea|select)\b((?:[^>"']|"[^"]*"|'[^']*')*)>/gi)){
|
|
262
|
+
var _attributes_ariahidden;
|
|
206
263
|
const attributes = parseAttributes(match[2]);
|
|
207
264
|
const type = match[1].toLowerCase() === 'input' ? (attributes['type'] || 'text').toLowerCase() : match[1].toLowerCase();
|
|
208
265
|
if (type === 'hidden' || type === 'submit' || type === 'button' || type === 'image') continue;
|
|
266
|
+
// A field no visitor sees is a trap for bots or a widget's shadow, not a question (AGL-4083).
|
|
267
|
+
if (((_attributes_ariahidden = attributes['aria-hidden']) != null ? _attributes_ariahidden : '').toLowerCase() === 'true' || attributes['tabindex'] === '-1') continue;
|
|
209
268
|
fields.push({
|
|
210
269
|
type,
|
|
211
270
|
name: clip(attributes['name'] || attributes['id'] || '', 80)
|
|
@@ -241,19 +300,154 @@ function removeElements(html, tags) {
|
|
|
241
300
|
const first = (_String_split_ = String(srcset != null ? srcset : '').split(',')[0]) == null ? void 0 : _String_split_.trim().split(/\s+/)[0];
|
|
242
301
|
return first || undefined;
|
|
243
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Where a link a page shows goes (AGL-4083): an absolute http(s) address
|
|
305
|
+
* without its fragment, a `mailto:` with an address in it, or a `tel:` with
|
|
306
|
+
* a number in it; `null` for anything else (a script, a jump on the page).
|
|
307
|
+
*/ export function resolveSiteLink(raw, base) {
|
|
308
|
+
var _ref;
|
|
309
|
+
var _resolveSiteUrl;
|
|
310
|
+
const value = String(raw != null ? raw : '').trim();
|
|
311
|
+
const mail = /^mailto:([^?\s<>"']+@[^?\s<>"']+)/i.exec(value);
|
|
312
|
+
if (mail) return `mailto:${decodeURIComponent(mail[1]).toLowerCase()}`;
|
|
313
|
+
const phone = /^tel:([+\d][\d\s().-]{2,30})$/i.exec(value);
|
|
314
|
+
if (phone) return `tel:${phone[1].replace(/[\s().-]/g, '')}`;
|
|
315
|
+
if (value.startsWith('#')) return null;
|
|
316
|
+
return (_ref = (_resolveSiteUrl = resolveSiteUrl(value, base)) == null ? void 0 : _resolveSiteUrl.toString()) != null ? _ref : null;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* A `<picture>` whose `<img>` has no address of its own (a placeholder, or
|
|
320
|
+
* none at all) is read from its first `<source>` (AGL-4083); a `<picture>`
|
|
321
|
+
* whose `<img>` has one keeps it. The sources go with the void elements.
|
|
322
|
+
*/ function readPictures(html) {
|
|
323
|
+
return html.replace(/<picture\b(?:[^>"']|"[^"]*"|'[^']*')*>([\s\S]*?)<\/picture\s*>/gi, (whole, inner)=>{
|
|
324
|
+
var _attributes_alt;
|
|
325
|
+
const img = /<img\b((?:[^>"']|"[^"]*"|'[^']*')*)>/i.exec(inner);
|
|
326
|
+
const attributes = img ? parseAttributes(img[1]) : {};
|
|
327
|
+
const own = attributes['src'] || attributes['data-src'] || firstSrcset(attributes['srcset'] || attributes['data-srcset']);
|
|
328
|
+
if (own && !/^data:/i.test(own.trim())) return whole;
|
|
329
|
+
let source;
|
|
330
|
+
for (const match of inner.matchAll(/<source\b((?:[^>"']|"[^"]*"|'[^']*')*)>/gi)){
|
|
331
|
+
const sourceAttributes = parseAttributes(match[1]);
|
|
332
|
+
source = firstSrcset(sourceAttributes['srcset'] || sourceAttributes['data-srcset']);
|
|
333
|
+
if (source) break;
|
|
334
|
+
}
|
|
335
|
+
if (!source) return whole;
|
|
336
|
+
const quote = (value)=>value.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');
|
|
337
|
+
return ` <img src="${quote(source)}" alt="${quote((_attributes_alt = attributes['alt']) != null ? _attributes_alt : '')}"> `;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
/** The head's icons, the largest first within each kind. */ function readIcons(html, pageUrl) {
|
|
341
|
+
const icons = [];
|
|
342
|
+
for (const match of html.matchAll(/<link\b((?:[^>"']|"[^"]*"|'[^']*')*)>/gi)){
|
|
343
|
+
var _attributes_rel, _match, _attributes_sizes;
|
|
344
|
+
var _attributes_type;
|
|
345
|
+
const attributes = parseAttributes(match[1]);
|
|
346
|
+
const rels = ((_attributes_rel = attributes['rel']) != null ? _attributes_rel : '').toLowerCase().split(/\s+/);
|
|
347
|
+
const rel = rels.includes('apple-touch-icon') || rels.includes('apple-touch-icon-precomposed') ? 'apple-touch-icon' : rels.includes('icon') ? 'icon' : null;
|
|
348
|
+
if (!rel) continue;
|
|
349
|
+
const url = resolveSiteUrl(attributes['href'], pageUrl);
|
|
350
|
+
if (!url) continue;
|
|
351
|
+
const sizes = [
|
|
352
|
+
...(_match = ((_attributes_sizes = attributes['sizes']) != null ? _attributes_sizes : '').match(/(\d+)\s*x\s*(\d+)/gi)) != null ? _match : []
|
|
353
|
+
];
|
|
354
|
+
const size = sizes.reduce((most, entry)=>Math.max(most, ...entry.split(/x/i).map((side)=>Number(side) || 0)), 0);
|
|
355
|
+
icons.push({
|
|
356
|
+
url: url.toString(),
|
|
357
|
+
rel,
|
|
358
|
+
size: size || null,
|
|
359
|
+
type: ((_attributes_type = attributes['type']) == null ? void 0 : _attributes_type.trim()) || null
|
|
360
|
+
});
|
|
361
|
+
if (icons.length >= SITE_IMPORT_PAGE_LIMITS.icons) break;
|
|
362
|
+
}
|
|
363
|
+
return icons.sort((a, b)=>{
|
|
364
|
+
var _b_size, _a_size;
|
|
365
|
+
return a.rel === b.rel ? ((_b_size = b.size) != null ? _b_size : 0) - ((_a_size = a.size) != null ? _a_size : 0) : a.rel === 'apple-touch-icon' ? -1 : 1;
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
const ORGANIZATION_TYPE = /(?:^|\b)(?:Organization|Corporation|LocalBusiness|NGO|EducationalOrganization|MedicalOrganization|SportsOrganization|Store|Restaurant|ProfessionalService|HomeAndConstructionBusiness|HealthAndBeautyBusiness|FoodEstablishment|AutomotiveBusiness|LegalService|FinancialService|Dentist|Physician|LodgingBusiness|EntertainmentBusiness)$/;
|
|
369
|
+
/**
|
|
370
|
+
* The first Organization (or kind of local business) the page's structured
|
|
371
|
+
* data describes, read before scripts are thrown away. Malformed data, or
|
|
372
|
+
* none, is `null`; nothing in it is ever run.
|
|
373
|
+
*/ function readOrganization(html) {
|
|
374
|
+
var _node_contactPoint, _node_sameAs;
|
|
375
|
+
const nodes = [];
|
|
376
|
+
const visit = (value, depth)=>{
|
|
377
|
+
if (depth > 4 || nodes.length > 50 || !value || typeof value !== 'object') return;
|
|
378
|
+
if (Array.isArray(value)) {
|
|
379
|
+
for (const entry of value)visit(entry, depth + 1);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const node = value;
|
|
383
|
+
nodes.push(node);
|
|
384
|
+
if (node['@graph']) visit(node['@graph'], depth + 1);
|
|
385
|
+
};
|
|
386
|
+
for (const match of html.matchAll(/<script\b[^>]*\btype\s*=\s*["']?application\/ld\+json["']?[^>]*>([\s\S]*?)<\/script\s*>/gi)){
|
|
387
|
+
try {
|
|
388
|
+
visit(JSON.parse(match[1]), 0);
|
|
389
|
+
} catch (unused) {
|
|
390
|
+
// Structured data that does not parse says nothing.
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const typeOf = (node)=>{
|
|
394
|
+
var _node_type;
|
|
395
|
+
return [].concat((_node_type = node['@type']) != null ? _node_type : []).map(String);
|
|
396
|
+
};
|
|
397
|
+
const node = nodes.find((entry)=>typeOf(entry).some((type)=>ORGANIZATION_TYPE.test(type)));
|
|
398
|
+
if (!node) return null;
|
|
399
|
+
const text = (value, max = 300)=>typeof value === 'string' ? clip(collapse(value), max) : '';
|
|
400
|
+
const urlOf = (value)=>{
|
|
401
|
+
const raw = typeof value === 'string' ? value : value && typeof value === 'object' ? value['url'] : '';
|
|
402
|
+
return typeof raw === 'string' && /^https?:\/\//i.test(raw.trim()) ? raw.trim() : '';
|
|
403
|
+
};
|
|
404
|
+
const contact = [].concat((_node_contactPoint = node['contactPoint']) != null ? _node_contactPoint : []).find((entry)=>entry && typeof entry === 'object');
|
|
405
|
+
const address = (()=>{
|
|
406
|
+
const value = node['address'];
|
|
407
|
+
if (typeof value === 'string') return text(value);
|
|
408
|
+
if (!value || typeof value !== 'object') return '';
|
|
409
|
+
const parts = value;
|
|
410
|
+
const region = [
|
|
411
|
+
text(parts['addressRegion']),
|
|
412
|
+
text(parts['postalCode'])
|
|
413
|
+
].filter(Boolean).join(' ');
|
|
414
|
+
const country = parts['addressCountry'];
|
|
415
|
+
return [
|
|
416
|
+
text(parts['streetAddress']),
|
|
417
|
+
text(parts['addressLocality']),
|
|
418
|
+
region,
|
|
419
|
+
typeof country === 'string' ? text(country) : text(country == null ? void 0 : country['name'])
|
|
420
|
+
].filter(Boolean).join(', ');
|
|
421
|
+
})();
|
|
422
|
+
return {
|
|
423
|
+
name: text(node['name']),
|
|
424
|
+
description: text(node['description'], 1000),
|
|
425
|
+
url: urlOf(node['url']),
|
|
426
|
+
logo: urlOf(node['logo']),
|
|
427
|
+
telephone: text(node['telephone']) || text(contact == null ? void 0 : contact['telephone']),
|
|
428
|
+
email: (text(node['email']) || text(contact == null ? void 0 : contact['email'])).replace(/^mailto:/i, ''),
|
|
429
|
+
address,
|
|
430
|
+
sameAs: [].concat((_node_sameAs = node['sameAs']) != null ? _node_sameAs : []).map((entry)=>urlOf(entry)).filter(Boolean).slice(0, 20)
|
|
431
|
+
};
|
|
432
|
+
}
|
|
244
433
|
/**
|
|
245
434
|
* The words, structure, pictures and links of one page.
|
|
246
435
|
*
|
|
247
436
|
* `isSameSite(url)` says whether an address is on the site being imported;
|
|
248
437
|
* only those become `links`, and images are marked by it.
|
|
249
438
|
*/ export function extractSiteImportPage(html, pageUrl, isSameSite) {
|
|
250
|
-
var _ref;
|
|
251
|
-
var _parseAttributes_lang;
|
|
439
|
+
var _ref, _ref1;
|
|
440
|
+
var _resolveSiteUrl, _parseAttributes_lang;
|
|
252
441
|
const empty = {
|
|
253
442
|
title: '',
|
|
254
443
|
description: '',
|
|
255
444
|
lang: null,
|
|
256
445
|
canonical: null,
|
|
446
|
+
ogImage: null,
|
|
447
|
+
siteName: '',
|
|
448
|
+
icons: [],
|
|
449
|
+
brand: null,
|
|
450
|
+
organization: null,
|
|
257
451
|
blocks: [],
|
|
258
452
|
images: [],
|
|
259
453
|
navLinks: [],
|
|
@@ -268,7 +462,10 @@ function removeElements(html, tags) {
|
|
|
268
462
|
if (PASSWORD_INPUT.test(source)) return _extends({}, empty, {
|
|
269
463
|
loginForm: true
|
|
270
464
|
});
|
|
271
|
-
|
|
465
|
+
// Read before scripts go: the structured data is a script's body.
|
|
466
|
+
const organization = readOrganization(source);
|
|
467
|
+
const icons = readIcons(source, pageUrl);
|
|
468
|
+
source = removeElements(readPictures(source), REMOVED_WITH_CONTENT).replace(REMOVED_VOID, ' ');
|
|
272
469
|
// Forms next, so a field list is read before the controls go.
|
|
273
470
|
const forms = takeForms(source);
|
|
274
471
|
source = removeElements(forms.html, FORM_CONTROLS);
|
|
@@ -284,6 +481,8 @@ function removeElements(html, tags) {
|
|
|
284
481
|
const titleMatch = /<title\b[^>]*>([\s\S]*?)<\/title\s*>/i.exec(source);
|
|
285
482
|
const title = clip(collapse(decodeHtmlEntities((_ref = titleMatch == null ? void 0 : titleMatch[1]) != null ? _ref : '')) || metaContent('og:title', 'property'), 300);
|
|
286
483
|
const description = clip(metaContent('description', 'name') || metaContent('og:description', 'property'), 1000);
|
|
484
|
+
const ogImage = (_ref1 = (_resolveSiteUrl = resolveSiteUrl(metaContent('og:image', 'property') || metaContent('og:image:url', 'property') || metaContent('twitter:image', 'name'), pageUrl)) == null ? void 0 : _resolveSiteUrl.toString()) != null ? _ref1 : null;
|
|
485
|
+
const siteName = clip(metaContent('og:site_name', 'property') || metaContent('application-name', 'name'), 120);
|
|
287
486
|
const htmlTag = /<html\b((?:[^>"']|"[^"]*"|'[^']*')*)>/i.exec(source);
|
|
288
487
|
const lang = htmlTag ? ((_parseAttributes_lang = parseAttributes(htmlTag[1])['lang']) == null ? void 0 : _parseAttributes_lang.trim()) || null : null;
|
|
289
488
|
let canonical = null;
|
|
@@ -291,9 +490,9 @@ function removeElements(html, tags) {
|
|
|
291
490
|
var _attributes_rel;
|
|
292
491
|
const attributes = parseAttributes(match[1]);
|
|
293
492
|
if (((_attributes_rel = attributes['rel']) != null ? _attributes_rel : '').toLowerCase().split(/\s+/).includes('canonical')) {
|
|
294
|
-
var
|
|
295
|
-
var
|
|
296
|
-
canonical = (
|
|
493
|
+
var _ref2;
|
|
494
|
+
var _resolveSiteUrl1;
|
|
495
|
+
canonical = (_ref2 = (_resolveSiteUrl1 = resolveSiteUrl(attributes['href'], pageUrl)) == null ? void 0 : _resolveSiteUrl1.toString()) != null ? _ref2 : null;
|
|
297
496
|
break;
|
|
298
497
|
}
|
|
299
498
|
}
|
|
@@ -306,47 +505,90 @@ function removeElements(html, tags) {
|
|
|
306
505
|
const navLinks = [];
|
|
307
506
|
const links = [];
|
|
308
507
|
const linkSeen = new Set();
|
|
508
|
+
let brand = null;
|
|
309
509
|
let navDepth = 0;
|
|
310
510
|
let headerDepth = 0;
|
|
311
511
|
let footerDepth = 0;
|
|
312
512
|
let current = null;
|
|
313
513
|
let buffer = '';
|
|
314
|
-
let anchor = null;
|
|
514
|
+
/** The link being read: its menu address (http(s) only), where it goes, its words and its first picture. */ let anchor = null;
|
|
515
|
+
/** The links read into the block being built, written after it. */ let pending = [];
|
|
315
516
|
const region = ()=>footerDepth > 0 ? 'footer' : headerDepth > 0 ? 'header' : 'main';
|
|
316
517
|
// A site without a <nav> keeps its menu in the header; its links are the nav.
|
|
317
518
|
const inNav = ()=>navDepth > 0 || !hasNav && headerDepth > 0;
|
|
519
|
+
const room = ()=>blocks.length < SITE_IMPORT_PAGE_LIMITS.blocks;
|
|
318
520
|
const flush = ()=>{
|
|
319
521
|
var _ref;
|
|
320
|
-
const
|
|
522
|
+
const raw = buffer;
|
|
523
|
+
const written = pending;
|
|
321
524
|
buffer = '';
|
|
525
|
+
pending = [];
|
|
526
|
+
if (anchor && collapse(decodeHtmlEntities(raw.slice(anchor.start)))) anchor.crossed = true;
|
|
527
|
+
if (anchor) anchor.start = 0;
|
|
322
528
|
const kind = (_ref = current == null ? void 0 : current.kind) != null ? _ref : 'paragraph';
|
|
323
529
|
const level = current == null ? void 0 : current.level;
|
|
530
|
+
const loose = current === null;
|
|
324
531
|
current = null;
|
|
325
|
-
if (
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
blocks.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
532
|
+
if (navDepth > 0) return;
|
|
533
|
+
const text = clip(collapse(decodeHtmlEntities(raw)), SITE_IMPORT_PAGE_LIMITS.blockChars);
|
|
534
|
+
// Loose words that are only links (a row of buttons, an address in a
|
|
535
|
+
// card) are those links, each a block of its own; words around a link
|
|
536
|
+
// keep their block, and the link follows it.
|
|
537
|
+
let rest = raw;
|
|
538
|
+
for (const link of [
|
|
539
|
+
...written
|
|
540
|
+
].sort((a, b)=>b.start - a.start))rest = `${rest.slice(0, link.start)}${rest.slice(link.end)}`;
|
|
541
|
+
const onlyLinks = loose && written.length > 0 && !collapse(decodeHtmlEntities(rest));
|
|
542
|
+
if (text && !onlyLinks && room()) {
|
|
543
|
+
const last = blocks[blocks.length - 1];
|
|
544
|
+
if (!(last && last.kind !== 'image' && last.kind !== 'link' && last.text === text)) {
|
|
545
|
+
if (kind === 'heading') {
|
|
546
|
+
blocks.push({
|
|
547
|
+
kind,
|
|
548
|
+
level: level != null ? level : 2,
|
|
549
|
+
text,
|
|
550
|
+
region: region()
|
|
551
|
+
});
|
|
552
|
+
} else {
|
|
553
|
+
blocks.push({
|
|
554
|
+
kind,
|
|
555
|
+
text,
|
|
556
|
+
region: region()
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
for (const link of written){
|
|
562
|
+
if (!room()) break;
|
|
563
|
+
const last = blocks[blocks.length - 1];
|
|
564
|
+
if ((last == null ? void 0 : last.kind) === 'link' && last.href === link.href && last.text === link.text) continue;
|
|
565
|
+
const inline = !onlyLinks || link.crossed;
|
|
566
|
+
blocks.push(_extends({
|
|
567
|
+
kind: 'link',
|
|
568
|
+
text: link.text,
|
|
569
|
+
href: link.href,
|
|
570
|
+
region: link.region
|
|
571
|
+
}, inline ? {
|
|
572
|
+
inline: true
|
|
573
|
+
} : {}));
|
|
341
574
|
}
|
|
342
575
|
};
|
|
576
|
+
/** A boundary between two runs of words: in the block, and in the link being read. */ const boundary = (block)=>{
|
|
577
|
+
buffer += RUN_BREAK;
|
|
578
|
+
if (!anchor) return;
|
|
579
|
+
anchor.text += RUN_BREAK;
|
|
580
|
+
if (block && anchor.lines[anchor.lines.length - 1] !== '') anchor.lines.push('');
|
|
581
|
+
};
|
|
343
582
|
for (const match of body.matchAll(new RegExp(`${TAG.source}|([^<]+)|<`, 'g'))){
|
|
344
583
|
var _match_;
|
|
345
584
|
const text = match[4];
|
|
346
585
|
if (text !== undefined || match[2] === undefined) {
|
|
347
586
|
const run = text != null ? text : '<';
|
|
348
587
|
buffer += run;
|
|
349
|
-
if (anchor)
|
|
588
|
+
if (anchor) {
|
|
589
|
+
anchor.text += run;
|
|
590
|
+
anchor.lines[anchor.lines.length - 1] += run;
|
|
591
|
+
}
|
|
350
592
|
continue;
|
|
351
593
|
}
|
|
352
594
|
const closing = match[1] === '/';
|
|
@@ -355,22 +597,51 @@ function removeElements(html, tags) {
|
|
|
355
597
|
if (tag === 'a') {
|
|
356
598
|
if (closing) {
|
|
357
599
|
if (anchor) {
|
|
358
|
-
|
|
359
|
-
|
|
600
|
+
var _anchor_lines_find;
|
|
601
|
+
const label = clip(collapse(decodeHtmlEntities(anchor.text)), 300);
|
|
602
|
+
const firstLine = clip(collapse(decodeHtmlEntities((_anchor_lines_find = anchor.lines.find((line)=>collapse(decodeHtmlEntities(line)))) != null ? _anchor_lines_find : '')), 120);
|
|
603
|
+
if (inNav() && anchor.url && firstLine && navLinks.length < SITE_IMPORT_PAGE_LIMITS.navLinks) {
|
|
360
604
|
navLinks.push({
|
|
361
|
-
text:
|
|
605
|
+
text: firstLine,
|
|
362
606
|
url: anchor.url.toString()
|
|
363
607
|
});
|
|
364
608
|
}
|
|
609
|
+
if (!brand && anchor.region !== 'footer' && (headerDepth > 0 || navDepth > 0) && anchor.url && isSameSite(anchor.url) && anchor.url.pathname === '/') {
|
|
610
|
+
brand = {
|
|
611
|
+
logo: anchor.image,
|
|
612
|
+
text: label,
|
|
613
|
+
region: anchor.region
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
if (!inNav() && anchor.href && label) {
|
|
617
|
+
pending.push({
|
|
618
|
+
text: label,
|
|
619
|
+
href: anchor.href,
|
|
620
|
+
start: anchor.start,
|
|
621
|
+
end: buffer.length,
|
|
622
|
+
crossed: anchor.crossed,
|
|
623
|
+
region: anchor.region
|
|
624
|
+
});
|
|
625
|
+
}
|
|
365
626
|
anchor = null;
|
|
366
627
|
}
|
|
628
|
+
boundary(false);
|
|
367
629
|
continue;
|
|
368
630
|
}
|
|
631
|
+
boundary(false);
|
|
369
632
|
const url = resolveSiteUrl(attributes['href'], pageUrl);
|
|
370
|
-
anchor =
|
|
633
|
+
anchor = {
|
|
371
634
|
url,
|
|
372
|
-
|
|
373
|
-
|
|
635
|
+
href: resolveSiteLink(attributes['href'], pageUrl),
|
|
636
|
+
text: '',
|
|
637
|
+
lines: [
|
|
638
|
+
''
|
|
639
|
+
],
|
|
640
|
+
image: null,
|
|
641
|
+
start: buffer.length,
|
|
642
|
+
crossed: false,
|
|
643
|
+
region: region()
|
|
644
|
+
};
|
|
374
645
|
if (url && isSameSite(url) && links.length < SITE_IMPORT_PAGE_LIMITS.links) {
|
|
375
646
|
const key = url.toString();
|
|
376
647
|
if (!linkSeen.has(key)) {
|
|
@@ -382,27 +653,31 @@ function removeElements(html, tags) {
|
|
|
382
653
|
}
|
|
383
654
|
if (tag === 'img' && !closing) {
|
|
384
655
|
const url = resolveSiteUrl(attributes['src'] || attributes['data-src'] || firstSrcset(attributes['srcset'] || attributes['data-srcset']), pageUrl);
|
|
656
|
+
boundary(false);
|
|
385
657
|
if (url && images.length < SITE_IMPORT_PAGE_LIMITS.images) {
|
|
386
658
|
var _attributes_alt;
|
|
387
659
|
const key = url.toString();
|
|
388
660
|
const alt = clip(collapse((_attributes_alt = attributes['alt']) != null ? _attributes_alt : ''), 300);
|
|
661
|
+
if (anchor && !anchor.image) anchor.image = {
|
|
662
|
+
url: key,
|
|
663
|
+
alt
|
|
664
|
+
};
|
|
389
665
|
if (!imageSeen.has(key)) {
|
|
390
666
|
imageSeen.add(key);
|
|
391
667
|
images.push({
|
|
392
668
|
url: key,
|
|
393
669
|
alt,
|
|
394
|
-
sameSite: isSameSite(url)
|
|
670
|
+
sameSite: isSameSite(url),
|
|
671
|
+
region: region()
|
|
395
672
|
});
|
|
396
673
|
if (navDepth === 0) {
|
|
397
674
|
flush();
|
|
398
|
-
if (blocks.
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
});
|
|
405
|
-
}
|
|
675
|
+
if (room()) blocks.push({
|
|
676
|
+
kind: 'image',
|
|
677
|
+
url: key,
|
|
678
|
+
alt,
|
|
679
|
+
region: region()
|
|
680
|
+
});
|
|
406
681
|
}
|
|
407
682
|
}
|
|
408
683
|
}
|
|
@@ -410,6 +685,7 @@ function removeElements(html, tags) {
|
|
|
410
685
|
}
|
|
411
686
|
if (tag === 'nav' || tag === 'header' || tag === 'footer') {
|
|
412
687
|
flush();
|
|
688
|
+
boundary(true);
|
|
413
689
|
const step = closing ? -1 : 1;
|
|
414
690
|
if (tag === 'nav') navDepth = Math.max(0, navDepth + step);
|
|
415
691
|
if (tag === 'header') headerDepth = Math.max(0, headerDepth + step);
|
|
@@ -420,9 +696,11 @@ function removeElements(html, tags) {
|
|
|
420
696
|
if (heading || BLOCK_TAGS[tag]) {
|
|
421
697
|
if (closing) {
|
|
422
698
|
if ((current == null ? void 0 : current.tag) === tag) flush();
|
|
699
|
+
boundary(true);
|
|
423
700
|
continue;
|
|
424
701
|
}
|
|
425
702
|
flush();
|
|
703
|
+
boundary(true);
|
|
426
704
|
current = heading ? {
|
|
427
705
|
kind: 'heading',
|
|
428
706
|
level: Number(heading[1]),
|
|
@@ -436,10 +714,14 @@ function removeElements(html, tags) {
|
|
|
436
714
|
if (BREAK_TAGS.has(tag)) {
|
|
437
715
|
if (tag === 'br' && current) {
|
|
438
716
|
buffer += ' ';
|
|
717
|
+
if (anchor) anchor.text += ' ';
|
|
439
718
|
continue;
|
|
440
719
|
}
|
|
441
720
|
flush();
|
|
721
|
+
boundary(true);
|
|
722
|
+
continue;
|
|
442
723
|
}
|
|
724
|
+
if (RUN_BOUNDARY.has(tag)) boundary(false);
|
|
443
725
|
}
|
|
444
726
|
flush();
|
|
445
727
|
return {
|
|
@@ -447,6 +729,11 @@ function removeElements(html, tags) {
|
|
|
447
729
|
description,
|
|
448
730
|
lang,
|
|
449
731
|
canonical,
|
|
732
|
+
ogImage,
|
|
733
|
+
siteName,
|
|
734
|
+
icons,
|
|
735
|
+
brand,
|
|
736
|
+
organization,
|
|
450
737
|
blocks,
|
|
451
738
|
images,
|
|
452
739
|
navLinks,
|