@scalemule/news-publication 0.0.80 → 0.0.82

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.
@@ -12,6 +12,47 @@ function articlePhotoHtmlMatches(html, lead, social) {
12
12
  return image && openGraph;
13
13
  }
14
14
 
15
+ // src/article-edit-verification.ts
16
+ var decode = (value) => value.replace(/&(?:amp|lt|gt|quot|apos|#39|#\d+|#x[\da-f]+);/giu, (s) => {
17
+ const named = {
18
+ "&": "&",
19
+ "&lt;": "<",
20
+ "&gt;": ">",
21
+ "&quot;": '"',
22
+ "&apos;": "'",
23
+ "&#39;": "'"
24
+ };
25
+ if (named[s.toLowerCase()]) return named[s.toLowerCase()];
26
+ const hex = s.toLowerCase().startsWith("&#x");
27
+ const n = parseInt(s.slice(hex ? 3 : 2, -1), hex ? 16 : 10);
28
+ return Number.isFinite(n) && n >= 0 && n <= 1114111 ? String.fromCodePoint(n) : s;
29
+ });
30
+ var text = (html) => decode(html.replace(/<[^>]*>/gu, " ")).replace(/\s+/gu, " ").trim();
31
+ var plain = (value) => value.replace(/\s+/gu, " ").trim();
32
+ function articleEditHtmlMatches(html, expected) {
33
+ const visible = html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/giu, " ").replace(/<style\b[^>]*>[\s\S]*?<\/style>/giu, " ");
34
+ const headline = [...visible.matchAll(/<h1\b[^>]*>([\s\S]*?)<\/h1>/giu)].some(
35
+ (m) => text(m[1]) === plain(expected.title)
36
+ );
37
+ if (!headline) return false;
38
+ const paragraphs = [
39
+ ...expected.content_html.matchAll(
40
+ /<(p|li|h[2-6]|blockquote)\b[^>]*>([\s\S]*?)<\/\1>/giu
41
+ )
42
+ ].map((m) => text(m[2])).filter(Boolean);
43
+ const all = text(visible);
44
+ if (!paragraphs.length || !paragraphs.every((p) => all.includes(p)))
45
+ return false;
46
+ const titles = [
47
+ ...visible.matchAll(
48
+ /<aside\b[^>]*class=["'][^"']*\barticle-verified-card\b[^"']*["'][^>]*>([\s\S]*?)<\/aside>/giu
49
+ )
50
+ ].flatMap(
51
+ (m) => [...m[1].matchAll(/<h2\b[^>]*>([\s\S]*?)<\/h2>/giu)].map((m2) => text(m2[1]))
52
+ );
53
+ return JSON.stringify(titles.sort()) === JSON.stringify(expected.card_titles.map(plain).sort());
54
+ }
55
+
15
56
  // src/article-photo-operation.ts
16
57
  var ArticlePhotoOperationError = class extends Error {
17
58
  constructor(code, stage, message, fileId, status) {
@@ -287,6 +328,325 @@ async function runArticlePhoto(t, input) {
287
328
  }
288
329
  }
289
330
 
331
+ // src/article-edit-operation.ts
332
+ var ArticleEditOperationError = class extends Error {
333
+ constructor(code, stage, message, status, revisionId) {
334
+ super(message);
335
+ this.code = code;
336
+ this.stage = stage;
337
+ this.status = status;
338
+ this.revisionId = revisionId;
339
+ this.name = "ArticleEditOperationError";
340
+ }
341
+ code;
342
+ stage;
343
+ status;
344
+ revisionId;
345
+ };
346
+ var canonical2 = (v) => Array.isArray(v) ? v.map(canonical2) : v && typeof v === "object" ? Object.fromEntries(
347
+ Object.keys(v).sort().map((k) => [k, canonical2(v[k])])
348
+ ) : v;
349
+ var equal = (a, b) => JSON.stringify(canonical2(a)) === JSON.stringify(canonical2(b));
350
+ var digest2 = async (v) => [
351
+ ...new Uint8Array(
352
+ await crypto.subtle.digest(
353
+ "SHA-256",
354
+ new TextEncoder().encode(JSON.stringify(canonical2(v)))
355
+ )
356
+ )
357
+ ].map((v2) => v2.toString(16).padStart(2, "0")).join("");
358
+ var patchKeys = /* @__PURE__ */ new Set([
359
+ "title",
360
+ "subtitle",
361
+ "content_markdown",
362
+ "excerpt",
363
+ "section",
364
+ "community",
365
+ "format",
366
+ "layout",
367
+ "cards",
368
+ "citations"
369
+ ]);
370
+ function validate(input) {
371
+ if (!input.operationKey || !/^[\w.:-]{1,128}$/u.test(input.operationKey) || !input.articleId || !input.patch || typeof input.patch !== "object" || Array.isArray(input.patch) || !Object.keys(input.patch).length)
372
+ throw Error(
373
+ "Supply an article, stable operation key and nonempty editorial patch"
374
+ );
375
+ for (const [key, value] of Object.entries(input.patch)) {
376
+ if (!patchKeys.has(key)) throw Error("Unsupported edit field: " + key);
377
+ if (["cards", "citations"].includes(key)) {
378
+ if (!Array.isArray(value)) throw Error(key + " must be an array");
379
+ } else if (key === "layout") {
380
+ if (!value || typeof value !== "object" || typeof value.id !== "string" || !Number.isInteger(value.version))
381
+ throw Error("Select an existing versioned layout");
382
+ } else if (typeof value !== "string")
383
+ throw Error(key + " must be a string");
384
+ }
385
+ if (input.expectedGeneration !== void 0 && (!Number.isSafeInteger(input.expectedGeneration) || input.expectedGeneration < 1))
386
+ throw Error("Expected generation must be positive");
387
+ if (input.enableReviewPolicy && ![
388
+ "NONE",
389
+ "FACT_CHECK",
390
+ "CONTRIBUTOR_REVIEW",
391
+ "SUBJECT_APPROVAL_REQUIRED",
392
+ "SUBJECT_APPROVAL_PLUS_EDITOR"
393
+ ].includes(input.enableReviewPolicy))
394
+ throw Error("Select an explicit supported review policy");
395
+ if (new TextEncoder().encode(JSON.stringify(input.patch)).length > 128 * 1024)
396
+ throw Error("Editorial patch exceeds 128 KiB");
397
+ }
398
+ async function runArticleEdit(t, input) {
399
+ validate(input);
400
+ const started = performance.now();
401
+ let stage = "preflight";
402
+ const timeout = input.timeoutMs ?? 1e4;
403
+ if (!Number.isFinite(timeout) || timeout < 100 || timeout > 12e4)
404
+ throw Error("Edit deadline must be between 100 and 120000 milliseconds");
405
+ const signal = AbortSignal.timeout(timeout);
406
+ const request = (path, method, body, extra) => t.request(path, method, body, extra, signal);
407
+ const identity = await digest2([
408
+ t.publicationId,
409
+ input.articleId,
410
+ input.operationKey,
411
+ input.patch,
412
+ input.expectedGeneration ?? null,
413
+ input.enableReviewPolicy ?? null
414
+ ]);
415
+ if (input.checkpoint && (input.checkpoint.version !== 1 || input.checkpoint.identity !== identity || input.checkpoint.articleId !== input.articleId))
416
+ throw new ArticleEditOperationError(
417
+ "OPERATION_CONFLICT",
418
+ "validation",
419
+ "This checkpoint belongs to different input; reuse its original patch and operation key"
420
+ );
421
+ let state = input.checkpoint ? structuredClone(input.checkpoint) : { version: 1, identity, articleId: input.articleId, stage };
422
+ const save = async (next) => {
423
+ stage = next;
424
+ state.stage = next;
425
+ await input.saveCheckpoint(structuredClone(state));
426
+ };
427
+ const article = "/articles/" + encodeURIComponent(input.articleId);
428
+ const currentRevision = (record) => record.revisions?.find((v) => v.id === record.current_revision_id);
429
+ const identityOf = (post) => ({
430
+ slug: post.slug,
431
+ published_at: post.published_at ?? null,
432
+ canonical_url: post.canonical_url ?? null
433
+ });
434
+ const identityMatches = (post) => !state.sourceIdentity || state.sourceIdentity.slug === post.slug && (state.sourceIdentity.published_at === null || state.sourceIdentity.published_at === (post.published_at ?? null)) && (state.sourceIdentity.canonical_url === null || state.sourceIdentity.canonical_url === (post.canonical_url ?? null));
435
+ const result = (record, status) => ({
436
+ article_id: input.articleId,
437
+ revision_id: state.revisionId ?? record.current_revision_id,
438
+ generation: record.generation,
439
+ state: status,
440
+ editorial_status: record.status,
441
+ elapsed_ms: Math.round(performance.now() - started)
442
+ });
443
+ try {
444
+ const [options, source, initial] = await Promise.all([
445
+ request(
446
+ "/article-workflow/options?article_id=" + encodeURIComponent(input.articleId)
447
+ ),
448
+ request(article + "/document"),
449
+ request(article + "/collaboration").catch((error) => {
450
+ if (error.status === 404 && error.message === "Story collaboration is not enabled")
451
+ return null;
452
+ throw error;
453
+ })
454
+ ]);
455
+ if (options.workflow_version !== 1 || options.capabilities?.versioned_editorial_fields !== true)
456
+ throw new ArticleEditOperationError(
457
+ "CAPABILITY_UNAVAILABLE",
458
+ stage,
459
+ "The server must expose versioned_editorial_fields before this edit; no article was changed"
460
+ );
461
+ if (!identityMatches(source))
462
+ throw new ArticleEditOperationError(
463
+ "ARTICLE_IDENTITY_CHANGED",
464
+ stage,
465
+ "Article URL, publication date or canonical identity changed; inspect before continuing"
466
+ );
467
+ state.sourceIdentity ??= identityOf(source);
468
+ let record = initial;
469
+ if (!record) {
470
+ if (!input.enableReviewPolicy)
471
+ throw new ArticleEditOperationError(
472
+ "ENROLLMENT_REQUIRED",
473
+ stage,
474
+ "Legacy article: choose an explicit review policy for versioned editorial onboarding; no content was changed"
475
+ );
476
+ if (source.status === "scheduled")
477
+ throw new ArticleEditOperationError(
478
+ "SCHEDULED_ARTICLE",
479
+ stage,
480
+ "Onboarding cancels an old schedule; handle this scheduled article explicitly"
481
+ );
482
+ await save("enroll");
483
+ await request(article + "/collaboration/enable", "POST", {
484
+ review_policy: input.enableReviewPolicy
485
+ });
486
+ record = await request(article + "/collaboration");
487
+ }
488
+ if (state.revisionId) {
489
+ if (record.current_revision_id !== state.revisionId)
490
+ throw new ArticleEditOperationError(
491
+ "ARTICLE_CHANGED",
492
+ stage,
493
+ "Another revision is current; this operation cannot release or overwrite it",
494
+ 409,
495
+ state.revisionId
496
+ );
497
+ const selected = currentRevision(record);
498
+ if (!selected || !equal(selected.document, state.document))
499
+ throw new ArticleEditOperationError(
500
+ "VERIFICATION_CONFLICT",
501
+ stage,
502
+ "The saved revision differs from this operation",
503
+ 409,
504
+ state.revisionId
505
+ );
506
+ } else {
507
+ if (!state.document) {
508
+ if (input.expectedGeneration !== void 0 && record.generation !== input.expectedGeneration)
509
+ throw new ArticleEditOperationError(
510
+ "ARTICLE_CHANGED",
511
+ stage,
512
+ "The article generation changed; reload before applying this patch",
513
+ 409
514
+ );
515
+ const selected = currentRevision(record);
516
+ if (!selected?.complete_snapshot && selected?.complete_snapshot !== void 0)
517
+ throw new ArticleEditOperationError(
518
+ "INCOMPLETE_REVISION",
519
+ stage,
520
+ "Inspect this historical revision before editing"
521
+ );
522
+ if (!selected?.document)
523
+ throw Error("Current immutable article document is missing");
524
+ const effective = { ...selected.document };
525
+ for (const key of ["cards", "citations"])
526
+ if (effective[key] === void 0 && selected.editorial_metadata?.[key] !== void 0)
527
+ effective[key] = selected.editorial_metadata[key];
528
+ if (effective.excerpt === void 0 && source.excerpt !== null && source.excerpt !== void 0)
529
+ effective.excerpt = source.excerpt;
530
+ const changed = Object.entries(input.patch).some(
531
+ ([key, value]) => !equal(effective[key], value)
532
+ );
533
+ if (!changed) {
534
+ state.document = selected.document;
535
+ state.revisionId = record.current_revision_id;
536
+ state.savedGeneration = record.generation;
537
+ state.unchanged = true;
538
+ await save("verify");
539
+ if (!input.release) return result(record, "unchanged");
540
+ } else {
541
+ const document = { ...selected.document, ...input.patch };
542
+ if (document.excerpt === void 0 && source.excerpt !== null && source.excerpt !== void 0)
543
+ document.excerpt = source.excerpt;
544
+ state.document = document;
545
+ state.expectedGeneration = record.generation;
546
+ await save("save");
547
+ }
548
+ }
549
+ if (!state.revisionId) {
550
+ const saved = await request(
551
+ "/article-workflow",
552
+ "POST",
553
+ {
554
+ operation: "save",
555
+ article_id: input.articleId,
556
+ expected_generation: state.expectedGeneration,
557
+ document: state.document,
558
+ reason: state.sourceIdentity?.published_at ? "POST_PUBLICATION_CORRECTION" : "EDITOR_EDIT"
559
+ },
560
+ { "Idempotency-Key": input.operationKey }
561
+ );
562
+ state.revisionId = saved.revision_id;
563
+ state.savedGeneration = saved.generation;
564
+ await save("verify");
565
+ record = await request(article + "/collaboration");
566
+ if (record.current_revision_id !== state.revisionId || !equal(currentRevision(record)?.document, state.document))
567
+ throw new ArticleEditOperationError(
568
+ "ARTICLE_CHANGED",
569
+ stage,
570
+ "A different revision is current; no release was attempted",
571
+ 409,
572
+ state.revisionId
573
+ );
574
+ }
575
+ }
576
+ if (record.published_revision_id === state.revisionId) {
577
+ await save("published");
578
+ return result(record, "already_published");
579
+ }
580
+ if (!input.release) {
581
+ await save("saved");
582
+ return result(record, state.unchanged ? "unchanged" : "saved_for_review");
583
+ }
584
+ await save("release");
585
+ try {
586
+ await request(article + "/collaboration", "POST", {
587
+ action: "RELEASE",
588
+ expected_generation: record.generation,
589
+ scheduled_at: null
590
+ });
591
+ } catch (error) {
592
+ const checked = await request(article + "/collaboration");
593
+ if (checked.published_revision_id === state.revisionId && checked.current_revision_id === state.revisionId) {
594
+ record = checked;
595
+ } else throw error;
596
+ }
597
+ record = await request(article + "/collaboration");
598
+ if (record.current_revision_id !== state.revisionId || record.published_revision_id !== state.revisionId)
599
+ throw new ArticleEditOperationError(
600
+ "VERIFICATION_CONFLICT",
601
+ stage,
602
+ "This exact revision has not been confirmed published",
603
+ 409,
604
+ state.revisionId
605
+ );
606
+ const after = await request(article + "/document");
607
+ if (!identityMatches(after))
608
+ throw new ArticleEditOperationError(
609
+ "ARTICLE_IDENTITY_CHANGED",
610
+ stage,
611
+ "The release changed a permanent article identity",
612
+ 409,
613
+ state.revisionId
614
+ );
615
+ for (const key of ["title", "subtitle", "content_markdown", "excerpt"])
616
+ if (state.document?.[key] !== void 0 && !equal(after[key] ?? "", state.document[key]))
617
+ throw new ArticleEditOperationError(
618
+ "VERIFICATION_CONFLICT",
619
+ stage,
620
+ "Public article field differs after release: " + key,
621
+ 409,
622
+ state.revisionId
623
+ );
624
+ const normalizedCards = state.document?.cards;
625
+ if (normalizedCards && after.metadata?.cards?.length !== normalizedCards.length)
626
+ throw new ArticleEditOperationError(
627
+ "VERIFICATION_CONFLICT",
628
+ stage,
629
+ "Public card count differs from the saved revision",
630
+ 409,
631
+ state.revisionId
632
+ );
633
+ await save("published");
634
+ return result(record, "published");
635
+ } catch (error) {
636
+ if (error instanceof ArticleEditOperationError) throw error;
637
+ const status = error?.status;
638
+ const message = error instanceof Error ? error.message : "Article edit failed";
639
+ const code = signal.aborted ? "DEADLINE_EXCEEDED" : status === 401 || status === 403 ? "PERMISSION_DENIED" : status === 409 && stage === "release" ? "RELEASE_HELD" : status === 409 ? "ARTICLE_CHANGED" : "STEP_FAILED";
640
+ throw new ArticleEditOperationError(
641
+ code,
642
+ stage,
643
+ signal.aborted ? "Edit reached its deadline; resume the same patch, operation key and checkpoint. Success was not asserted." : message,
644
+ status,
645
+ state.revisionId
646
+ );
647
+ }
648
+ }
649
+
290
650
  // src/article-workflow-client.ts
291
651
  var ArticleWorkflowError = class extends Error {
292
652
  constructor(message, status, articleId) {
@@ -343,6 +703,9 @@ var PublicationArticleClient = class {
343
703
  `/article-workflow/options${articleId ? `?article_id=${encodeURIComponent(articleId)}` : ""}`
344
704
  );
345
705
  }
706
+ publicArticle(slug, signal) {
707
+ return this.request("/article-content/" + encodeURIComponent(slug), void 0, void 0, void 0, signal);
708
+ }
346
709
  /** Explicit approver-authorized onboarding for a legacy article; never called by get().
347
710
  * Existing schedules are cancelled by the server; public content remains unchanged.
348
711
  * Missing immutable media identities must be resolved through newsroom media controls.
@@ -371,6 +734,10 @@ var PublicationArticleClient = class {
371
734
  uploadAndReplacePublishedCover(input) {
372
735
  return runArticlePhoto({ publicationId: this.config.publicationId, apiUrl: this.config.apiUrl, fetch: this.config.fetch ?? fetch, request: this.request.bind(this) }, input);
373
736
  }
737
+ /** Save one exact revision; release is explicit and never manufactures approval. */
738
+ editArticle(input) {
739
+ return runArticleEdit({ publicationId: this.config.publicationId, request: this.request.bind(this) }, input);
740
+ }
374
741
  /** Replace a legacy publication cover using ready, measured public Photo renditions.
375
742
  * This never enrolls a story, publishes text, creates narration or retries a conflict.
376
743
  * Collaboration-managed stories retain their server-enforced immutable revision workflow.
@@ -533,141 +900,39 @@ var PublicationArticleClient = class {
533
900
  "If-Match": String(generation)
534
901
  });
535
902
  }
536
- /** One agent operation over existing owners. Stops at conflicts/unknown upload acknowledgement;
537
- * replay the same operation key to inspect/reuse staged contributions before another transfer.
538
- * Editorial approval and release are always separate explicit operations. */
539
- async submitArticle(input) {
540
- if (input.document.media?.length)
541
- throw new Error(
542
- "Pass file assets separately when creating an article package"
543
- );
544
- const capabilities = await this.options();
545
- if (capabilities.workflow_version !== 1)
546
- throw new Error("Unsupported article workflow version");
547
- if (input.assets?.length && !capabilities.capabilities.managed_private_asset_upload)
548
- throw new Error("Managed article upload is not deployed");
549
- const created = await this.create(
550
- input.operationKey,
551
- input.authorProfileId,
552
- input.document,
553
- input.reviewPolicy
554
- );
555
- try {
556
- const selected = [];
557
- for (const asset of input.assets ?? []) {
558
- let current = await this.get(created.article_id);
559
- const digest2 = Array.from(
560
- new Uint8Array(
561
- await crypto.subtle.digest(
562
- "SHA-256",
563
- await asset.file.arrayBuffer()
564
- )
565
- )
566
- ).map((v) => v.toString(16).padStart(2, "0")).join("");
567
- const existing = current.contributions.find(
568
- (c) => c.kind === "MEDIA" && c.state !== "REJECTED" && c.state !== "WITHDRAWN" && c.payload.sha256 === digest2 && Object.entries(asset.metadata).every(
569
- ([k, v]) => c.payload[k] === v
570
- )
571
- );
572
- const staged = existing?.payload.file_id ? { contribution_id: existing.id, file_id: existing.payload.file_id } : await this.upload(
573
- created.article_id,
574
- current.generation,
575
- asset.file,
576
- asset.filename,
577
- asset.metadata
578
- );
579
- await this.registerPhoto(staged.file_id);
580
- if (input.approveMedia) {
581
- if (!asset.metadata.rights_confirmed || asset.metadata.private_preview_only)
582
- throw new Error("Publication requires confirmed image rights");
583
- if (existing?.state !== "APPROVED") {
584
- current = await this.get(created.article_id);
585
- await this.command(created.article_id, current.generation, {
586
- action: "RESOLVE",
587
- contribution_id: staged.contribution_id,
588
- state: "APPROVED"
589
- });
590
- }
591
- selected.push({
592
- kind: "image",
593
- contribution_id: staged.contribution_id,
594
- file_id: staged.file_id,
595
- caption: asset.metadata.caption,
596
- credit: asset.metadata.credit,
597
- alt: asset.metadata.alt,
598
- position: asset.position
599
- });
600
- }
601
- }
602
- if (selected.length) {
603
- const current = await this.get(created.article_id);
604
- const document = { ...input.document, media: selected };
605
- const saved = current.revisions.find(
606
- (r) => r.id === current.current_revision_id
607
- )?.document;
608
- if (!saved) throw new Error("Current immutable revision is missing");
609
- if (sameDocument(saved, document))
610
- return {
611
- ...created,
612
- revision_id: current.current_revision_id,
613
- generation: current.generation,
614
- editorial_status: current.status,
615
- replayed: true
616
- };
617
- if (!sameDocument(
618
- { ...saved, media: [] },
619
- { ...input.document, media: [] }
620
- ) || saved.media?.length)
621
- throw new ArticleWorkflowError(
622
- "The article was edited after this package began; load its current revision before changing it",
623
- 409,
624
- created.article_id
625
- );
626
- return await this.save(
627
- `${input.operationKey}:assets`,
628
- created.article_id,
629
- current.generation,
630
- document,
631
- "MEDIA_UPDATE"
632
- );
633
- }
634
- return created;
635
- } catch (error) {
636
- if (error instanceof ArticleWorkflowError) {
637
- error.articleId = created.article_id;
638
- throw error;
639
- }
640
- throw new ArticleWorkflowError(
641
- error instanceof Error ? error.message : "Article package interrupted",
642
- 0,
643
- created.article_id
644
- );
645
- }
903
+ /** One server-owned submission. No client-side Photo/TTS orchestration and no
904
+ * fallback to a different workflow. A 202 receipt is acceptance, not readiness.
905
+ * Replay the identical manifest/files/key after an uncertain acknowledgement.
906
+ */
907
+ submit(manifest, operationKey, files = [], signal) {
908
+ if (!operationKey) throw new Error("A stable operation key is required");
909
+ if (files.length > 10 || files.some((f) => f.file.size < 1 || f.file.size > 25 * 1024 * 1024) || files.reduce((n, f) => n + f.file.size, 0) > 50 * 1024 * 1024) throw new Error("Supply up to ten photographs, at most 25 MiB each and 50 MiB total");
910
+ const slots = new Set(files.map((f) => f.slot));
911
+ if (slots.size !== files.length || files.length !== (manifest.assets?.length ?? 0) || manifest.assets?.some((a) => !slots.has(a.slot))) throw new Error("Supply one file for each declared asset slot");
912
+ if (!files.length) return this.request("/submissions", "POST", manifest, { "Idempotency-Key": operationKey }, signal);
913
+ const form = new FormData();
914
+ form.append("manifest", JSON.stringify(manifest));
915
+ for (const asset of files) form.append(asset.slot, asset.file, asset.filename);
916
+ return this.request("/submissions", "POST", form, { "Idempotency-Key": operationKey }, signal);
917
+ }
918
+ submission(submissionId, signal) {
919
+ return this.request("/submissions/" + encodeURIComponent(submissionId), "GET", void 0, void 0, signal);
920
+ }
921
+ submitArticle(input) {
922
+ if (input.document.media?.length) throw new Error("Pass file assets separately when creating an article package");
923
+ const assets = (input.assets ?? []).map((a, i) => ({ ...a, slot: "asset_" + String.fromCharCode(97 + i) }));
924
+ return this.submit({
925
+ article: { operation: "create", author_profile_id: input.authorProfileId, document: input.document, review_policy: input.reviewPolicy },
926
+ intent: input.intent ?? "draft",
927
+ approve_media: input.approveMedia,
928
+ assets: assets.map((a) => ({ slot: a.slot, position: a.position, metadata: a.metadata }))
929
+ }, input.operationKey, assets);
646
930
  }
647
931
  };
648
- function sameDocument(a, b) {
649
- const canonical2 = (d) => [
650
- d.title,
651
- d.subtitle || "",
652
- d.content_markdown,
653
- d.section,
654
- d.community || "",
655
- d.format || null,
656
- d.layout ? [d.layout.id, d.layout.version] : null,
657
- (d.media || []).map((m) => [
658
- m.kind || "image",
659
- m.contribution_id,
660
- m.file_id,
661
- m.caption,
662
- m.credit,
663
- m.alt,
664
- m.position
665
- ])
666
- ];
667
- return JSON.stringify(canonical2(a)) === JSON.stringify(canonical2(b));
668
- }
669
932
 
933
+ exports.ArticleEditOperationError = ArticleEditOperationError;
670
934
  exports.ArticlePhotoOperationError = ArticlePhotoOperationError;
671
935
  exports.ArticleWorkflowError = ArticleWorkflowError;
672
936
  exports.PublicationArticleClient = PublicationArticleClient;
937
+ exports.articleEditHtmlMatches = articleEditHtmlMatches;
673
938
  exports.articlePhotoHtmlMatches = articlePhotoHtmlMatches;