@materializecms/server 0.5.0 → 0.6.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/CHANGELOG.md +15 -0
- package/package.json +4 -4
- package/src/ai.d.ts +6 -1
- package/src/ai.js +11 -3
- package/src/app.js +672 -21
- package/src/brief.d.ts +43 -0
- package/src/brief.js +172 -0
- package/src/studio.d.ts +13 -0
- package/src/studio.js +75 -0
- package/src/tools.d.ts +1 -0
- package/src/tools.js +57 -2
- package/src/wordpress.d.ts +2 -0
- package/src/wordpress.js +22 -5
- package/src/wpimport.d.ts +19 -1
- package/src/wpimport.js +112 -3
package/src/wpimport.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { slugify } from '@materializecms/core';
|
|
2
|
-
import { htmlToPage, htmlToRichText,
|
|
2
|
+
import { htmlToPage, htmlToRichText, imageAltsIn, mainContent, originalOf, plain, } from './wordpress.js';
|
|
3
3
|
/** Which sources fit a field of this kind. */
|
|
4
4
|
export function sourcesFor(f) {
|
|
5
5
|
switch (f.kind) {
|
|
@@ -113,6 +113,7 @@ export async function runImport(site, plan, deps) {
|
|
|
113
113
|
problems: [],
|
|
114
114
|
media: { downloaded: 0, failed: 0, reasons: {} },
|
|
115
115
|
redirects: 0,
|
|
116
|
+
relaid: 0,
|
|
116
117
|
};
|
|
117
118
|
const report = () => deps.onProgress({ ...progress });
|
|
118
119
|
const targetOf = (item) => (item.kind === 'post' ? plan.posts : plan.pages);
|
|
@@ -136,8 +137,11 @@ export async function runImport(site, plan, deps) {
|
|
|
136
137
|
const t = targetOf(item);
|
|
137
138
|
const fields = Object.values(t.fields);
|
|
138
139
|
if (fields.includes('content'))
|
|
139
|
-
for (const src of
|
|
140
|
-
|
|
140
|
+
for (const [src, alt] of imageAltsIn(item.html)) {
|
|
141
|
+
const url = new URL(src, site.url).href;
|
|
142
|
+
if (!wanted.get(url))
|
|
143
|
+
wanted.set(url, alt);
|
|
144
|
+
}
|
|
141
145
|
if (fields.includes('featured') && item.featured?.url)
|
|
142
146
|
wanted.set(new URL(item.featured.url, site.url).href, item.featured.alt);
|
|
143
147
|
}
|
|
@@ -217,6 +221,8 @@ export async function runImport(site, plan, deps) {
|
|
|
217
221
|
const moves = [];
|
|
218
222
|
// Addresses taken by this import: WordPress allows the same slug under different parent pages.
|
|
219
223
|
const taken = new Map();
|
|
224
|
+
// Canvas pages filled from the content: the ones the AI may lay out again.
|
|
225
|
+
const canvas = [];
|
|
220
226
|
for (const entry of plans) {
|
|
221
227
|
const { item, t, def } = entry;
|
|
222
228
|
let { slug, url } = entry;
|
|
@@ -268,6 +274,8 @@ export async function runImport(site, plan, deps) {
|
|
|
268
274
|
progress.problems.push({ title: doc.title, reason: 'importato come bozza: ha campi da completare' });
|
|
269
275
|
}
|
|
270
276
|
progress.created.push({ id: doc.id, collection: def.name, title: doc.title, published });
|
|
277
|
+
if (Object.entries(t.fields).some(([name, source]) => source === 'content' && def.fields[name]?.kind === 'page'))
|
|
278
|
+
canvas.push({ id: doc.id, collection: def.name, title: doc.title });
|
|
271
279
|
const from = pathOf(item.link, site.url);
|
|
272
280
|
if (published && from && url && from !== url)
|
|
273
281
|
moves.push({ from, to: url });
|
|
@@ -282,6 +290,31 @@ export async function runImport(site, plan, deps) {
|
|
|
282
290
|
}
|
|
283
291
|
if (plan.redirects && moves.length)
|
|
284
292
|
progress.redirects = await deps.addRedirects(moves);
|
|
293
|
+
// 4. The AI lays the pages out again, one at a time, in their drafts: what WordPress had stays
|
|
294
|
+
// published until someone publishes the new layout.
|
|
295
|
+
if (plan.aiLayout && deps.relayout && canvas.length) {
|
|
296
|
+
progress.phase = 'layout';
|
|
297
|
+
progress.done = 0;
|
|
298
|
+
progress.total = canvas.length;
|
|
299
|
+
report();
|
|
300
|
+
for (const doc of canvas) {
|
|
301
|
+
try {
|
|
302
|
+
const why = await deps.relayout(doc);
|
|
303
|
+
if (why)
|
|
304
|
+
progress.problems.push({ title: doc.title, reason: `impaginazione semplice lasciata: ${why}` });
|
|
305
|
+
else
|
|
306
|
+
progress.relaid++;
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
progress.problems.push({
|
|
310
|
+
title: doc.title,
|
|
311
|
+
reason: `l’AI non ha rifatto l’impaginazione: ${error.message}`,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
progress.done++;
|
|
315
|
+
report();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
285
318
|
progress.phase = 'done';
|
|
286
319
|
report();
|
|
287
320
|
return progress;
|
|
@@ -350,3 +383,79 @@ async function valueFor(f, source, item, ctx, plan, site, relations, deps) {
|
|
|
350
383
|
}
|
|
351
384
|
}
|
|
352
385
|
export { fold };
|
|
386
|
+
/** The words of a page (texts and buttons), lowercase, three letters or more. */
|
|
387
|
+
function wordsOf(nodes) {
|
|
388
|
+
const out = new Set();
|
|
389
|
+
const go = (list) => {
|
|
390
|
+
for (const n of list) {
|
|
391
|
+
if (!n.hidden) {
|
|
392
|
+
const text = [n.text, ...(n.runs ?? []).map((r) => r.t)].filter(Boolean).join(' ');
|
|
393
|
+
for (const w of text.toLowerCase().match(/[\p{L}\p{N}]{3,}/gu) ?? [])
|
|
394
|
+
out.add(w);
|
|
395
|
+
}
|
|
396
|
+
if (n.children)
|
|
397
|
+
go(n.children);
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
go(nodes);
|
|
401
|
+
return out;
|
|
402
|
+
}
|
|
403
|
+
function imagesOf(nodes) {
|
|
404
|
+
const out = new Set();
|
|
405
|
+
const go = (list) => {
|
|
406
|
+
for (const n of list) {
|
|
407
|
+
if (n.type === 'image' && n.src && !n.hidden)
|
|
408
|
+
out.add(n.src);
|
|
409
|
+
if (n.bgImage?.src)
|
|
410
|
+
out.add(n.bgImage.src);
|
|
411
|
+
if (n.children)
|
|
412
|
+
go(n.children);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
go(nodes);
|
|
416
|
+
return out;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Whether a page laid out again by the AI still says what it said: no text lost, nothing made up
|
|
420
|
+
* (library sample texts left behind count as made up), every image still there. A reason when not.
|
|
421
|
+
*/
|
|
422
|
+
export function relayoutProblem(before, after) {
|
|
423
|
+
const was = wordsOf(before);
|
|
424
|
+
const now = wordsOf(after);
|
|
425
|
+
if (!now.size)
|
|
426
|
+
return 'la pagina sarebbe rimasta vuota';
|
|
427
|
+
const lost = [...was].filter((w) => !now.has(w)).length;
|
|
428
|
+
const added = [...now].filter((w) => !was.has(w)).length;
|
|
429
|
+
if (lost > Math.max(3, was.size * 0.1))
|
|
430
|
+
return 'l’AI avrebbe tolto parte del testo';
|
|
431
|
+
if (added > Math.max(8, was.size * 0.15))
|
|
432
|
+
return 'l’AI avrebbe aggiunto testi che non c’erano';
|
|
433
|
+
const pictures = imagesOf(after);
|
|
434
|
+
if ([...imagesOf(before)].some((src) => !pictures.has(src)))
|
|
435
|
+
return 'l’AI avrebbe tolto delle immagini';
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
/** Descriptions the page had for its images, put back where the new layout left them empty. */
|
|
439
|
+
export function restoreAlts(before, after) {
|
|
440
|
+
const alts = new Map();
|
|
441
|
+
const read = (list) => {
|
|
442
|
+
for (const n of list) {
|
|
443
|
+
if (n.type === 'image' && n.src && n.alt)
|
|
444
|
+
alts.set(n.src, n.alt);
|
|
445
|
+
if (n.bgImage?.src && n.bgImage.alt)
|
|
446
|
+
alts.set(n.bgImage.src, n.bgImage.alt);
|
|
447
|
+
if (n.children)
|
|
448
|
+
read(n.children);
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
read(before);
|
|
452
|
+
const fix = (n) => {
|
|
453
|
+
let out = n;
|
|
454
|
+
if (n.type === 'image' && n.src && !n.alt?.trim() && alts.has(n.src))
|
|
455
|
+
out = { ...out, alt: alts.get(n.src) };
|
|
456
|
+
if (n.bgImage?.src && !n.bgImage.alt?.trim() && alts.has(n.bgImage.src))
|
|
457
|
+
out = { ...out, bgImage: { ...n.bgImage, alt: alts.get(n.bgImage.src) ?? '' } };
|
|
458
|
+
return n.children ? { ...out, children: n.children.map(fix) } : out;
|
|
459
|
+
};
|
|
460
|
+
return after.map(fix);
|
|
461
|
+
}
|