@scalemule/news-publication 0.0.93 → 0.0.94

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.
@@ -354,7 +354,12 @@ function headlineProblems(fields) {
354
354
  problems.push(`${name} must be a string`);
355
355
  return;
356
356
  }
357
+ if (!value.trim()) {
358
+ problems.push(`${name} is blank; omit it to fall back to the title`);
359
+ return;
360
+ }
357
361
  if (headlineLength(value.trim()) > max) problems.push(`${name} exceeds ${max} characters`);
362
+ if (/[\p{Cc}]/u.test(value)) problems.push(`${name} contains control characters`);
358
363
  };
359
364
  check("headline", fields.headline, HEADLINE_MAX_CHARS);
360
365
  check("fly_title", fields.fly_title, FLY_TITLE_MAX_CHARS);
@@ -506,6 +511,13 @@ async function runArticleEdit(t, input) {
506
511
  throw error;
507
512
  })
508
513
  ]);
514
+ const headlineKeys = ["headline", "fly_title", "seo_title"].filter((key) => key in input.patch);
515
+ if (headlineKeys.length && options.capabilities?.headline_set !== true)
516
+ throw new ArticleEditOperationError(
517
+ "CAPABILITY_UNAVAILABLE",
518
+ "preflight",
519
+ `The server must expose headline_set before editing ${headlineKeys.join(", ")}; no article was changed`
520
+ );
509
521
  if (options.workflow_version !== 1 || options.capabilities?.versioned_editorial_fields !== true)
510
522
  throw new ArticleEditOperationError(
511
523
  "CAPABILITY_UNAVAILABLE",
@@ -208,6 +208,8 @@ type ArticleWorkflowOptions = {
208
208
  prioritized_narration_processing?: boolean;
209
209
  content_first_editor_document?: boolean;
210
210
  versioned_editorial_fields?: boolean;
211
+ /** The server stores headline, fly_title and seo_title on the versioned document. */
212
+ headline_set?: boolean;
211
213
  versioned_editorial_tags?: boolean;
212
214
  managed_submission_version?: number | null;
213
215
  multipart_submission?: boolean;
@@ -208,6 +208,8 @@ type ArticleWorkflowOptions = {
208
208
  prioritized_narration_processing?: boolean;
209
209
  content_first_editor_document?: boolean;
210
210
  versioned_editorial_fields?: boolean;
211
+ /** The server stores headline, fly_title and seo_title on the versioned document. */
212
+ headline_set?: boolean;
211
213
  versioned_editorial_tags?: boolean;
212
214
  managed_submission_version?: number | null;
213
215
  multipart_submission?: boolean;
@@ -1,4 +1,4 @@
1
- import { headlineProblems } from './chunk-SNQKSUWB.js';
1
+ import { headlineProblems } from './chunk-7SAFGLPH.js';
2
2
 
3
3
  // src/article-photo-verification.ts
4
4
  function articlePhotoHtmlMatches(html, lead, social) {
@@ -483,6 +483,13 @@ async function runArticleEdit(t, input) {
483
483
  throw error;
484
484
  })
485
485
  ]);
486
+ const headlineKeys = ["headline", "fly_title", "seo_title"].filter((key) => key in input.patch);
487
+ if (headlineKeys.length && options.capabilities?.headline_set !== true)
488
+ throw new ArticleEditOperationError(
489
+ "CAPABILITY_UNAVAILABLE",
490
+ "preflight",
491
+ `The server must expose headline_set before editing ${headlineKeys.join(", ")}; no article was changed`
492
+ );
486
493
  if (options.workflow_version !== 1 || options.capabilities?.versioned_editorial_fields !== true)
487
494
  throw new ArticleEditOperationError(
488
495
  "CAPABILITY_UNAVAILABLE",
@@ -15,7 +15,7 @@ function headlineSet(article, selection = {}) {
15
15
  const title = text(article.title) ?? "";
16
16
  const headline = text(display?.display_title) ?? text(article.headline) ?? title;
17
17
  return {
18
- flyTitle: text(article.fly_title) ?? text(article.section),
18
+ flyTitle: text(display?.fly_title) ?? text(article.fly_title) ?? text(article.section),
19
19
  headline,
20
20
  rubric: text(display?.deck) ?? text(article.subtitle),
21
21
  seoTitle: text(article.seo_title) ?? title,
@@ -30,7 +30,12 @@ function headlineProblems(fields) {
30
30
  problems.push(`${name} must be a string`);
31
31
  return;
32
32
  }
33
+ if (!value.trim()) {
34
+ problems.push(`${name} is blank; omit it to fall back to the title`);
35
+ return;
36
+ }
33
37
  if (headlineLength(value.trim()) > max) problems.push(`${name} exceeds ${max} characters`);
38
+ if (/[\p{Cc}]/u.test(value)) problems.push(`${name} contains control characters`);
34
39
  };
35
40
  check("headline", fields.headline, HEADLINE_MAX_CHARS);
36
41
  check("fly_title", fields.fly_title, FLY_TITLE_MAX_CHARS);
package/dist/index.cjs CHANGED
@@ -825,7 +825,7 @@ function headlineSet(article, selection = {}) {
825
825
  const title = text(article.title) ?? "";
826
826
  const headline = text(display?.display_title) ?? text(article.headline) ?? title;
827
827
  return {
828
- flyTitle: text(article.fly_title) ?? text(article.section),
828
+ flyTitle: text(display?.fly_title) ?? text(article.fly_title) ?? text(article.section),
829
829
  headline,
830
830
  rubric: text(display?.deck) ?? text(article.subtitle),
831
831
  seoTitle: text(article.seo_title) ?? title,
@@ -840,7 +840,12 @@ function headlineProblems(fields) {
840
840
  problems.push(`${name} must be a string`);
841
841
  return;
842
842
  }
843
+ if (!value.trim()) {
844
+ problems.push(`${name} is blank; omit it to fall back to the title`);
845
+ return;
846
+ }
843
847
  if (headlineLength(value.trim()) > max) problems.push(`${name} exceeds ${max} characters`);
848
+ if (/[\p{Cc}]/u.test(value)) problems.push(`${name} contains control characters`);
844
849
  };
845
850
  check("headline", fields.headline, HEADLINE_MAX_CHARS);
846
851
  check("fly_title", fields.fly_title, FLY_TITLE_MAX_CHARS);
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ export { CookieSettingsView } from './chunk-PBRDIXQI.js';
8
8
  import { localPublications } from './chunk-NNEY6VBX.js';
9
9
  export { COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EDITORIAL_PRINCIPLES, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, NETWORK_PARENT, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, communityPublications, consentModel, decorateNetworkUrl, effectivePreferences, findPublicationByHost, findPublicationBySlug, getCookieConsent, getNetworkPublicationUrl, getNetworkReaderHandoffUrl, hasConsented, impliedPreferences, isCategoryAllowed, isGeoConsentRequired, listedPublications, localPublications, queryNetworkConsentHub, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub, topicalPublications } from './chunk-NNEY6VBX.js';
10
10
  export { THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, ThemeToggle, applyTheme, clearTheme, getStoredTheme, getSystemTheme, resolveEffectiveTheme, setTheme, subscribeTheme, themeInitScript } from './chunk-NQD3M7ZY.js';
11
- export { FLY_TITLE_MAX_CHARS, HEADLINE_MAX_CHARS, HEADLINE_RECOMMENDED_CHARS, RUBRIC_MAX_CHARS, SEO_TITLE_MAX_CHARS, headlineLength, headlineProblems, headlineSet } from './chunk-SNQKSUWB.js';
11
+ export { FLY_TITLE_MAX_CHARS, HEADLINE_MAX_CHARS, HEADLINE_RECOMMENDED_CHARS, RUBRIC_MAX_CHARS, SEO_TITLE_MAX_CHARS, headlineLength, headlineProblems, headlineSet } from './chunk-7SAFGLPH.js';
12
12
  export { communityMapEmbeds } from './chunk-IMYBSG7J.js';
13
13
  export { articleFormatLabel, articleHeroRatio, articlePresentation, formatArtworkCaption, parseDocumentBlocks, renderArticleBlocksToHtml, sanitizeInlineMarkdown } from './chunk-DN3TBZLK.js';
14
14
  import { definePublication } from './chunk-57YVL6BD.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/news-publication",
3
- "version": "0.0.93",
3
+ "version": "0.0.94",
4
4
  "description": "Shared server framework for ScaleMule local news publications. Walnut Creek Times is the broadsheet default; each paper supplies its own identity and feature flags.",
5
5
  "type": "module",
6
6
  "bin": {