@classic-homes/slides 0.3.0 → 0.4.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/README.md CHANGED
@@ -165,7 +165,17 @@ The core entry has no Node dependencies (it runs in Workers and browsers);
165
165
  (with `hint`s for chart, grid, url, video-url and point pickers),
166
166
  `blankSlide(layout)` gives a valid starting slide, and `renderDeck(deck,
167
167
  { includeHidden: true, initialSlide })` plus the `postMessage` API drive a
168
- live preview.
168
+ live preview. Fields flagged `advanced` (slide id, speaking time, accent
169
+ colour) belong behind an "Advanced" section.
170
+
171
+ Click-to-edit: render the preview with `editorMarkers: true` and, inside an
172
+ iframe, clicking a title, bullet or picture posts
173
+ `{ type: 'classic-slides:select', index, path }` to the parent (`index` is the
174
+ deck slide, `path` the field such as `['items', 2, 'text']`, or `null` for the
175
+ slide itself). Send `{ type: 'classic-slides:highlight', index, path }` to
176
+ outline the field being edited. Never turn it on for shared or exported decks.
177
+ `layoutGalleryDeck()` returns one deck with every layout's starting slide, for
178
+ rendering a layout picker's thumbnails in one pass.
169
179
 
170
180
  Rendered HTML embeds the deck source (minus image data), so
171
181
  `extractDeckSource(html)` recovers the editable deck.
@@ -4,7 +4,7 @@ import {
4
4
  LAYOUT_CATEGORIES,
5
5
  baseSlide,
6
6
  getLayout
7
- } from "./chunk-GXGDPBAR.js";
7
+ } from "./chunk-PTTLL6PQ.js";
8
8
 
9
9
  // src/catalog.ts
10
10
  import { z } from "zod";
@@ -104,4 +104,4 @@ export {
104
104
  deckJsonSchema,
105
105
  AUTHORING_GUIDE
106
106
  };
107
- //# sourceMappingURL=chunk-YI7A4EWX.js.map
107
+ //# sourceMappingURL=chunk-JFUJ47TP.js.map
@@ -2,7 +2,7 @@ import {
2
2
  parseDeckSource,
3
3
  renderDeck,
4
4
  stringifyDeck
5
- } from "./chunk-GXGDPBAR.js";
5
+ } from "./chunk-PTTLL6PQ.js";
6
6
 
7
7
  // src/node/index.ts
8
8
  import { readFile, stat, writeFile } from "fs/promises";
@@ -103,4 +103,4 @@ export {
103
103
  saveDeckFile,
104
104
  buildDeckFile
105
105
  };
106
- //# sourceMappingURL=chunk-2IPLZ3SA.js.map
106
+ //# sourceMappingURL=chunk-L7G3VH7K.js.map
@@ -620,6 +620,10 @@ function prose(text) {
620
620
  return `<p>${inline(para)}</p>`;
621
621
  }).join("");
622
622
  }
623
+ function plain(text) {
624
+ if (!text) return "";
625
+ return text.replace(/`([^`]+)`/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1");
626
+ }
623
627
 
624
628
  // src/video.ts
625
629
  var YT = /^(?:https:\/\/)?(?:www\.|m\.)?(?:youtube\.com\/(?:watch\?(?:.*&)?v=|embed\/|shorts\/|live\/)|youtu\.be\/)([\w-]{11})/;
@@ -2193,8 +2197,11 @@ var LABELS = {
2193
2197
  imageAspect: "Photo shape",
2194
2198
  numberFrom: "First badge number",
2195
2199
  url: "URL",
2196
- id: "Slide id"
2200
+ id: "Slide id",
2201
+ time: "Speaking time",
2202
+ track: "Accent colour"
2197
2203
  };
2204
+ var ADVANCED = /* @__PURE__ */ new Set(["id", "time", "track"]);
2198
2205
  function labelFor(key) {
2199
2206
  if (LABELS[key]) return LABELS[key];
2200
2207
  const words = key.replace(/([A-Z])/g, " $1").toLowerCase();
@@ -2231,7 +2238,8 @@ function describeField(key, schema) {
2231
2238
  required,
2232
2239
  ...description ? { description } : {},
2233
2240
  ...dflt !== void 0 ? { default: dflt } : {},
2234
- ...MULTILINE.has(key) ? { multiline: true } : {}
2241
+ ...MULTILINE.has(key) ? { multiline: true } : {},
2242
+ ...ADVANCED.has(key) ? { advanced: true } : {}
2235
2243
  };
2236
2244
  if (inner === Image) return { ...base, kind: "image" };
2237
2245
  if (inner === ChartSpec) {
@@ -2335,6 +2343,16 @@ function layoutEditorInfo(name) {
2335
2343
  function allLayoutEditorInfo() {
2336
2344
  return LAYOUTS.map((l) => layoutEditorInfo(l.name));
2337
2345
  }
2346
+ function layoutGalleryDeck() {
2347
+ const slides = [];
2348
+ const slideFor = {};
2349
+ for (const layout of LAYOUTS) {
2350
+ slideFor[layout.name] = slides.length;
2351
+ slides.push(blankSlide(layout.name));
2352
+ }
2353
+ const journey2 = { steps: ["Contract", "Design", "Build", "Close", "Move in", "Warranty"] };
2354
+ return { deck: { title: "Layouts", journey: journey2, slides }, slideFor };
2355
+ }
2338
2356
  var PLACEHOLDER_IMAGE = "data:image/svg+xml;base64," + btoa(
2339
2357
  '<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="1000" viewBox="0 0 1600 1000"><rect width="1600" height="1000" fill="#ecebe6"/><rect x="24" y="24" width="1552" height="952" fill="none" stroke="#b9bcb4" stroke-width="6" stroke-dasharray="28 20"/><text x="800" y="520" text-anchor="middle" font-family="Figtree,Arial,sans-serif" font-size="64" fill="#7a7a7a">Add a photo</text></svg>'
2340
2358
  );
@@ -2567,8 +2585,83 @@ var RUNTIME_JS = String.raw`
2567
2585
  if (!embedded || e.source !== window.parent) return;
2568
2586
  if (d.type === 'classic-slides:go' && Number.isInteger(d.index)) go(d.index, true);
2569
2587
  else if (d.type === 'classic-slides:notes') { if (notes && notes.classList.contains('open') !== !!d.open) toggleNotes(); }
2588
+ else if (d.type === 'classic-slides:highlight' && editor) highlight(d.index, d.path);
2570
2589
  });
2571
2590
 
2591
+ // ---------- editor click-to-edit ----------
2592
+ // Matches the clicked element to a slide field by its visible text (or an
2593
+ // image by its src), walking up from the target, so layouts need no markup.
2594
+ const editor = embedded && body.hasAttribute('data-editor');
2595
+ const fieldsEl = editor && document.getElementById('deck-fields');
2596
+ const FIELDS = fieldsEl ? JSON.parse(fieldsEl.textContent || '[]') : [];
2597
+ const norm = (t) => (t || '').replace(/\s+/g, ' ').trim();
2598
+ function ownText(el) {
2599
+ let t = '';
2600
+ el.childNodes.forEach((n) => { if (n.nodeType === 3) t += n.textContent; });
2601
+ return norm(t);
2602
+ }
2603
+ function fieldFor(el, candidates) {
2604
+ if (el.tagName === 'IMG') {
2605
+ const src = el.getAttribute('src');
2606
+ return candidates.find((c) => c.img && c.img === src);
2607
+ }
2608
+ const text = norm(el.textContent);
2609
+ const own = ownText(el);
2610
+ return candidates.find((c) => c.text && (c.text === text || (own && c.text === own)));
2611
+ }
2612
+ function locate(target) {
2613
+ const section = target.closest && target.closest('.slide');
2614
+ if (!section) return null;
2615
+ const index = Number(section.getAttribute('data-index'));
2616
+ const candidates = FIELDS[index] || [];
2617
+ for (let el = target; el && el !== section; el = el.parentElement) {
2618
+ const field = fieldFor(el, candidates);
2619
+ if (field) return { index, path: field.path, el };
2620
+ }
2621
+ return { index, path: null, el: section };
2622
+ }
2623
+ let hovered = null;
2624
+ let selected = null;
2625
+ function highlight(index, path) {
2626
+ selected?.classList.remove('cs-edit-selected');
2627
+ selected = null;
2628
+ if (!Array.isArray(path)) return;
2629
+ const key = JSON.stringify(path);
2630
+ const candidates = FIELDS[index] || [];
2631
+ for (const section of deck.querySelectorAll('.slide[data-index="' + index + '"]')) {
2632
+ for (const el of section.querySelectorAll('*')) {
2633
+ const field = fieldFor(el, candidates);
2634
+ if (field && JSON.stringify(field.path) === key) {
2635
+ selected = el;
2636
+ el.classList.add('cs-edit-selected');
2637
+ return;
2638
+ }
2639
+ }
2640
+ }
2641
+ }
2642
+ if (editor) {
2643
+ const style = document.createElement('style');
2644
+ style.textContent = '.cs-edit-hover{outline:2px dashed rgba(0,122,255,.7);outline-offset:4px;cursor:pointer}' +
2645
+ '.cs-edit-selected{outline:2px solid rgb(0,122,255);outline-offset:4px}';
2646
+ document.head.appendChild(style);
2647
+ deck.addEventListener('click', (e) => {
2648
+ const hit = locate(e.target);
2649
+ if (!hit) return;
2650
+ // Links on a slide would navigate the preview away
2651
+ if (e.target.closest('a[href]')) e.preventDefault();
2652
+ window.parent.postMessage({ type: 'classic-slides:select', index: hit.index, path: hit.path }, '*');
2653
+ }, true);
2654
+ deck.addEventListener('mouseover', (e) => {
2655
+ const hit = locate(e.target);
2656
+ const el = hit && hit.path ? hit.el : null;
2657
+ if (el === hovered) return;
2658
+ hovered?.classList.remove('cs-edit-hover');
2659
+ hovered = el;
2660
+ el?.classList.add('cs-edit-hover');
2661
+ });
2662
+ deck.addEventListener('mouseleave', () => { hovered?.classList.remove('cs-edit-hover'); hovered = null; });
2663
+ }
2664
+
2572
2665
  function onKey(e) {
2573
2666
  if (e.metaKey || e.ctrlKey || e.altKey) return;
2574
2667
  const k = e.key;
@@ -3286,6 +3379,48 @@ body.presenter{margin:0;height:100vh;display:grid;grid-template-rows:1fr auto;ba
3286
3379
 
3287
3380
  // src/render.ts
3288
3381
  var PREVIEW_SIZE = { width: 1280, height: 720 };
3382
+ var NON_VISUAL_KEYS = /* @__PURE__ */ new Set([
3383
+ "layout",
3384
+ "id",
3385
+ "notes",
3386
+ "time",
3387
+ "hidden",
3388
+ "background",
3389
+ "backgroundImage",
3390
+ "overlay",
3391
+ "track",
3392
+ "step",
3393
+ "stepDone",
3394
+ "showJourney",
3395
+ "href",
3396
+ "url",
3397
+ "fit",
3398
+ "focus",
3399
+ "size",
3400
+ "alt"
3401
+ ]);
3402
+ function fieldMarkers(slide, resolveImage = (src) => src) {
3403
+ const out = [];
3404
+ const walk = (value, path) => {
3405
+ if (typeof value === "string") {
3406
+ const text = plain(value).replace(/\s+/g, " ").trim();
3407
+ if (text) out.push({ path, text });
3408
+ } else if (Array.isArray(value)) {
3409
+ value.forEach((v, i) => walk(v, [...path, i]));
3410
+ } else if (value && typeof value === "object") {
3411
+ const obj = value;
3412
+ if (typeof obj.src === "string") {
3413
+ out.push({ path, img: resolveImage(obj.src) });
3414
+ return;
3415
+ }
3416
+ for (const [key, v] of Object.entries(obj)) {
3417
+ if (!NON_VISUAL_KEYS.has(key)) walk(v, [...path, key]);
3418
+ }
3419
+ }
3420
+ };
3421
+ walk(slide, []);
3422
+ return out;
3423
+ }
3289
3424
  var passThrough = (src) => {
3290
3425
  if (/^(data:|https?:)/.test(src)) return src;
3291
3426
  throw new Error(
@@ -3476,6 +3611,12 @@ async function renderDeck(input, options = {}) {
3476
3611
  });
3477
3612
  });
3478
3613
  const withNotes = options.speakerNotes !== false;
3614
+ const fields = options.editorMarkers ? `<script type="application/json" id="deck-fields">${JSON.stringify(
3615
+ deck.slides.map(
3616
+ (slide) => fieldMarkers(slide, (src) => images.get(src) ?? src)
3617
+ )
3618
+ ).replace(/</g, "\\u003c")}</script>
3619
+ ` : "";
3479
3620
  const json = (v) => JSON.stringify(v).replace(/</g, "\\u003c");
3480
3621
  const stripData = (key, v) => !withNotes && key === "notes" ? void 0 : typeof v === "string" && /^data:/i.test(v) ? "(embedded)" : v;
3481
3622
  const source = options.embedSource === false ? "" : `<script type="application/json" id="deck-source">${JSON.stringify(deck, stripData).replace(/</g, "\\u003c")}</script>
@@ -3492,7 +3633,7 @@ ${deck.description ? `<meta name="description" content="${escapeAttr(deck.descri
3492
3633
  <style id="brand-fonts">${fontsCss()}</style>
3493
3634
  <style>${tokensCss(brand.stock ? void 0 : brand.palette)}${DECK_CSS}${DECK_CSS_EXTRA}</style>
3494
3635
  </head>
3495
- <body${wantFooter ? ' class="has-foot"' : ""}${options.initialSlide ? ` data-initial-slide="${Math.max(0, Math.floor(options.initialSlide))}"` : ""}${deck.settings.autoAdvance ? ` data-auto-advance="${deck.settings.autoAdvance}"` : ""}${deck.settings.loop ? " data-loop" : ""}${options.allowEmbeddedKiosk ? " data-embedded-kiosk" : ""}>
3636
+ <body${wantFooter ? ' class="has-foot"' : ""}${options.initialSlide ? ` data-initial-slide="${Math.max(0, Math.floor(options.initialSlide))}"` : ""}${deck.settings.autoAdvance ? ` data-auto-advance="${deck.settings.autoAdvance}"` : ""}${deck.settings.loop ? " data-loop" : ""}${options.allowEmbeddedKiosk ? " data-embedded-kiosk" : ""}${options.editorMarkers ? " data-editor" : ""}>
3496
3637
  <div class="rail" id="rail"></div>
3497
3638
  <main class="deck" id="deck">
3498
3639
  ${sections.join("\n")}
@@ -3503,7 +3644,7 @@ ${withNotes ? `<div class="hud"><span class="counter" id="counter" aria-live="po
3503
3644
  <aside class="notes" id="notes" aria-live="polite"><div class="notes__head"><span class="tag">Speaker notes</span><span class="time" id="notesTime"></span></div><div class="notes__body" id="notesBody"></div></aside>` : `<div class="hud"><span class="counter" id="counter" aria-live="polite"></span></div>
3504
3645
  <div class="hint"><kbd>&uarr;</kbd><kbd>&darr;</kbd> or <kbd>Space</kbd> navigate &middot; <kbd>O</kbd> overview &middot; <kbd>F</kbd> fullscreen</div>`}
3505
3646
  <script type="application/json" id="deck-notes">${json(withNotes ? notes : [])}</script>
3506
- ${source}<script>${RUNTIME_JS}</script>
3647
+ ${source}${fields}<script>${RUNTIME_JS}</script>
3507
3648
  </body>
3508
3649
  </html>
3509
3650
  `;
@@ -3727,6 +3868,7 @@ export {
3727
3868
  commonSlideFields,
3728
3869
  layoutEditorInfo,
3729
3870
  allLayoutEditorInfo,
3871
+ layoutGalleryDeck,
3730
3872
  PLACEHOLDER_IMAGE,
3731
3873
  isPlaceholderImage,
3732
3874
  blankSlide,
@@ -3735,10 +3877,11 @@ export {
3735
3877
  formatIssues,
3736
3878
  DeckError,
3737
3879
  PREVIEW_SIZE,
3880
+ fieldMarkers,
3738
3881
  collectImageSources,
3739
3882
  renderDeck,
3740
3883
  extractDeckSource,
3741
3884
  parseDeckSource,
3742
3885
  stringifyDeck
3743
3886
  };
3744
- //# sourceMappingURL=chunk-GXGDPBAR.js.map
3887
+ //# sourceMappingURL=chunk-PTTLL6PQ.js.map