@openpresentation/opf-editor 0.11.3 → 0.14.2
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/README.md +82 -58
- package/dist/annotations.d.ts +16 -16
- package/dist/annotations.js +71 -79
- package/dist/assets.d.ts +7 -7
- package/dist/assets.js +15 -15
- package/dist/background-options.d.ts +2 -2
- package/dist/background-options.js +6 -6
- package/dist/block-controls.js +7 -7
- package/dist/block-convert.d.ts +7 -7
- package/dist/block-convert.js +23 -21
- package/dist/blocks.d.ts +8 -8
- package/dist/blocks.js +41 -40
- package/dist/canvas-fields.js +35 -12
- package/dist/canvas.d.ts +22 -30
- package/dist/canvas.js +57 -64
- package/dist/chart-data.d.ts +12 -10
- package/dist/chart-data.js +22 -21
- package/dist/chart-options-panel.d.ts +2 -2
- package/dist/chart-options-panel.js +82 -8
- package/dist/chart-options.d.ts +35 -9
- package/dist/chart-options.js +133 -40
- package/dist/checks.js +21 -0
- package/dist/color-authoring.js +1 -1
- package/dist/content-actions.d.ts +15 -15
- package/dist/content-actions.js +71 -70
- package/dist/content-controls.js +12 -12
- package/dist/data-grid.js +179 -10
- package/dist/data.d.ts +23 -2
- package/dist/data.js +40 -1
- package/dist/dataset-refs.d.ts +17 -0
- package/dist/dataset-refs.js +45 -0
- package/dist/design-controls.js +66 -47
- package/dist/design-options.d.ts +16 -14
- package/dist/design-options.js +86 -74
- package/dist/edit-helpers.js +8 -7
- package/dist/exact-source.js +5 -5
- package/dist/export.d.ts +5 -7
- package/dist/export.js +40 -14
- package/dist/find-panel.js +5 -5
- package/dist/find-replace.d.ts +3 -3
- package/dist/find-replace.js +35 -20
- package/dist/font-gate.js +49 -58
- package/dist/galleries.js +36 -52
- package/dist/grid-model.d.ts +99 -24
- package/dist/grid-model.js +577 -101
- package/dist/image-crop.d.ts +4 -4
- package/dist/image-crop.js +20 -20
- package/dist/image-cropper.js +3 -3
- package/dist/index.d.ts +28 -28
- package/dist/index.js +74 -90
- package/dist/json-options.js +17 -16
- package/dist/layout-handles.js +5 -5
- package/dist/layout-placeholders.js +4 -4
- package/dist/layout.d.ts +2 -2
- package/dist/layout.js +8 -7
- package/dist/numbering-panel.js +3 -5
- package/dist/numbering.d.ts +3 -5
- package/dist/numbering.js +14 -26
- package/dist/outline-view.js +24 -24
- package/dist/outline.d.ts +12 -12
- package/dist/outline.js +52 -50
- package/dist/persistence.d.ts +1 -1
- package/dist/persistence.js +19 -18
- package/dist/presentation-ids.js +2 -2
- package/dist/react.js +3 -3
- package/dist/review-panel.d.ts +30 -13
- package/dist/review-panel.js +154 -53
- package/dist/review.d.ts +36 -43
- package/dist/review.js +92 -53
- package/dist/rich-text-input.js +4 -3
- package/dist/rich-text-toolbar.js +5 -3
- package/dist/rich-text.js +5 -2
- package/dist/schema-inspector.d.ts +2 -2
- package/dist/schema-inspector.js +16 -11
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +25 -5
- package/dist/slide-manager.d.ts +2 -2
- package/dist/slide-manager.js +18 -18
- package/dist/slides.d.ts +20 -20
- package/dist/slides.js +99 -97
- package/dist/svelte.js +1 -1
- package/dist/switches.d.ts +10 -10
- package/dist/switches.js +136 -109
- package/dist/table-options.d.ts +8 -8
- package/dist/table-options.js +41 -30
- package/dist/table-structure.d.ts +10 -10
- package/dist/table-structure.js +21 -21
- package/dist/template-panel.d.ts +3 -3
- package/dist/template-panel.js +37 -13
- package/dist/templates.d.ts +22 -11
- package/dist/templates.js +48 -55
- package/dist/transfer.d.ts +4 -4
- package/dist/transfer.js +74 -54
- package/package.json +10 -6
- package/dist/font-defaults.js +0 -25
package/dist/design-controls.js
CHANGED
|
@@ -143,7 +143,7 @@ export function createDesignControls(container, options = {}) {
|
|
|
143
143
|
};
|
|
144
144
|
const reselect = (blockPath) => {
|
|
145
145
|
if (!blockPath || !options.onSelectPath) return;
|
|
146
|
-
const content = readBlockContent(editor.
|
|
146
|
+
const content = readBlockContent(editor.presentation, blockPath);
|
|
147
147
|
options.onSelectPath(content && !content.explicit ? `${blockPath}.${content.key}` : blockPath);
|
|
148
148
|
};
|
|
149
149
|
/** Run a change; report its outcome; on failure restore every control to the document. */
|
|
@@ -299,7 +299,7 @@ export function createDesignControls(container, options = {}) {
|
|
|
299
299
|
help: "Describes the image for screen readers. Saved with the image's asset; typed before an upload, it is used for that upload. Press Enter to apply.",
|
|
300
300
|
onCommit: (value) => {
|
|
301
301
|
const id = assetIdOf(lastRef);
|
|
302
|
-
if (!id || editor.
|
|
302
|
+
if (!id || editor.presentation.assets?.[id] === undefined) return;
|
|
303
303
|
run(() => setAssetAlt(editor, id, value), "Alt text saved.");
|
|
304
304
|
},
|
|
305
305
|
});
|
|
@@ -333,7 +333,7 @@ export function createDesignControls(container, options = {}) {
|
|
|
333
333
|
source.setList(assetList());
|
|
334
334
|
source.set(lastRef, note);
|
|
335
335
|
const id = assetIdOf(lastRef);
|
|
336
|
-
const entry = id ? editor.
|
|
336
|
+
const entry = id ? editor.presentation.assets?.[id] : undefined;
|
|
337
337
|
// Alt text belongs to one image: another target (variant, zone, slide) never inherits what was typed for the last one.
|
|
338
338
|
alt.set(entry && typeof entry === "object" && typeof entry.alt === "string" ? entry.alt : "");
|
|
339
339
|
// The field keeps what is being typed, except when the image it describes has changed.
|
|
@@ -372,7 +372,7 @@ export function createDesignControls(container, options = {}) {
|
|
|
372
372
|
root.append(scopeField.wrap);
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
-
const assetList = () => Object.keys(editor.
|
|
375
|
+
const assetList = () => Object.keys(editor.presentation.assets ?? {}).map((id) => `asset:${id}`);
|
|
376
376
|
const stringOf = (value) => (typeof value === "string" ? value : value && typeof value === "object" && typeof value.src === "string" ? value.src : "");
|
|
377
377
|
const isCustomObject = (value) => Boolean(value) && typeof value === "object";
|
|
378
378
|
|
|
@@ -392,8 +392,8 @@ export function createDesignControls(container, options = {}) {
|
|
|
392
392
|
});
|
|
393
393
|
body.append(field.wrap);
|
|
394
394
|
syncs.push(() => {
|
|
395
|
-
field.setOptions(listSwitchOptions(editor.
|
|
396
|
-
const current = currentSwitchValue(editor.
|
|
395
|
+
field.setOptions(listSwitchOptions(editor.presentation, dimension, catalogOptions()).map((entry) => ({ value: entry.id, label: entry.label })));
|
|
396
|
+
const current = currentSwitchValue(editor.presentation, dimension, perSlide ? scoped() : {});
|
|
397
397
|
field.set(current.value, perSlide ? sourceNote(current.scope, current.value !== undefined) : "");
|
|
398
398
|
});
|
|
399
399
|
return field;
|
|
@@ -409,8 +409,8 @@ export function createDesignControls(container, options = {}) {
|
|
|
409
409
|
});
|
|
410
410
|
body.append(slideSize.wrap);
|
|
411
411
|
syncs.push(() => {
|
|
412
|
-
slideSize.setOptions(listSwitchOptions(editor.
|
|
413
|
-
const { value } = currentSwitchValue(editor.
|
|
412
|
+
slideSize.setOptions(listSwitchOptions(editor.presentation, "slide-sizes").map((entry) => ({ value: entry.id, label: entry.label })));
|
|
413
|
+
const { value } = currentSwitchValue(editor.presentation, "slide-sizes");
|
|
414
414
|
slideSize.set(value && typeof value === "object" ? "Custom size" : value);
|
|
415
415
|
});
|
|
416
416
|
|
|
@@ -420,8 +420,8 @@ export function createDesignControls(container, options = {}) {
|
|
|
420
420
|
});
|
|
421
421
|
body.append(layout.wrap);
|
|
422
422
|
syncs.push(() => {
|
|
423
|
-
layout.setOptions(listSwitchOptions(editor.
|
|
424
|
-
layout.set(currentSwitchValue(editor.
|
|
423
|
+
layout.setOptions(listSwitchOptions(editor.presentation, "layouts", catalogOptions()).map((entry) => ({ value: entry.id, label: entry.label })));
|
|
424
|
+
layout.set(currentSwitchValue(editor.presentation, "layouts", { slideIndex: getSlide() }).value);
|
|
425
425
|
});
|
|
426
426
|
}
|
|
427
427
|
|
|
@@ -525,7 +525,7 @@ export function createDesignControls(container, options = {}) {
|
|
|
525
525
|
body.append(typeSelect.wrap, themePanel, solidPanel, gradientPanel, imagePanel, patternPanel, opacity.wrap, h("div", { class: "opf-dc-actions" }, apply, remove));
|
|
526
526
|
|
|
527
527
|
const load = () => {
|
|
528
|
-
const current = readBackground(editor.
|
|
528
|
+
const current = readBackground(editor.presentation, scoped());
|
|
529
529
|
const note = sourceNote(current.scope, current.type !== undefined);
|
|
530
530
|
typeSelect.set(current.type ?? "theme", note);
|
|
531
531
|
slot.set(current.type === "theme" ? current.slot : "light1");
|
|
@@ -592,10 +592,10 @@ export function createDesignControls(container, options = {}) {
|
|
|
592
592
|
onCommit: (value) => image({ size: value }, "Image size changed."),
|
|
593
593
|
});
|
|
594
594
|
const inset = checkField("image-inset", "Inset inside the slide padding", { onChange: (checked) => image({ inset: checked ? true : null }, "Image inset changed.") });
|
|
595
|
-
const placeholderFill = selectField("image-placeholder-fill", "
|
|
595
|
+
const placeholderFill = selectField("image-placeholder-fill", "Image placeholders", {
|
|
596
596
|
empty: "Presentation default",
|
|
597
|
-
help: "How
|
|
598
|
-
onChange: (value) => run(() => switchDimension(editor, "image-treatments", { imageFill: value === "" ? null : value }), "
|
|
597
|
+
help: "How images fill their layout placeholders across the presentation.",
|
|
598
|
+
onChange: (value) => run(() => switchDimension(editor, "image-treatments", { imageFill: value === "" ? null : value }), "Image placeholder fill changed."),
|
|
599
599
|
});
|
|
600
600
|
placeholderFill.setOptions([
|
|
601
601
|
{ value: "crop", label: "Crop to fill" },
|
|
@@ -603,7 +603,7 @@ export function createDesignControls(container, options = {}) {
|
|
|
603
603
|
]);
|
|
604
604
|
body.append(position.wrap, source.wrap, fill.wrap, shape.wrap, size.wrap, inset.wrap, placeholderFill.wrap);
|
|
605
605
|
syncs.push(() => {
|
|
606
|
-
const option = getDesignOption(editor.
|
|
606
|
+
const option = getDesignOption(editor.presentation, "slideImage", scoped());
|
|
607
607
|
const value = option.value;
|
|
608
608
|
const object = value && typeof value === "object" && !Array.isArray(value) && value.position ? value : undefined;
|
|
609
609
|
const note = sourceNote(option.scope, option.value !== undefined);
|
|
@@ -615,7 +615,7 @@ export function createDesignControls(container, options = {}) {
|
|
|
615
615
|
inset.set(object?.inset === true);
|
|
616
616
|
// Fit, shape, size and inset only make sense once the image has a position.
|
|
617
617
|
for (const control of [fill, shape, size, inset]) control.wrap.hidden = !object;
|
|
618
|
-
placeholderFill.set(editor.
|
|
618
|
+
placeholderFill.set(editor.presentation.design?.imageFill ?? "");
|
|
619
619
|
});
|
|
620
620
|
}
|
|
621
621
|
|
|
@@ -632,7 +632,7 @@ export function createDesignControls(container, options = {}) {
|
|
|
632
632
|
{ value: "footer", label: "Footer" },
|
|
633
633
|
]);
|
|
634
634
|
furniture.set(pick.which);
|
|
635
|
-
const zoneSelect = selectField("hf-zone", "Zone", { help: "Each zone stacks its parts top to bottom: logo, image, text, organization, socials, section, slide number, date." });
|
|
635
|
+
const zoneSelect = selectField("hf-zone", "Zone", { help: "Each zone stacks its parts top to bottom: logo, image, text, organization, speaker, socials, section, slide number, date." });
|
|
636
636
|
zoneSelect.setOptions(HEADER_FOOTER_ZONES.map((value) => ({ value, label: titleCase(value) })));
|
|
637
637
|
zoneSelect.set(pick.zone);
|
|
638
638
|
furniture.select.addEventListener("change", () => {
|
|
@@ -678,22 +678,23 @@ export function createDesignControls(container, options = {}) {
|
|
|
678
678
|
onCommit: (value) => edit({ dateFormat: value.trim() }, `${where()} date format changed.`),
|
|
679
679
|
});
|
|
680
680
|
const organization = checkField("hf-organization", "Show the organization name", { onChange: (checked) => edit({ organization: checked }, `${where()} organization ${checked ? "shown" : "removed"}.`) });
|
|
681
|
+
const speaker = checkField("hf-speaker", "Show the speaker name and title", { onChange: (checked) => edit({ speaker: checked }, `${where()} speaker ${checked ? "shown" : "removed"}.`) });
|
|
681
682
|
const socials = checkField("hf-socials", "Show the organization's social profiles", { onChange: (checked) => edit({ socials: checked }, `${where()} social profiles ${checked ? "shown" : "removed"}.`) });
|
|
682
683
|
const section = checkField("hf-section", "Show the section label", { onChange: (checked) => edit({ section: checked }, `${where()} section label ${checked ? "shown" : "removed"}.`) });
|
|
683
684
|
const summary = h("ul", { class: "opf-dc-summary", "aria-label": "Zones in use" });
|
|
684
685
|
const warnings = h("ul", { class: "opf-dc-warnings", "aria-label": "Header and footer warnings" });
|
|
685
|
-
const controls = [text, logo, image, number, numberFormat, dateNow, dateFixed, dateFormat, organization, socials, section];
|
|
686
|
+
const controls = [text, logo, image, number, numberFormat, dateNow, dateFixed, dateFormat, organization, speaker, socials, section];
|
|
686
687
|
body.append(furniture.wrap, hide.wrap, zoneSelect.wrap, summary, ...controls.map((control) => control.wrap), warnings);
|
|
687
688
|
const describe = (fields) =>
|
|
688
689
|
Object.entries(fields)
|
|
689
690
|
.map(([key, value]) => (typeof value === "boolean" ? key : `${key} ${typeof value === "string" ? `“${value}”` : ""}`.trim()))
|
|
690
691
|
.join(", ");
|
|
691
692
|
syncs.push(() => {
|
|
692
|
-
const state = headerFooterState(editor.
|
|
693
|
+
const state = headerFooterState(editor.presentation, which(), scoped());
|
|
693
694
|
// Offered on a slide that inherits the deck's furniture or already hides it, never over zones the slide set itself; at the deck only to undo a hidden state.
|
|
694
695
|
hide.wrap.hidden = scopeIndex() === undefined ? !state.hidden : state.own && !state.hidden;
|
|
695
696
|
hide.set(state.hidden, state.inherited && !state.hidden ? "from the presentation" : "");
|
|
696
|
-
const fields = readHeaderFooterZone(editor.
|
|
697
|
+
const fields = readHeaderFooterZone(editor.presentation, which(), zone(), scoped());
|
|
697
698
|
text.set(typeof fields.text === "string" ? fields.text : "");
|
|
698
699
|
logo.set(fields.logo === true);
|
|
699
700
|
image.set(stringOf(fields.image), "", `${target()}:${which()}:${zone()}`);
|
|
@@ -703,16 +704,17 @@ export function createDesignControls(container, options = {}) {
|
|
|
703
704
|
dateFixed.set(typeof fields.date === "string" ? fields.date : "");
|
|
704
705
|
dateFormat.set(typeof fields.dateFormat === "string" ? fields.dateFormat : "");
|
|
705
706
|
organization.set(fields.organization === true);
|
|
707
|
+
speaker.set(fields.speaker === true);
|
|
706
708
|
socials.set(fields.socials === true);
|
|
707
709
|
section.set(fields.section === true);
|
|
708
710
|
for (const control of controls) for (const input of control.wrap.querySelectorAll("input,select,button")) input.disabled = state.hidden;
|
|
709
711
|
summary.replaceChildren(
|
|
710
712
|
...HEADER_FOOTER_ZONES.map((name) => {
|
|
711
|
-
const zoneFields = readHeaderFooterZone(editor.
|
|
713
|
+
const zoneFields = readHeaderFooterZone(editor.presentation, which(), name, scoped());
|
|
712
714
|
return h("li", {}, `${titleCase(name)}: ${Object.keys(zoneFields).length ? describe(zoneFields) : "empty"}`);
|
|
713
715
|
}),
|
|
714
716
|
);
|
|
715
|
-
warnings.replaceChildren(...designWarnings(editor.
|
|
717
|
+
warnings.replaceChildren(...designWarnings(editor.presentation, getSlide()).filter((warning) => /^design\.(header|footer)/.test(warning.path)).map((warning) => h("li", {}, warning.message)));
|
|
716
718
|
});
|
|
717
719
|
}
|
|
718
720
|
|
|
@@ -752,6 +754,18 @@ export function createDesignControls(container, options = {}) {
|
|
|
752
754
|
apply: (ref) => setDesignOption(editor, "watermark", ref === null ? null : { src: ref }, scoped()),
|
|
753
755
|
build: (ref, doc) => prepareDesignOption(doc, "watermark", { src: ref }, scoped()),
|
|
754
756
|
});
|
|
757
|
+
// FA-13: a watermark is an image or a text stamp; the text field and the image cluster replace each other.
|
|
758
|
+
const watermarkText = textField("watermark-text", "Watermark text", {
|
|
759
|
+
placeholder: "DRAFT",
|
|
760
|
+
help: "A text stamp centered on the slide, drawn diagonally in the theme text color at the opacity below. Press Enter to apply; clear the field to remove it. It replaces a watermark image.",
|
|
761
|
+
onCommit: (value) => run(() => {
|
|
762
|
+
const text = value.trim();
|
|
763
|
+
const current = getDesignOption(editor.presentation, "watermark", scoped()).value;
|
|
764
|
+
if (text === "") return current && typeof current === "object" && typeof current.text === "string" ? setDesignOption(editor, "watermark", null, scoped()) : { changed: false };
|
|
765
|
+
const opacity = current && typeof current === "object" && typeof current.opacity === "number" ? current.opacity : 0.1;
|
|
766
|
+
return setDesignOption(editor, "watermark", { text, opacity }, scoped());
|
|
767
|
+
}, value.trim() === "" ? "Watermark removed." : "Watermark text set."),
|
|
768
|
+
});
|
|
755
769
|
const watermarkOpacity = numberField("watermark-opacity", "Watermark opacity (0 to 1)", {
|
|
756
770
|
min: 0,
|
|
757
771
|
max: 1,
|
|
@@ -761,30 +775,31 @@ export function createDesignControls(container, options = {}) {
|
|
|
761
775
|
const watermarkOff = checkField("watermark-off", "Hide the inherited watermark", {
|
|
762
776
|
onChange: (checked) => run(() => setDesignOption(editor, "watermark", checked ? false : null, scoped()), checked ? "Watermark hidden." : "Watermark restored."),
|
|
763
777
|
});
|
|
764
|
-
body.append(variant.wrap, logoSource.wrap, orgLogo.wrap, bullet.wrap, accent.wrap, watermarkSource.wrap, watermarkOpacity.wrap, watermarkOff.wrap);
|
|
778
|
+
body.append(variant.wrap, logoSource.wrap, orgLogo.wrap, bullet.wrap, accent.wrap, watermarkSource.wrap, watermarkText.wrap, watermarkOpacity.wrap, watermarkOff.wrap);
|
|
765
779
|
const warningList = h("ul", { class: "opf-dc-warnings", "aria-label": "Logo warnings" });
|
|
766
780
|
body.append(warningList);
|
|
767
781
|
syncs.push(() => {
|
|
768
|
-
const variants = readLogoVariants(editor.
|
|
782
|
+
const variants = readLogoVariants(editor.presentation, scoped());
|
|
769
783
|
logoSource.set(stringOf(variants[variant.select.value]), "", `${target()}:${variant.select.value}`);
|
|
770
|
-
const organization = editor.
|
|
784
|
+
const organization = editor.presentation.organization;
|
|
771
785
|
const owner = Array.isArray(organization) ? organization[0] : organization;
|
|
772
786
|
orgLogo.set(stringOf(owner?.logo), "", "org");
|
|
773
787
|
for (const input of orgLogo.wrap.querySelectorAll("input")) input.disabled = !owner;
|
|
774
|
-
const bulletOption = getDesignOption(editor.
|
|
788
|
+
const bulletOption = getDesignOption(editor.presentation, "listBullet", scoped());
|
|
775
789
|
bullet.set(bulletOption.value ?? "", sourceNote(bulletOption.scope, bulletOption.value !== undefined));
|
|
776
|
-
const accentOption = getDesignOption(editor.
|
|
790
|
+
const accentOption = getDesignOption(editor.presentation, "accentFont", scoped());
|
|
777
791
|
accent.set(accentOption.value ?? "", sourceNote(accentOption.scope, accentOption.value !== undefined));
|
|
778
|
-
const watermark = getDesignOption(editor.
|
|
792
|
+
const watermark = getDesignOption(editor.presentation, "watermark", scoped());
|
|
779
793
|
const mark = watermark.value;
|
|
780
|
-
// The opacity belongs to a watermark image; without one there is nothing to fade.
|
|
781
|
-
for (const input of watermarkOpacity.wrap.querySelectorAll("input")) input.disabled = !(typeof mark === "string" || (mark && typeof mark === "object" && typeof mark.src === "string"));
|
|
794
|
+
// The opacity belongs to a watermark image or text; without one there is nothing to fade.
|
|
795
|
+
for (const input of watermarkOpacity.wrap.querySelectorAll("input")) input.disabled = !(typeof mark === "string" || (mark && typeof mark === "object" && (typeof mark.src === "string" || typeof mark.text === "string")));
|
|
796
|
+
watermarkText.set(mark && typeof mark === "object" && typeof mark.text === "string" ? mark.text : "", sourceNote(watermark.scope, mark !== undefined));
|
|
782
797
|
watermarkSource.set(typeof mark === "string" ? mark : mark && typeof mark === "object" ? stringOf(mark) : "", sourceNote(watermark.scope, mark !== undefined), target());
|
|
783
798
|
watermarkOpacity.set(mark && typeof mark === "object" && typeof mark.opacity === "number" ? String(mark.opacity) : "");
|
|
784
|
-
const own = scopeIndex() === undefined ? editor.
|
|
799
|
+
const own = scopeIndex() === undefined ? editor.presentation.design?.watermark : editor.presentation.slides?.[scopeIndex()]?.design?.watermark;
|
|
785
800
|
watermarkOff.set(own === false);
|
|
786
801
|
watermarkOff.wrap.hidden = state.scope !== "slide";
|
|
787
|
-
warningList.replaceChildren(...designWarnings(editor.
|
|
802
|
+
warningList.replaceChildren(...designWarnings(editor.presentation, getSlide()).filter((warning) => warning.path === "design.listBullet").map((warning) => h("li", {}, warning.message)));
|
|
788
803
|
});
|
|
789
804
|
}
|
|
790
805
|
|
|
@@ -801,7 +816,7 @@ export function createDesignControls(container, options = {}) {
|
|
|
801
816
|
field.setOptions(values.map((value) => ({ value, label: labels[value] ?? titleCase(value) })));
|
|
802
817
|
body.append(field.wrap);
|
|
803
818
|
syncs.push(() => {
|
|
804
|
-
const option = getDesignOption(editor.
|
|
819
|
+
const option = getDesignOption(editor.presentation, id, scoped());
|
|
805
820
|
field.set(option.value ?? "", sourceNote(option.scope, option.value !== undefined));
|
|
806
821
|
});
|
|
807
822
|
};
|
|
@@ -819,7 +834,7 @@ export function createDesignControls(container, options = {}) {
|
|
|
819
834
|
]);
|
|
820
835
|
body.append(box.wrap);
|
|
821
836
|
syncs.push(() => {
|
|
822
|
-
const option = getDesignOption(editor.
|
|
837
|
+
const option = getDesignOption(editor.presentation, "contentBox", scoped());
|
|
823
838
|
box.set(option.value === undefined ? "" : option.value ? "yes" : "no", sourceNote(option.scope, option.value !== undefined));
|
|
824
839
|
});
|
|
825
840
|
}
|
|
@@ -832,11 +847,11 @@ export function createDesignControls(container, options = {}) {
|
|
|
832
847
|
const field = selectField(name, label, { help, onChange: (value) => run(() => switchDimension(editor, dimension, value, catalogOptions()), `${label} set to ${field.select.selectedOptions[0]?.textContent ?? value}.`) });
|
|
833
848
|
body.append(field.wrap);
|
|
834
849
|
syncs.push(() => {
|
|
835
|
-
field.setOptions(listSwitchOptions(editor.
|
|
836
|
-
field.set(currentSwitchValue(editor.
|
|
850
|
+
field.setOptions(listSwitchOptions(editor.presentation, dimension, catalogOptions()).map((entry) => ({ value: entry.id, label: entry.label })));
|
|
851
|
+
field.set(currentSwitchValue(editor.presentation, dimension).value);
|
|
837
852
|
});
|
|
838
853
|
};
|
|
839
|
-
single("narrative", "Narrative", "narratives", "The
|
|
854
|
+
single("narrative", "Narrative", "narratives", "The narrative plan the deck points at (a catalog id; a custom one is a record in the JSON source). The slides do not change, and keep their beat links.");
|
|
840
855
|
single("tone", "Tone", "tones");
|
|
841
856
|
single("purpose", "Purpose", "purposes", "What the deck is for. Authoring metadata: the slides do not change. A goal written in the JSON source shows as custom.");
|
|
842
857
|
const audience = selectField("audience", "Audience", {
|
|
@@ -850,9 +865,11 @@ export function createDesignControls(container, options = {}) {
|
|
|
850
865
|
});
|
|
851
866
|
body.append(audience.wrap);
|
|
852
867
|
syncs.push(() => {
|
|
853
|
-
audience.setOptions(listSwitchOptions(editor.
|
|
854
|
-
|
|
855
|
-
|
|
868
|
+
audience.setOptions(listSwitchOptions(editor.presentation, "audiences", catalogOptions()).map((entry) => ({ value: entry.id, label: entry.label })));
|
|
869
|
+
// The root audience is a string, one inline Audience object or an array of both: the picker shows the catalog ids.
|
|
870
|
+
const value = editor.presentation.audience;
|
|
871
|
+
const entries = Array.isArray(value) ? value : value === undefined ? [] : [value];
|
|
872
|
+
audience.set(entries.map((entry) => (entry && typeof entry === "object" ? entry.id : entry)).filter((id) => typeof id === "string"));
|
|
856
873
|
});
|
|
857
874
|
|
|
858
875
|
const owner = selectField("social-owner", "Socials belong to");
|
|
@@ -866,17 +883,17 @@ export function createDesignControls(container, options = {}) {
|
|
|
866
883
|
handle.input.addEventListener("change", applySocial);
|
|
867
884
|
body.append(owner.wrap, platform.wrap, handle.wrap, button("Save handle", applySocial));
|
|
868
885
|
const showHandle = () => {
|
|
869
|
-
const host = editor.
|
|
886
|
+
const host = editor.presentation[owner.select.value];
|
|
870
887
|
const target = Array.isArray(host) ? host[0] : host;
|
|
871
888
|
handle.set(typeof target?.socials?.[platform.select.value] === "string" ? target.socials[platform.select.value] : "");
|
|
872
889
|
};
|
|
873
890
|
owner.select.addEventListener("change", showHandle);
|
|
874
891
|
platform.select.addEventListener("change", showHandle);
|
|
875
892
|
syncs.push(() => {
|
|
876
|
-
const owners = ["speaker", "organization"].filter((name) => editor.
|
|
893
|
+
const owners = ["speaker", "organization"].filter((name) => editor.presentation[name]);
|
|
877
894
|
owner.setOptions(owners.length ? owners.map((name) => ({ value: name, label: titleCase(name) })) : [{ value: "speaker", label: "Speaker (add one first)", disabled: true }]);
|
|
878
895
|
if (!owner.select.value || !owners.includes(owner.select.value)) owner.select.value = owners[0] ?? "speaker";
|
|
879
|
-
platform.setOptions(listSwitchOptions(editor.
|
|
896
|
+
platform.setOptions(listSwitchOptions(editor.presentation, "socials", catalogOptions()).map((entry) => ({ value: entry.id, label: entry.label })));
|
|
880
897
|
showHandle();
|
|
881
898
|
});
|
|
882
899
|
}
|
|
@@ -972,7 +989,7 @@ export function createDesignControls(container, options = {}) {
|
|
|
972
989
|
|
|
973
990
|
function syncSelection() {
|
|
974
991
|
const selected = getSelected();
|
|
975
|
-
const document_ = editor.
|
|
992
|
+
const document_ = editor.presentation;
|
|
976
993
|
const blockPath = selected ? blockPathForSelection(document_, selected) : undefined;
|
|
977
994
|
const content = blockPath ? readBlockContent(document_, blockPath) : undefined;
|
|
978
995
|
dynamic.blockPath = blockPath;
|
|
@@ -1009,9 +1026,11 @@ export function createDesignControls(container, options = {}) {
|
|
|
1009
1026
|
const tablePath = parsed?.tablePath ?? (content?.key === "table" ? `${blockPath}.table` : undefined);
|
|
1010
1027
|
dynamic.tablePath = tablePath;
|
|
1011
1028
|
dynamic.cell = parsed?.cell;
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1029
|
+
// A table that shows a shared dataset (RR-54) has no cell styles or merges: the table panel is for inline tables.
|
|
1030
|
+
const tableValue = tablePath ? editor.get(tablePath) : undefined;
|
|
1031
|
+
tableUi.details.hidden = !tablePath || !Array.isArray(tableValue?.rows);
|
|
1032
|
+
if (tablePath && !tableUi.details.hidden) {
|
|
1033
|
+
const table = tableValue;
|
|
1015
1034
|
tableUi.summary.textContent = `Table (${tablePath})`;
|
|
1016
1035
|
const style = readTableStyle(document_, tablePath);
|
|
1017
1036
|
dynamic.tableStyle = style.preset === "custom" ? { header: "theme", banding: false, borders: "theme" } : { header: style.header, banding: style.banding, borders: style.borders };
|
package/dist/design-options.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export declare const DESIGN_OPTIONS: readonly DesignOptionDescriptor[];
|
|
|
25
25
|
export declare const LOGO_VARIANTS: readonly ["default", "light", "dark", "stacked", "stackedLight", "stackedDark", "icon", "iconLight", "iconDark", "wordmark", "wordmarkLight", "wordmarkDark"];
|
|
26
26
|
export type LogoVariant = (typeof LOGO_VARIANTS)[number];
|
|
27
27
|
/** Every field a header or footer zone can hold. */
|
|
28
|
-
export declare const ZONE_FIELDS: readonly ["logo", "text", "image", "slideNumber", "slideNumberFormat", "date", "dateFormat", "organization", "socials", "section"];
|
|
28
|
+
export declare const ZONE_FIELDS: readonly ["logo", "text", "image", "slideNumber", "slideNumberFormat", "date", "dateFormat", "organization", "speaker", "socials", "section"];
|
|
29
29
|
/** Date format tokens (English names). */
|
|
30
30
|
export declare const DATE_FORMAT_TOKENS: readonly string[];
|
|
31
31
|
export declare const HEADER_FOOTER_ZONES: readonly ["left", "center", "right"];
|
|
@@ -53,7 +53,7 @@ export interface PreparedDesignOption {
|
|
|
53
53
|
/** The logo variant or header/footer zone an edit named. */
|
|
54
54
|
variant?: string;
|
|
55
55
|
zone?: string;
|
|
56
|
-
|
|
56
|
+
presentation: unknown;
|
|
57
57
|
patches: JsonPatchOperation[];
|
|
58
58
|
changed: boolean;
|
|
59
59
|
/** Slides whose own design hides a deck-level change. */
|
|
@@ -61,8 +61,8 @@ export interface PreparedDesignOption {
|
|
|
61
61
|
/** Settings that need a logo the document does not have. */
|
|
62
62
|
warnings: DesignWarning[];
|
|
63
63
|
}
|
|
64
|
-
export interface DesignOptionChange extends Omit<EditorChange, "
|
|
65
|
-
|
|
64
|
+
export interface DesignOptionChange extends Omit<EditorChange, "presentation" | "patches"> {
|
|
65
|
+
presentation: unknown;
|
|
66
66
|
patches: JsonPatchOperation[];
|
|
67
67
|
option: string;
|
|
68
68
|
scope: "deck" | "slide";
|
|
@@ -73,16 +73,16 @@ export interface DesignOptionChange extends Omit<EditorChange, "document" | "pat
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/** Compute the patch for one option. `null` removes it at that scope; object options (watermark, slideImage) merge the fields passed. */
|
|
76
|
-
export declare function prepareDesignOption(
|
|
76
|
+
export declare function prepareDesignOption(presentation: unknown, option: DesignOptionId, value: unknown, options?: DesignOptionOptions): PreparedDesignOption;
|
|
77
77
|
/** Set one option as a single undoable transaction. */
|
|
78
78
|
export declare function setDesignOption(editor: EditorSession, option: DesignOptionId, value: unknown, options?: DesignOptionOptions): DesignOptionChange;
|
|
79
79
|
/** `{ value, scope, inherited }`; scope is "slide", "deck" or "default". */
|
|
80
|
-
export declare function getDesignOption(
|
|
81
|
-
export declare function prepareLogoVariant(
|
|
80
|
+
export declare function getDesignOption(presentation: unknown, option: DesignOptionId, options?: Pick<DesignOptionOptions, "slideIndex" | "index">): { value: unknown; scope: "slide" | "deck" | "default"; inherited: boolean };
|
|
81
|
+
export declare function prepareLogoVariant(presentation: unknown, variant: LogoVariant, source: string | Record<string, unknown> | null, options?: DesignOptionOptions): PreparedDesignOption;
|
|
82
82
|
/** Set or clear one `design.logo` variant; a lone default stays a bare source. */
|
|
83
83
|
export declare function setLogoVariant(editor: EditorSession, variant: LogoVariant, source: string | Record<string, unknown> | null, options?: DesignOptionOptions): DesignOptionChange;
|
|
84
84
|
/** The variants `design.logo` sets at a scope (a bare logo is reported as `default`). */
|
|
85
|
-
export declare function readLogoVariants(
|
|
85
|
+
export declare function readLogoVariants(presentation: unknown, options?: Pick<DesignOptionOptions, "slideIndex">): Partial<Record<LogoVariant, unknown>>;
|
|
86
86
|
export interface HeaderFooterZoneFields {
|
|
87
87
|
logo?: boolean | null;
|
|
88
88
|
text?: string | null;
|
|
@@ -92,17 +92,19 @@ export interface HeaderFooterZoneFields {
|
|
|
92
92
|
date?: boolean | string | null;
|
|
93
93
|
dateFormat?: string | null;
|
|
94
94
|
organization?: boolean | null;
|
|
95
|
+
/** The first speaker's name and title ("Ada Lovelace, CTO"). */
|
|
96
|
+
speaker?: boolean | null;
|
|
95
97
|
socials?: boolean | null;
|
|
96
98
|
section?: boolean | null;
|
|
97
99
|
}
|
|
98
|
-
export declare function prepareHeaderFooterZone(
|
|
100
|
+
export declare function prepareHeaderFooterZone(presentation: unknown, which: "header" | "footer", zone: HeaderFooterZone, fields: HeaderFooterZoneFields, options?: DesignOptionOptions): PreparedDesignOption;
|
|
99
101
|
/** Merge fields into one header or footer zone; null, false (flags) or "" remove a field, an empty zone and header are removed. A slide's own header replaces the deck's whole one, so the first edit on a slide starts from a copy of the deck's and keeps its other zones. */
|
|
100
102
|
export declare function setHeaderFooterZone(editor: EditorSession, which: "header" | "footer", zone: HeaderFooterZone, fields: HeaderFooterZoneFields, options?: DesignOptionOptions): DesignOptionChange;
|
|
101
103
|
/** One zone's fields as they apply at a scope: the slide's own header or footer when it has one, else the deck's. */
|
|
102
|
-
export declare function readHeaderFooterZone(
|
|
104
|
+
export declare function readHeaderFooterZone(presentation: unknown, which: "header" | "footer", zone: HeaderFooterZone, options?: Pick<DesignOptionOptions, "slideIndex">): HeaderFooterZoneFields;
|
|
103
105
|
/** Whether the scope sets the header or footer itself (`own`), shows the deck's (`inherited`) or hides it with `false` (`hidden`). */
|
|
104
|
-
export declare function headerFooterState(
|
|
106
|
+
export declare function headerFooterState(presentation: unknown, which: "header" | "footer", options?: Pick<DesignOptionOptions, "slideIndex">): { own: boolean; inherited: boolean; hidden: boolean };
|
|
105
107
|
/** Whether a logo resolves for the slide: slide design, deck design, then the primary organization. */
|
|
106
|
-
export declare function hasResolvableLogo(
|
|
107
|
-
/** Settings that need content the document does not have: a logo (zones with `logo: true`, picture bullets),
|
|
108
|
-
export declare function designWarnings(
|
|
108
|
+
export declare function hasResolvableLogo(presentation: unknown, slideIndex: number): boolean;
|
|
109
|
+
/** Settings that need content the document does not have: a logo (zones with `logo: true`, picture bullets), an organization or its social profiles, or a named speaker, for zones that show them. */
|
|
110
|
+
export declare function designWarnings(presentation: unknown, slideIndex?: number): DesignWarning[];
|