@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/src/wpimport.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { slugify } from '@materializecms/core';
2
- import { htmlToPage, htmlToRichText, imagesIn, mainContent, originalOf, plain, } from './wordpress.js';
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 imagesIn(item.html))
140
- wanted.set(new URL(src, site.url).href, '');
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
+ }