@livepreso/content-react 2.3.1 → 3.0.1

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.
@@ -6,5 +6,9 @@ export function OverlayPortal({ children }) {
6
6
  if ((!entered && !exiting) || exited) {
7
7
  return;
8
8
  }
9
- return createPortal(children, window.Bridge.UI.overlay);
9
+ const overlay = window.Bridge.UI.overlay;
10
+ if (!overlay) {
11
+ return;
12
+ }
13
+ return createPortal(children, overlay);
10
14
  }
@@ -54,6 +54,24 @@ function useDefaultCMSValueFromPresomanager(id, { slidePath = null } = {}) {
54
54
  return cmsVals?.find?.((c) => c.key === key)?.value ?? null;
55
55
  }
56
56
 
57
+ function isShowing(presentationKey) {
58
+ return Bridge.presenter.getPresentation().current?.key === presentationKey;
59
+ }
60
+
61
+ /**
62
+ * Whether the app allows this field to be edited.
63
+ *
64
+ * True where nothing says otherwise, so a deck older than the check keeps
65
+ * working.
66
+ */
67
+ export function useCanEditCMSValKey(id) {
68
+ const { slidePath } = useSlide();
69
+
70
+ // Neighbouring slides are mounted too, so the key resolves against this slide
71
+ // rather than whichever one the presenter is showing.
72
+ return Bridge.CMSVals?.canEditKey?.(id, { slidePath }) ?? true;
73
+ }
74
+
57
75
  export function useCMSVal(
58
76
  id,
59
77
  { enabled = true, slidePath: slidePathOverride = null } = {},
@@ -64,10 +82,11 @@ export function useCMSVal(
64
82
  const {
65
83
  isUserTemplate,
66
84
  isUserTemplateLibrary,
85
+ presentationKey,
67
86
  slidePath: ownSlidePath,
68
87
  } = useSlide();
69
88
 
70
- // Defaults to the calling slide's path.
89
+ // A caller that passes no path gets the path of the slide it renders in.
71
90
  const slidePath = slidePathOverride ?? ownSlidePath;
72
91
  const [userTemplateValue, setUserTemplateValue] = useUserTemplateCMS(id);
73
92
  const defaultFromPM = useDefaultCMSValueFromPresomanager(id, { slidePath });
@@ -95,6 +114,12 @@ export function useCMSVal(
95
114
  return defaultFromPM;
96
115
  }
97
116
 
117
+ // Neighbouring slides are mounted too, and the presenter resolves a key
118
+ // against the slide that is showing.
119
+ if (!slidePath && !isShowing(presentationKey)) {
120
+ return undefined;
121
+ }
122
+
98
123
  return Bridge.CMSVals.get(id, { slidePath });
99
124
  }, [
100
125
  enabled,
@@ -102,11 +127,14 @@ export function useCMSVal(
102
127
  defaultFromPM,
103
128
  isUserTemplate,
104
129
  isUserTemplateLibrary,
130
+ userTemplateValue,
105
131
  isActiveSlide,
106
132
  isPresomanager,
133
+ // Read off the article, so it can arrive after the first render.
134
+ presentationKey,
107
135
  ]);
108
136
 
109
- const [value, setCMSValue] = useState(getCmsVal());
137
+ const [value, setCMSValue] = useState(getCmsVal);
110
138
 
111
139
  function updateCMSVal(value) {
112
140
  if (isUserTemplate) {
@@ -114,15 +142,28 @@ export function useCMSVal(
114
142
  } else {
115
143
  Bridge.CMSVals.set(id, value, { slidePath });
116
144
  }
117
- setCMSValue(value);
118
145
  }
119
146
 
147
+ useEffect(() => {
148
+ const stored = getCmsVal();
149
+
150
+ // undefined means the value cannot be read from here, so a slide part
151
+ // way out of view keeps what it had instead of blanking.
152
+ if (stored !== undefined) {
153
+ setCMSValue(stored);
154
+ }
155
+ }, [getCmsVal]);
156
+
120
157
  useEffect(() => {
121
158
  if (!enabled || !isActiveSlide) {
122
159
  return;
123
160
  }
124
161
 
125
162
  function onCMSValChanged(key, newValue) {
163
+ if (!slidePath && !isShowing(presentationKey)) {
164
+ return;
165
+ }
166
+
126
167
  const fullKey = Bridge.CMSVals.getFullKey(id, { slidePath });
127
168
 
128
169
  if (key !== fullKey) {
@@ -132,10 +173,10 @@ export function useCMSVal(
132
173
  setCMSValue(newValue);
133
174
  }
134
175
 
135
- Bridge.presenter.getPresentation().on("cmsValChanged", onCMSValChanged);
176
+ Bridge.CMSVals.on("changed", onCMSValChanged);
136
177
 
137
178
  return () => {
138
- Bridge.presenter.getPresentation().off("cmsValChanged", onCMSValChanged);
179
+ Bridge.CMSVals.off("changed", onCMSValChanged);
139
180
  };
140
181
  }, [enabled, id, getCmsVal]);
141
182
 
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ import { act, renderHook } from "@testing-library/react";
6
+
7
+ import { useCMSVal } from "./cmsvals.js";
8
+ import { useModes } from "./use-modes.js";
9
+ import { useSlide, useSlideModel } from "./slide.js";
10
+ import { useSlideContext } from "./use-slide-context.js";
11
+ import { useTransitions } from "./use-transitions";
12
+
13
+ jest.mock("./use-modes.js", () => ({ useModes: jest.fn() }));
14
+ jest.mock("./slide.js", () => ({
15
+ useSlide: jest.fn(),
16
+ useSlideModel: jest.fn(),
17
+ }));
18
+ jest.mock("./use-slide-context.js", () => ({ useSlideContext: jest.fn() }));
19
+ jest.mock("./use-transitions", () => ({ useTransitions: jest.fn() }));
20
+
21
+ function stub({ showing = "a-preso", stored = null } = {}) {
22
+ const listeners = new Set();
23
+ let current = showing ? { key: showing } : null;
24
+
25
+ const presentation = {
26
+ on: (name, listener) => listeners.add(listener),
27
+ off: (name, listener) => listeners.delete(listener),
28
+ get current() {
29
+ return current;
30
+ },
31
+ };
32
+
33
+ global.Bridge = {
34
+ CMSVals: {
35
+ get: jest.fn(() => stored),
36
+ set: jest.fn(),
37
+ getFullKey: (id) => `stored-${id}`,
38
+ on: (name, listener) => listeners.add(listener),
39
+ off: (name, listener) => listeners.delete(listener),
40
+ },
41
+ presenter: { getPresentation: () => presentation },
42
+ };
43
+
44
+ useTransitions.mockReturnValue({ entered: true, entering: false });
45
+ useModes.mockReturnValue({ isCdk: true });
46
+ useSlideModel.mockReturnValue(null);
47
+ useSlideContext.mockReturnValue([null, jest.fn()]);
48
+ useSlide.mockReturnValue({ presentationKey: showing });
49
+
50
+ return {
51
+ report: (key, value) => {
52
+ for (const listener of listeners) {
53
+ listener(key, value);
54
+ }
55
+ },
56
+ };
57
+ }
58
+
59
+ describe("useCMSVal", () => {
60
+ it("should read again once the slide it is on says which one it is", () => {
61
+ stub({ showing: "a-preso", stored: "what the store holds" });
62
+ useSlide.mockReturnValue({ presentationKey: null });
63
+
64
+ const { result, rerender } = renderHook(() => useCMSVal("a-note"));
65
+
66
+ expect(result.current[0]).toBeUndefined();
67
+
68
+ useSlide.mockReturnValue({ presentationKey: "a-preso" });
69
+ rerender();
70
+
71
+ expect(result.current[0]).toBe("what the store holds");
72
+ });
73
+
74
+ it("should show what the store reported rather than what was written", () => {
75
+ const { report } = stub({ stored: "what the store holds" });
76
+
77
+ const { result } = renderHook(() => useCMSVal("a-note"));
78
+
79
+ act(() => result.current[1]("typed"));
80
+
81
+ expect(result.current[0]).toBe("what the store holds");
82
+
83
+ act(() => report("stored-a-note", "what the store kept"));
84
+
85
+ expect(result.current[0]).toBe("what the store kept");
86
+ });
87
+ });
@@ -45,16 +45,18 @@ export function useCWEEditableInfo(id) {
45
45
  }
46
46
 
47
47
  useEffect(() => {
48
- if (isPresomanager) return;
48
+ if (isPresomanager) {
49
+ return;
50
+ }
49
51
 
50
52
  function onContextChange() {
51
53
  updateValue();
52
54
  }
53
55
 
54
- Bridge.presenter.getPresentation().on("change:context", onContextChange);
56
+ Bridge.Context.on("changed", onContextChange);
55
57
 
56
58
  return () => {
57
- Bridge.presenter.getPresentation().off("change:context", onContextChange);
59
+ Bridge.Context.off("changed", onContextChange);
58
60
  };
59
61
  }, [isPresomanager, value, getValue]);
60
62
 
@@ -9,6 +9,7 @@ export { useTransitions } from "./use-transitions.js";
9
9
  export { useSubslides } from "./use-subslides.js";
10
10
  export { useSlideDone } from "./use-slide-done.js";
11
11
  export { useEditableFieldKey } from "./use-editable-field-key.js";
12
+ export { useTargetKey } from "./use-target-key.js";
12
13
  export { useEditableInitialState } from "./use-editable-initial-state.js";
13
14
  export {
14
15
  usePrepEditable,
@@ -17,4 +18,5 @@ export {
17
18
  usePrepEditableImage,
18
19
  } from "./prep-editable.js";
19
20
  export { useCWEEditableInfo, useCWEEditableValue } from "./cwe-editable.js";
20
- export { useCMSVal } from "./cmsvals.js";
21
+ export { useCMSVal, useCanEditCMSValKey } from "./cmsvals.js";
22
+ export { useActions } from "./use-actions.js";
@@ -9,13 +9,14 @@ import { useEditableFieldKey } from "./use-editable-field-key";
9
9
  * of the context to allow the user template magic to work, and this is the
10
10
  * function to use to make that seamless.
11
11
  */
12
- export function usePrepEditable(editableKey) {
13
- const { isPresomanager } = useModes();
12
+ export function usePrepEditable(editableKey, { isGlobal = false } = {}) {
13
+ const { isPresomanager, isReadOnly: isPageReadOnly } = useModes();
14
14
  const { slideID, isUserTemplateLibrary } = useSlide();
15
15
 
16
16
  const key = useEditableFieldKey(editableKey, { isPrep: true });
17
17
 
18
- const prepKey = `${slideID}--${key}`;
18
+ // Not owned by a slide, so every slide using this key sees one value.
19
+ const prepKey = isGlobal ? key : `${slideID}--${key}`;
19
20
  const keyPath = `_editableContent.${prepKey}`;
20
21
 
21
22
  const [obj, setObj] = useSlideContext(
@@ -25,7 +26,7 @@ export function usePrepEditable(editableKey) {
25
26
  value: null,
26
27
  },
27
28
  {
28
- enabled: !isPresomanager || isUserTemplateLibrary,
29
+ enabled: !isPageReadOnly && (!isPresomanager || isUserTemplateLibrary),
29
30
  },
30
31
  );
31
32
 
@@ -70,7 +71,7 @@ export function usePrepEditableValue(id, defaultValue = null) {
70
71
  * Returns the URL of a prep editable dynamic image on the current slide.
71
72
  */
72
73
  export function usePrepEditableImage(editableKey) {
73
- const { isPresomanager } = useModes();
74
+ const { isPresomanager, isReadOnly: isPageReadOnly } = useModes();
74
75
  const { slideID, isUserTemplateLibrary } = useSlide();
75
76
 
76
77
  const key = useEditableFieldKey(editableKey, { isPrep: true });
@@ -82,7 +83,7 @@ export function usePrepEditableImage(editableKey) {
82
83
  keyPath,
83
84
  { image: null },
84
85
  {
85
- enabled: !isPresomanager || isUserTemplateLibrary,
86
+ enabled: !isPageReadOnly && (!isPresomanager || isUserTemplateLibrary),
86
87
  },
87
88
  );
88
89
 
@@ -0,0 +1,78 @@
1
+ import { useModes } from "./use-modes.js";
2
+ import { usePrepEditable } from "./prep-editable.js";
3
+ import { useSlide } from "./slide.js";
4
+ import { useSlideContext } from "./use-slide-context.js";
5
+
6
+ jest.mock("./use-modes.js", () => ({ useModes: jest.fn() }));
7
+ jest.mock("./slide.js", () => ({ useSlide: jest.fn() }));
8
+ jest.mock("./use-slide-context.js", () => ({ useSlideContext: jest.fn() }));
9
+
10
+ function stub() {
11
+ useModes.mockReturnValue({ isPresomanager: false });
12
+ useSlide.mockReturnValue({
13
+ slideID: "opening",
14
+ isPMTemplate: false,
15
+ isUserTemplate: false,
16
+ isUserTemplateLibrary: false,
17
+ });
18
+ useSlideContext.mockReturnValue([{ value: "Stored" }, jest.fn()]);
19
+ }
20
+
21
+ describe("usePrepEditable", () => {
22
+ beforeEach(() => {
23
+ jest.clearAllMocks();
24
+ stub();
25
+ });
26
+
27
+ it("should keep a value against the slide it was written on", () => {
28
+ usePrepEditable("note");
29
+
30
+ expect(useSlideContext).toHaveBeenCalledWith(
31
+ "_editableContent.opening--note",
32
+ expect.anything(),
33
+ expect.anything(),
34
+ );
35
+ });
36
+
37
+ it("should offer a prep editable on a user template being edited", () => {
38
+ useModes.mockReturnValue({ isPresomanager: true, isReadOnly: false });
39
+ useSlide.mockReturnValue({
40
+ slideID: "opening",
41
+ isUserTemplateLibrary: true,
42
+ });
43
+
44
+ usePrepEditable("note");
45
+
46
+ expect(useSlideContext).toHaveBeenCalledWith(
47
+ expect.anything(),
48
+ expect.anything(),
49
+ { enabled: true },
50
+ );
51
+ });
52
+
53
+ it("should offer none on a template preview that stores nothing", () => {
54
+ useModes.mockReturnValue({ isPresomanager: true, isReadOnly: true });
55
+ useSlide.mockReturnValue({
56
+ slideID: "opening",
57
+ isUserTemplateLibrary: true,
58
+ });
59
+
60
+ usePrepEditable("note");
61
+
62
+ expect(useSlideContext).toHaveBeenCalledWith(
63
+ expect.anything(),
64
+ expect.anything(),
65
+ { enabled: false },
66
+ );
67
+ });
68
+
69
+ it("should keep a global value under its bare key", () => {
70
+ usePrepEditable("note", { isGlobal: true });
71
+
72
+ expect(useSlideContext).toHaveBeenCalledWith(
73
+ "_editableContent.note",
74
+ expect.anything(),
75
+ expect.anything(),
76
+ );
77
+ });
78
+ });
@@ -6,8 +6,8 @@ export function useSlide() {
6
6
  const {
7
7
  slideID,
8
8
  slideKey,
9
- presentationKey,
10
9
  slidePath,
10
+ presentationKey,
11
11
  isSlideReady,
12
12
  doneRegister,
13
13
  isCurrentSlide,
@@ -22,8 +22,8 @@ export function useSlide() {
22
22
  return {
23
23
  slideID,
24
24
  slideKey,
25
- presentationKey,
26
25
  slidePath,
26
+ presentationKey,
27
27
  isSlideReady,
28
28
  doneRegister,
29
29
  isCurrentSlide,
@@ -0,0 +1,112 @@
1
+ import { useEffect, useMemo, useReducer } from "react";
2
+ import { bindActions, provideRendered } from "@livepreso/webpack-deck";
3
+
4
+ import { useSlide } from "./slide.js";
5
+ import { useTargetKey } from "./use-target-key.js";
6
+
7
+ /**
8
+ * The queries and mutations this editable declares, bound to
9
+ * wherever it is stored.
10
+ *
11
+ * ```
12
+ * const { queries, mutations } = useActions(editable, props);
13
+ *
14
+ * value={queries.get()} onChange={(html) => mutations.set({ html })}
15
+ * await queries.getAsync() await mutations.set({ html })
16
+ * ```
17
+ *
18
+ * A person editing the content reaches the mutation an assistant calls, which
19
+ * is why a component is never handed a plain setter.
20
+ */
21
+ export function useActions(
22
+ source,
23
+ args,
24
+ { defaultValue = null, field, rendered, onValidationError } = {},
25
+ ) {
26
+ const { slidePath } = useSlide();
27
+ const targetKey = useTargetKey();
28
+
29
+ const bound = useMemo(
30
+ () =>
31
+ bindActions(source, args, {
32
+ targetKey,
33
+ slidePath,
34
+ defaultValue,
35
+ field,
36
+ isRegistered: false,
37
+ }),
38
+ // The declaration and the args are the same objects every render, and the
39
+ // binding reads its stores on every call, so only where it stores can
40
+ // change under it.
41
+ [targetKey, slidePath],
42
+ );
43
+
44
+ useRendered(bound.key, rendered);
45
+
46
+ // Lists a field the build missed while it is on screen, so it can be edited.
47
+ useEffect(() => bound.register(), [bound]);
48
+
49
+ const [, render] = useReducer((count) => count + 1, 0);
50
+
51
+ useEffect(() => {
52
+ bound.on("changed", render);
53
+
54
+ return () => bound.off("changed", render);
55
+ }, [bound]);
56
+
57
+ useEffect(() => {
58
+ if (!onValidationError) {
59
+ return undefined;
60
+ }
61
+
62
+ bound.on("validation-error", onValidationError);
63
+
64
+ return () => bound.off("validation-error", onValidationError);
65
+ }, [bound, onValidationError]);
66
+
67
+ return {
68
+ channel: bound.channel,
69
+ errors: bound.errors,
70
+ queries: bound.queries,
71
+ mutations: bound.mutations,
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Holds what a field has while it is on screen, for the entries that name it.
77
+ *
78
+ * The nodes are read on every render because a ref is filled after the render
79
+ * that created it, so reading during one would hold null.
80
+ */
81
+ function useRendered(fieldKey, refs) {
82
+ const names = Object.keys(refs ?? {});
83
+
84
+ useEffect(() => {
85
+ if (names.length === 0) {
86
+ return undefined;
87
+ }
88
+
89
+ return provideRendered(
90
+ fieldKey,
91
+ Object.fromEntries(
92
+ names.map((name) => [name, readRenderedValue(refs[name])]),
93
+ ),
94
+ );
95
+ });
96
+ }
97
+
98
+ /**
99
+ * A ref gives its element. A flag such as `always: true` passes through, so an entry can
100
+ * require the slide on screen.
101
+ */
102
+ function readRenderedValue(value) {
103
+ if (isRef(value)) {
104
+ return value.current;
105
+ }
106
+
107
+ return value;
108
+ }
109
+
110
+ function isRef(value) {
111
+ return typeof value === "object" && value !== null && "current" in value;
112
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ import { act, renderHook } from "@testing-library/react";
6
+ import {
7
+ defineEditableSource,
8
+ defineActions,
9
+ prop,
10
+ } from "@livepreso/webpack-deck";
11
+
12
+ import { useActions } from "./use-actions.js";
13
+ import { useSlide } from "./slide.js";
14
+ import { useTargetKey } from "./use-target-key.js";
15
+
16
+ jest.mock("./slide.js", () => ({ useSlide: jest.fn() }));
17
+ jest.mock("./use-target-key.js", () => ({ useTargetKey: jest.fn() }));
18
+
19
+ function Field() {}
20
+
21
+ function aNoteField() {
22
+ return defineEditableSource(Field, {
23
+ fields: { id: prop("name") },
24
+ actions: defineActions({
25
+ defaultState: "",
26
+ mime: "text/plain",
27
+ description: "A note.",
28
+ queries: { get: ({ state }) => state },
29
+ mutations: { set: ({ input }) => input.text.trim() },
30
+ }),
31
+ everyone: { when: prop("isCompany"), key: "{targetKey}-{id}" },
32
+ });
33
+ }
34
+
35
+ function stub({
36
+ state = null,
37
+ channel = "everyone",
38
+ applyResult = { errors: {} },
39
+ } = {}) {
40
+ const listeners = new Set();
41
+
42
+ const versionOne = {
43
+ read: jest.fn().mockReturnValue({ state, channel, provided: {} }),
44
+ apply: jest.fn().mockResolvedValue(applyResult),
45
+ on: (name, listener) => listeners.add(listener),
46
+ off: (name, listener) => listeners.delete(listener),
47
+ };
48
+
49
+ global.Bridge = { Interface: { open: () => versionOne } };
50
+
51
+ useTargetKey.mockReturnValue("a-target");
52
+ useSlide.mockReturnValue({ slidePath: "a-deck/a-section/a-target" });
53
+
54
+ return {
55
+ versionOne,
56
+ raiseChanged: (changed) => {
57
+ for (const listener of listeners) {
58
+ listener(changed);
59
+ }
60
+ },
61
+ };
62
+ }
63
+
64
+ function aDrawnField() {
65
+ return defineEditableSource(Field, {
66
+ fields: { id: prop("name") },
67
+ actions: defineActions({
68
+ queries: { readDrawn: ({ rendered }) => rendered.drawn },
69
+ }),
70
+ everyone: { key: "{targetKey}-{id}" },
71
+ });
72
+ }
73
+
74
+ function renderField(options) {
75
+ return renderHook(() =>
76
+ useActions(aNoteField(), { name: "a-field", isCompany: true }, options),
77
+ );
78
+ }
79
+
80
+ describe("useActions", () => {
81
+ it("should give back what the binding reads", () => {
82
+ stub({ state: "Stored" });
83
+
84
+ expect(renderField().result.current.queries.get()).toBe("Stored");
85
+ });
86
+
87
+ it("should say which channel the field stores on", () => {
88
+ stub({ channel: "personal" });
89
+
90
+ expect(renderField().result.current.channel).toBe("personal");
91
+ });
92
+
93
+ it("should read the channel fresh once the presenter has answered it", () => {
94
+ const { raiseChanged, versionOne } = stub({ channel: null });
95
+ const field = renderField();
96
+
97
+ // While the presenter reports no channel, the field takes the declared one.
98
+ expect(field.result.current.channel).toBe("everyone");
99
+
100
+ versionOne.read.mockReturnValue({
101
+ state: "Stored",
102
+ channel: "personal",
103
+ provided: {},
104
+ });
105
+ act(() =>
106
+ raiseChanged({
107
+ slidePath: "a-deck/a-section/a-target",
108
+ field: "a-target-a-field",
109
+ }),
110
+ );
111
+
112
+ expect(field.result.current.channel).toBe("personal");
113
+ });
114
+
115
+ it("should render again once a mutation's errors change", async () => {
116
+ stub({ applyResult: { errors: { "too-long": {} } } });
117
+ const field = renderField();
118
+
119
+ await act(() => field.result.current.mutations.set({ text: "New" }));
120
+
121
+ expect(field.result.current.errors.set).toEqual({ "too-long": {} });
122
+ });
123
+
124
+ it.each([
125
+ ["a ref, as its element", { current: "an-element" }, "an-element"],
126
+ ["a plain value, as it is", true, true],
127
+ ])("should hand a query reading rendered %s", (_label, drawn, expected) => {
128
+ stub();
129
+
130
+ const field = renderHook(() =>
131
+ useActions(aDrawnField(), { name: "a-field" }, { rendered: { drawn } }),
132
+ );
133
+
134
+ expect(field.result.current.queries.readDrawn()).toBe(expected);
135
+ });
136
+
137
+ it("should tell onValidationError about a call that failed a rule", async () => {
138
+ stub({ applyResult: { errors: { "too-long": {} } } });
139
+ const onValidationError = jest.fn();
140
+ const field = renderField({ onValidationError });
141
+
142
+ await act(() => field.result.current.mutations.set({ text: "New" }));
143
+
144
+ expect(onValidationError).toHaveBeenCalledWith({
145
+ operation: "set",
146
+ input: { text: "New" },
147
+ errors: { "too-long": {} },
148
+ });
149
+ });
150
+ });
@@ -13,7 +13,12 @@ export function useEditableFieldKey(key) {
13
13
  const presomanagerTemplateKey = getPMTemplateFieldKey(key, slideID);
14
14
  const userTemplateKey = getUserTemplateFieldKey(key, slideID);
15
15
 
16
- if (isUserTemplate) return userTemplateKey;
17
- if (isPMTemplate) return presomanagerTemplateKey;
16
+ if (isUserTemplate) {
17
+ return userTemplateKey;
18
+ }
19
+
20
+ if (isPMTemplate) {
21
+ return presomanagerTemplateKey;
22
+ }
18
23
  return key;
19
24
  }
@@ -164,8 +164,8 @@ export function useRootSlide(article) {
164
164
  // If for whatever reason this isn't in the dom, we fall back to the ID.
165
165
  presentationKey,
166
166
 
167
- // This is the data-slide-path on the article. Slide path represented as
168
- // "deck/section/slide". Useful for Bridge commands that require a path.
167
+ // Reads data-slide-path from the article, in the form
168
+ // "deck/section/slide", which is the form Bridge commands expect.
169
169
  slidePath,
170
170
 
171
171
  isCurrentSlide: isCurrent,