@taprootio/site-authoring 0.26.0 → 0.27.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taprootio/site-authoring",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "description": "Taproot site authoring CLI: sign in once in the browser, then pull, validate, push, preview, approve, and deploy a site from the terminal",
5
5
  "type": "module",
6
6
  "license": "ISC",
@@ -1,5 +1,19 @@
1
1
  {
2
2
  "entries": [
3
+ {
4
+ "version": "0.27.0",
5
+ "note": "Embedded forms inherit configurable background, border and shadow defaults from the site, with per-form overrides.",
6
+ "behavior": [
7
+ "Form definitions accept sparse presentation.background, presentation.border and presentation.shadow boolean overrides. Omitted values inherit the site; explicit false disables the decoration. An empty presentation object is omitted by the form editor.",
8
+ "Site publishing preferences expose formBoxBackground, formBoxBorder and formBoxShadow. Background and shadow default on; border defaults off. Stage publishing preferences and the form version to apply appearance changes.",
9
+ "Forms pull and push preserve appearance overrides, including explicit false.",
10
+ "The help reference version is 44."
11
+ ],
12
+ "help": [
13
+ "forms",
14
+ "settings"
15
+ ]
16
+ },
3
17
  {
4
18
  "version": "0.26.0",
5
19
  "note": "marketing pull and marketing push keep a site's campaigns and placements as files, and link build prints a tracked address, so an agent can run a site's attribution over the CLI.",
@@ -22,6 +22,17 @@
22
22
  "type": "string",
23
23
  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
24
24
  },
25
+ "presentation": {
26
+ "description": "Sparse embedded-form appearance choices. Omitted properties inherit the site's staged defaults; explicit false disables a choice. Omit the object when all choices inherit.",
27
+ "type": "object",
28
+ "minProperties": 1,
29
+ "additionalProperties": false,
30
+ "properties": {
31
+ "background": { "type": "boolean" },
32
+ "border": { "type": "boolean" },
33
+ "shadow": { "type": "boolean" }
34
+ }
35
+ },
25
36
  "submit_label": {
26
37
  "description": "The text on the submit button. Omit it for Taproot's default. Must contain more than whitespace; the validator also rejects a blank label, which a schema cannot express.",
27
38
  "type": "string",
@@ -106,7 +106,7 @@ export function contactFieldId(definition) {
106
106
  const emails = definition.fields.filter((field) => field.type === "email");
107
107
  return emails.length === 1 ? emails[0].id : undefined;
108
108
  }
109
- const DEFINITION_KEYS = ["fields", "contact_field", "submit_label", "after_submit", "newsletter_audience_id"];
109
+ const DEFINITION_KEYS = ["fields", "contact_field", "submit_label", "after_submit", "newsletter_audience_id", "presentation"];
110
110
  const BASE_FIELD_KEYS = ["id", "type", "label", "hint"];
111
111
  const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
112
112
  const isInteger = (value) => typeof value === "number" && Number.isInteger(value);
@@ -427,6 +427,22 @@ export function validateDefinition(definition) {
427
427
  }
428
428
  /** The button text and the after-submit choice, which sit beside the fields in a definition. */
429
429
  function validateFormSettings(definition, errors) {
430
+ if ("presentation" in definition) {
431
+ const presentation = definition.presentation;
432
+ if (!isPlainObject(presentation) || Object.keys(presentation).length === 0) {
433
+ errors.push({ path: "presentation", code: "invalid_presentation" });
434
+ }
435
+ else {
436
+ for (const key of sortedKeys(presentation)) {
437
+ if (key !== "background" && key !== "border" && key !== "shadow") {
438
+ errors.push({ path: `presentation.${key}`, code: "unknown_property" });
439
+ }
440
+ else if (typeof presentation[key] !== "boolean") {
441
+ errors.push({ path: `presentation.${key}`, code: "invalid_presentation_value" });
442
+ }
443
+ }
444
+ }
445
+ }
430
446
  const audience = definition.newsletter_audience_id;
431
447
  if (audience !== undefined && (typeof audience !== "string" || !isResourceId(audience))) {
432
448
  errors.push({ path: "newsletter_audience_id", code: "invalid_newsletter_audience" });
@@ -6,7 +6,7 @@ import { contactFieldId, validateDefinition } from "./field-validation.js";
6
6
  *
7
7
  * A form is one JSON file, `forms/<key>.json`, holding what an author decides:
8
8
  * its key, name, sink, retention, owner-notification preference and definition. The definition holds the fields
9
- * and the form's own settings (`contact_field`, `submit_label`, `after_submit`),
9
+ * and the form's own settings (`contact_field`, `submit_label`, `after_submit`, `presentation`),
10
10
  * exactly as the site stores it, so one validator reads one shape. The
11
11
  * definition's own rules belong to `shared/field-validation.ts` and are run
12
12
  * here through the generated copy, so this file never restates them. The one
@@ -289,6 +289,20 @@ export function projectFormForWorkspace(form, pathById) {
289
289
  if (form.definition.after_submit !== undefined) {
290
290
  definition.after_submit = canonicalAfterSubmit(form.definition.after_submit, pathById);
291
291
  }
292
+ if (form.definition.newsletter_audience_id !== undefined) {
293
+ definition.newsletter_audience_id = form.definition.newsletter_audience_id;
294
+ }
295
+ if (form.definition.presentation !== undefined) {
296
+ const presentation = {};
297
+ for (const name of ["background", "border", "shadow", ...Object.keys(form.definition.presentation).sort()]) {
298
+ if (form.definition.presentation[name] !== undefined) presentation[name] = form.definition.presentation[name];
299
+ }
300
+ definition.presentation = presentation;
301
+ }
302
+ // Preserve future properties on pull so an older workspace cannot silently erase them.
303
+ for (const name of Object.keys(form.definition).sort()) {
304
+ if (name !== "contact_field" && definition[name] === undefined) definition[name] = form.definition[name];
305
+ }
292
306
  return {
293
307
  key: form.key,
294
308
  ...projectFormSettings(form),
@@ -95,7 +95,7 @@ import {
95
95
 
96
96
  export { getAppearanceReference, getFooterReference, getSettingsReference, getThemeReference };
97
97
 
98
- export const REFERENCE_VERSION = 43;
98
+ export const REFERENCE_VERSION = 44;
99
99
 
100
100
  // The date-limit examples `help forms` prints; tests run them through the form validator.
101
101
  export const FORM_DATE_WINDOW_FIELD = Object.freeze({
@@ -883,6 +883,11 @@ const WORKFLOW_REFERENCES = Object.freeze({
883
883
  + "the visitor saw it, so a response shows what each box agreed to.",
884
884
  "Editing a definition appends a version on the site; versions are never changed. A page keeps the form "
885
885
  + "version it was published with until the next deploy, and a rollback restores the form it shipped with.",
886
+ "definition.presentation optionally overrides the site's form appearance: background, border and shadow are "
887
+ + "optional booleans. Omit a property to inherit the corresponding site preference (formBoxBackground, "
888
+ + "formBoxBorder, formBoxShadow); false turns that choice off. Omit presentation when all three inherit: an empty "
889
+ + "object is refused. Site defaults are background on, border off and shadow on. Background can be off while border "
890
+ + "and shadow remain on. The release freezes both the form choices and the site defaults.",
886
891
  "Both verbs write the file in one canonical key order, so a pulled file pushed back unchanged reports "
887
892
  + "'unchanged' and leaves the bytes as they were. Write your own files in that order and a push rewrites "
888
893
  + "nothing you did not change.",
@@ -272,6 +272,24 @@ const PREFERENCES_ENTRIES = {
272
272
  + "a deployment.",
273
273
  false,
274
274
  ),
275
+ formBoxBackground: entry(
276
+ `${FEATURES} > Form appearance > Background`,
277
+ "Whether embedded forms have a box background by default. A form's presentation.background overrides this choice.",
278
+ "Embedded forms, including validation and confirmation. A change applies after deployment.",
279
+ true,
280
+ ),
281
+ formBoxBorder: entry(
282
+ `${FEATURES} > Form appearance > Border`,
283
+ "Whether embedded forms have a border by default. A form's presentation.border overrides this choice.",
284
+ "Embedded forms. Independent of background and shadow; applies after deployment.",
285
+ false,
286
+ ),
287
+ formBoxShadow: entry(
288
+ `${FEATURES} > Form appearance > Shadow`,
289
+ "Whether embedded forms have a shadow by default. A form's presentation.shadow overrides this choice.",
290
+ "Embedded forms. Independent of background and border; applies after deployment.",
291
+ true,
292
+ ),
275
293
  tiptapImageMaxHeightVh: entry(
276
294
  `${FEATURES} > Image height`,
277
295
  "The tallest an image in a page body may be, as a percent of the screen height (45, 60, 75 or 90).",
@@ -130,6 +130,9 @@ export const SETTINGS_GROUPS = Object.freeze([
130
130
  // Asks readers before the visit cookie is set (TR01263). Off is what an omitted value means, so a snapshot of
131
131
  // an untouched site reads the same as the site behaves.
132
132
  { name: "analyticsConsentPrompt", wireType: "boolean" },
133
+ { name: "formBoxBackground", wireType: "boolean", zero: true },
134
+ { name: "formBoxBorder", wireType: "boolean" },
135
+ { name: "formBoxShadow", wireType: "boolean", zero: true },
133
136
  { name: "tiptapImageMaxHeightVh", wireType: "int" },
134
137
  { name: "tiptapImageMaxImagesPerRow", wireType: "int" },
135
138
  { name: "tiptapImageBorderWidth", wireType: "int" },
@@ -146,7 +149,7 @@ export const CANDIDATE_SELECTABLE_SETTINGS_TYPES = Object.freeze(
146
149
  );
147
150
 
148
151
  function zeroValue(field) {
149
- if (field.wireType === "boolean") return false;
152
+ if (field.wireType === "boolean") return field.zero ?? false;
150
153
  if (field.wireType === "int" || field.wireType === "double") return 0;
151
154
  if (field.wireType === "theme" || field.wireType === "document") return field.zero ?? {};
152
155
  return field.zero ?? "";