domma-cms 0.89.0 → 0.90.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/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/js/app.js +2 -2
- package/admin/js/lib/page-editor-arrange.js +1 -1
- package/admin/js/lib/page-editor-reference.js +153 -0
- package/admin/js/lib/seo-analyse.js +1 -0
- package/admin/js/lib/seo-redirects.js +1 -0
- package/admin/js/lib/seo-schema.js +2 -0
- package/admin/js/lib/seo-settings.js +2 -0
- package/admin/js/templates/page-editor.html +119 -80
- package/{plugins/seo/admin → admin/js}/templates/seo.html +1 -2
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/page-editor.js +62 -90
- package/admin/js/views/seo.js +144 -0
- package/package.json +2 -2
- package/plugins/free-tier.lock.json +0 -19
- package/server/routes/api/pages.js +10 -13
- package/server/routes/api/seo.js +201 -0
- package/server/server.js +12 -1
- package/server/services/menus.js +43 -0
- package/server/services/permissionRegistry.js +11 -0
- package/server/services/plugins.js +12 -2
- package/{plugins/seo/server → server/services/seo}/audit.js +2 -2
- package/server/services/seo/index.js +345 -0
- package/{plugins/seo/server → server/services/seo}/registry.js +5 -3
- package/server/services/seo/store.js +110 -0
- package/server/services/siteGitignore.js +4 -1
- package/server/services/tools.js +2 -1
- package/admin/js/templates/navigation.html +0 -226
- package/admin/js/views/navigation.js +0 -23
- package/plugins/seo/CLAUDE.md +0 -48
- package/plugins/seo/admin/lib/analyse.js +0 -286
- package/plugins/seo/admin/lib/redirects.js +0 -163
- package/plugins/seo/admin/lib/schema.js +0 -113
- package/plugins/seo/admin/lib/settings.js +0 -141
- package/plugins/seo/admin/views/seo.js +0 -912
- package/plugins/seo/config.js +0 -7
- package/plugins/seo/plugin.js +0 -477
- package/plugins/seo/plugin.json +0 -64
- package/plugins/seo/server/store.js +0 -76
- package/plugins/seo/tests/seo.test.js +0 -192
|
@@ -1,92 +1,64 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
[
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
### Custom Title with **bold** and [a link](/about)
|
|
42
|
-
[/header]
|
|
43
|
-
|
|
44
|
-
Body content here.
|
|
45
|
-
|
|
46
|
-
[footer]
|
|
47
|
-
[Read more](/about) | [icon name="arrow-right" /]
|
|
48
|
-
[/footer]
|
|
49
|
-
[/card]`,"Use [header] and [footer] sub-tags for Markdown-rendered header/footer regions. Sub-tags override the title/subtitle/icon and footer attributes when both are present.")),P.appendChild(q);const z=document.createElement("div");z.appendChild(m("Badge")),z.appendChild(f("Inline badge/label elements. Self-closing renders an empty coloured dot."));const Ee=document.createElement("table");Ee.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['variant="..."',"primary (default), secondary, success, danger, warning, info, light, dark"],["pill","Flag: rounded pill shape (.badge-pill)"],["outline","Flag: outlined style (.badge-outline)"],['size="small|large"',"Reduced or enlarged badge"]].forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),Ee.appendChild(o)}),z.appendChild(Ee),z.appendChild(l("Basic badge",'[badge variant="success"]New[/badge]',null)),z.appendChild(l("Pill outline badge",'[badge variant="danger" outline pill]Deprecated[/badge]',null)),P.appendChild(z);const j=document.createElement("div");j.appendChild(m("Button")),j.appendChild(f("Renders a styled anchor as a Domma button. Use the Insert \u2192 Button toolbar item for a guided dialog."));const ee=document.createElement("table");ee.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['href="..."',"Link destination (required)"],['label="..."',"Button text (self-closing form)"],['variant="..."',"primary (default), secondary, success, danger, warning, info, outline, ghost, link, outline-*"],['size="sm|lg"',"Button size modifier"],['icon="..."',"Domma icon name before the label"],['icon-after="..."',"Domma icon name after the label"],['target="..."','Link target, e.g. "_blank"'],['class="..."',"Extra CSS classes"],['id="..."',"Element id"]].forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),ee.appendChild(o)}),j.appendChild(ee),j.appendChild(l("Primary button",'[button href="/contact" variant="primary"]Get in touch[/button]',null)),j.appendChild(l("Self-closing with icon",'[button href="/download" variant="success" icon="download" size="sm" label="Download" /]',null)),j.appendChild(l("Opens in new tab",'[button href="https://example.com" variant="outline" target="_blank"]Visit site[/button]',null)),P.appendChild(j);const d=document.createElement("div");d.appendChild(m("Link (shortcode)")),d.appendChild(f("Renders a plain anchor with optional icon, target, and class. Use Insert \u2192 Link for a guided dialog. For simple inline links, standard Markdown [text](url) is fine."));const s=document.createElement("table");s.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['href="..."',"Link destination (required)"],['label="..."',"Link text (self-closing form)"],['target="..."','Link target, e.g. "_blank"'],['class="..."',"CSS classes on the <a>"],['icon="..."',"Domma icon name before the text"],['icon-after="..."',"Domma icon name after the text"],['id="..."',"Element id"]].forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),s.appendChild(o)}),d.appendChild(s),d.appendChild(l("External link in new tab",'[link href="https://example.com" target="_blank" icon-after="external-link"]Visit site[/link]',null)),d.appendChild(l("Self-closing with icon",'[link href="/about" icon="arrow-right" label="About us" /]',null)),P.appendChild(d);const K=document.createElement("div");K.appendChild(m("Spacer")),K.appendChild(f("Self-closing shortcode that inserts a fixed-height blank gap."));const ue=document.createElement("table");ue.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['size="N"',"Height in px (default: 8)"],['class="..."',"Extra CSS class on the spacer div"]].forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),ue.appendChild(o)}),K.appendChild(ue),K.appendChild(l("32px gap",'[spacer size="32" /]',null)),G.appendChild(K);const re=document.createElement("div");re.appendChild(m("Icon")),re.appendChild(f("Self-closing shortcode that renders any Domma icon inline."));const ge=document.createElement("table");ge.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['name="..."',"Domma icon name (required). Also accepted as src=."],['size="N"',"Width and height in px"],['color="..."',"CSS colour applied via style"],['class="..."',"Extra CSS classes"]].forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),ge.appendChild(o)}),re.appendChild(ge),re.appendChild(l("Coloured icon",'[icon name="star" size="24" color="#f5a623" /]',null)),P.appendChild(re);const be=document.createElement("div");be.appendChild(m("Center")),be.appendChild(f("Wraps content in a centred div. Works with any content including cards, grids, and text.")),be.appendChild(l("Example","[center]Centred content here[/center]",'Accepts an optional class="..." attribute for extra styling.')),G.appendChild(be);const le=document.createElement("div");le.appendChild(m("Hero")),le.appendChild(f("Full-width hero sections - no plugin required. Uses Domma's built-in Hero CSS component."));const pe=document.createElement("table");pe.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['title="..."',"Heading text"],['tagline="..."',"Subtitle text"],['size="sm|lg|full"',"Height variant (default: normal)"],['variant="dark|primary|gradient-blue|gradient-purple|gradient-sunset|gradient-ocean"',"Colour / gradient preset"],['image="url"',"Background image URL (adds cover mode)"],['overlay="light|dark|darker|gradient|gradient-reverse"',"Image overlay style"],['align="center|left"',"Content alignment (default: center)"],['fullwidth="true"',"Break out of the page container to span full viewport width"],['bg="..."',"Background colour CSS value"],["twinkle","Flag: adds particle overlay (requires Effects plugin)"],['twinkle-count="N"',"Number of particles"],['twinkle-colour="..."',"Particle colour CSS value"],["blobs","Flag: adds ambient blob background"],['blobs-type="..."',"Blob animation type (default: float-blobs)"],['class="..."',"Extra CSS classes"],['id="..."',"Element id"]].forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),pe.appendChild(o)}),le.appendChild(pe),le.appendChild(l("Basic hero",'[hero title="Welcome" tagline="Build something great"][/hero]',null)),le.appendChild(l("Gradient with body content",`[hero title="Get Started" tagline="Everything you need." size="lg" variant="gradient-blue" align="center"]
|
|
50
|
-
Some introductory **Markdown** content here.
|
|
51
|
-
[/hero]`,null)),le.appendChild(l("Background image with overlay",'[hero title="Our Story" image="/media/hero.jpg" overlay="dark" size="full"][/hero]',"Combine image + overlay for text legibility over photos.")),G.appendChild(le);const te=document.createElement("div");te.appendChild(m("Interactive Components")),te.appendChild(f("Tabs, accordion, carousel, and countdown - no plugin required.")),te.appendChild(l("Tabs",`[tabs]
|
|
52
|
-
[tab title="First"]Content **A**[/tab]
|
|
53
|
-
[tab title="Second"]Content **B**[/tab]
|
|
54
|
-
[/tabs]`,'Add style="pills" for pill-style navigation.')),te.appendChild(l("Accordion",`[accordion]
|
|
55
|
-
[item title="Question 1"]Answer here.[/item]
|
|
56
|
-
[item title="Question 2"]Answer here.[/item]
|
|
57
|
-
[/accordion]`,'Add multiple="true" to allow several panels open at once.')),te.appendChild(l("Accordion - collection-bound",'[collection slug="faq" display="accordion" title-field="title" body-field="description" /]',"Renders collection entries as accordion items. Use Insert \u2192 Accordion for a guided dialog.")),te.appendChild(l("Carousel",`[carousel autoplay="true" interval="5000"]
|
|
58
|
-
[slide title="Slide 1"]Description[/slide]
|
|
59
|
-
[slide image="/media/photo.jpg" title="Slide 2"]Caption[/slide]
|
|
60
|
-
[/carousel]`,'animation="slide|fade|crossfade" (default slide). Use Insert \u2192 Carousel for a guided dialog.')),te.appendChild(l("Carousel - fade / crossfade",`[carousel animation="crossfade" animation-duration="800" animation-easing="ease-in-out" autoplay="true"]
|
|
61
|
-
[slide image="/media/one.jpg" title="One"][/slide]
|
|
62
|
-
[slide image="/media/two.jpg" title="Two"][/slide]
|
|
63
|
-
[/carousel]`,"fade swaps opacity per slide; crossfade overlaps the two. animation-easing accepts any CSS timing function (ease, linear, cubic-bezier(...)).")),te.appendChild(l("Countdown (to date)",'[countdown to="2026-12-31" format="DD:HH:mm:ss" /]',null)),te.appendChild(l("Countdown (duration)",'[countdown duration="300" format="mm:ss" /]',"duration is in seconds. format options: mm:ss \xB7 HH:mm:ss \xB7 DD:HH:mm:ss")),P.appendChild(te);const he=document.createElement("div");he.appendChild(m("Timeline")),he.appendChild(f("Renders a Domma Progression component with event items."));const xe=document.createElement("table");xe.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['layout="vertical|centred|horizontal"',"[timeline] - layout orientation (default: vertical)"],['theme="minimal|corporate|modern"',"[timeline] - visual theme (default: minimal)"],['mode="timeline|roadmap"',"[timeline] - display mode (default: timeline)"],['class="..."',"[timeline] - extra CSS classes"],['id="..."',"[timeline] - element id"],['title="..."',"[event] - event heading"],['date="..."',"[event] - display date string"],['status="planned|in-progress|completed|blocked"',"[event] - progress status"],['icon="..."',"[event] - Domma icon name"]].forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),xe.appendChild(o)}),he.appendChild(xe),he.appendChild(l("Vertical timeline",`[timeline layout="vertical" theme="modern"]
|
|
64
|
-
[event title="Kickoff" date="Jan 2025" status="completed" icon="flag"]
|
|
65
|
-
Project launched.
|
|
66
|
-
[/event]
|
|
67
|
-
[event title="Beta" date="Jun 2025" status="in-progress" icon="rocket"]
|
|
68
|
-
In active development.
|
|
69
|
-
[/event]
|
|
70
|
-
[/timeline]`,null)),P.appendChild(he);const ie=document.createElement("div");ie.appendChild(m("Embedding a Form"));const J=document.createElement("table");J.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['name="..."',"Form slug (required). Also accepted as slug=."],['class="..."',"Extra CSS classes on the wrapper div"],['id="..."',"id attribute on the wrapper div"]].forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),J.appendChild(o)}),ie.appendChild(J),ie.appendChild(l("Basic",'[form name="contact" /]',"Forms are managed under Forms in the sidebar.")),ie.appendChild(l("With wrapper styling",'[form name="newsletter" class="mt-4" id="newsletter-form" /]',null)),Y.appendChild(ie);const Ne=document.createElement("div");Ne.appendChild(m("Displaying a Collection"));const fe=document.createElement("pre");fe.style.cssText=T+"display:block;padding:.5rem .75rem;white-space:pre;overflow-x:auto;",fe.textContent='[collection slug="enquiries" display="table" /]',Ne.appendChild(fe);const Oe=[["slug","Required. The collection slug."],["display","table (default), cards, or list."],["fields","Comma-separated field keys to show. Defaults to all."],["limit","Maximum number of entries to display."],["sort","Field key to sort by."],["order","asc or desc (default asc)."],["title-field","Field used as the card/list title (cards + list)."],["columns","Grid columns for cards display (2-4, default 3)."],["search","true/false - enable search in table mode (default true)."],["sortable","true/false - enable column sorting in table mode (default true)."],["exportable","true/false - show CSV export button in table mode (default false)."],["page-size","Rows per page in table mode (default 25)."],["empty","Text shown when there are no entries."],["cta","Action slug: enables per-entry CTA buttons."],["cta-label",'CTA button label (default: "Run").'],["cta-icon","CTA button icon name."],["cta-style","CTA button variant (default: primary)."],["cta-confirm","CTA confirmation prompt text."],["class","Extra CSS classes on the wrapper div."],["id","id attribute on the wrapper div."]],Me=document.createElement("table");Me.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",Oe.forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;width:35%;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),Me.appendChild(o)}),Ne.appendChild(Me),Ne.appendChild(f("Table display uses Domma Table - search, sort, pagination, column selector, and CSV export are all built in. Cards and lists are static.")),Y.appendChild(Ne);const _=document.createElement("div");_.appendChild(m("View - Pro")),_.appendChild(f("Renders a Collection with full filtering, pagination, and display options. Requires a Pro MongoDB collection."));const ke=document.createElement("table");ke.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[["slug","Required. The collection slug."],["display","table (default), cards, or list."],["limit","Maximum number of entries to display."],["fields","Comma-separated field keys to show."],["title-field","Field used as the card/list title."],["columns","Grid columns for cards display (2-4, default 3)."],["empty","Text shown when there are no entries."],["class","Extra CSS classes on the wrapper div."],["id","id attribute on the wrapper div."]].forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;width:35%;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),ke.appendChild(o)}),_.appendChild(ke),_.appendChild(l("Table display",'[view slug="products" display="table" limit="50" /]',null)),_.appendChild(l("Cards display",'[view slug="team" display="cards" columns="3" title-field="name" /]',null)),Y.appendChild(_);const Se=document.createElement("div");Se.appendChild(m("CTA - Pro")),Se.appendChild(f("Renders an action button that triggers a Pro Action against an entry. Requires the Pro Actions feature."));const Te=document.createElement("table");Te.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[["action","Required. The action slug."],["entry","Required. The entry id to act on."],["style","Button variant (default: primary)."],["icon","Domma icon name for the button."],["size","Button size (sm, lg)."],["confirm","Confirmation prompt text before running."],["label","Self-closing only: button label text."]].forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;width:35%;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),Te.appendChild(o)}),Se.appendChild(Te),Se.appendChild(l("Wrapping form",'[cta action="approve" entry="abc123" style="success" confirm="Approve this entry?"]Approve[/cta]',null)),Se.appendChild(l("Self-closing",'[cta action="archive" entry="abc123" icon="archive" label="Archive" /]',null)),Y.appendChild(Se);const ne=document.createElement("div");ne.appendChild(m("Effects")),ne.appendChild(f("Shortcodes for scroll-triggered animations, counters, typewriter text, and more. Requires the Domma Effects plugin."));const Ve=[['[reveal animation="fade"]...[/reveal]',"Fade/slide/zoom on scroll"],["[breathe]...[/breathe]","Continuous gentle scale loop"],["[pulse]...[/pulse]","Repeating scale pulse"],["[shake]...[/shake]","One-shot shake"],['[scribe speed="50"]...[/scribe]',"Typewriter - simple mode (text typed letter by letter)"],['[scribe loop="true"][render]Text[/render][wait]1500[/wait][undo /][/scribe]',"Typewriter - script mode (sequenced render/wait/undo actions)"],["[scramble]...[/scramble]","Character-scramble reveal"],['[counter to="100" /]',"Animated number counter (self-closing)"],["[ripple]...[/ripple]","Click-triggered ripple"],['[twinkle count="50"]...[/twinkle]',"Particle/star overlay"],['[animate type="fade-in-up"]...[/animate]',"CSS-only animation (no plugin needed)"],['[ambient type="aurora"]...[/ambient]',"CSS-only animated background"],['[firework type="burst" colour="rainbow" /]',"CSS firework (self-closing)"],["[fireworks]...[/fireworks]","Fireworks display container"],['[celebrate theme="auto" /]',"Seasonal canvas celebration (auto-detects date)"],["[christmas /]","Christmas - snowflakes, trees, snowmen, sleigh"],["[halloween /]","Halloween - pumpkins, ghosts, bats, spiders"],["[st-patricks /]","St Patrick's Day - shamrocks, coins, rainbows, leprechaun"],["[valentines /]","Valentine's Day - hearts, roses"],["[guy-fawkes /]","Guy Fawkes - fireworks, sparkles"],["[st-andrews /]","St Andrew's Day - heather, thistles"],["[st-davids /]","St David's Day - daffodils, dragons"],["[st-georges /]","St George's Day - roses, flags"]],ve=document.createElement("table");ve.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",Ve.forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),ve.appendChild(o)}),ne.appendChild(ve),ne.appendChild(l("Scribe script - looping multi-phrase typewriter",`[scribe loop="true" loop-delay="2000" delete-speed="20"]
|
|
71
|
-
[render]We build things that matter.[/render]
|
|
72
|
-
[wait]1200[/wait]
|
|
73
|
-
[undo all="true" /]
|
|
74
|
-
[render]We design for humans.[/render]
|
|
75
|
-
[wait]1200[/wait]
|
|
76
|
-
[undo all="true" /]
|
|
77
|
-
[/scribe]`,'Use [render]...[/render] to type text, [wait]Ms[/wait] to pause, and [undo /] to delete. loop="true" replays the sequence. Only [render], [wait], and [undo] shortcodes are parsed - plain text between actions is ignored.')),ne.appendChild(l("Christmas",'[christmas intensity="medium" snowmen="off" trees="off" wreaths="off" northStars="off" sleigh="off" train="off" elf="off" robin="off" firework="off" /]',"intensity: light | medium | heavy. Count-based (off disables entirely): snowmen, trees, wreaths, northStars. Probability-based specials (off prevents spawning): sleigh, train, elf, robin, firework. Snowflakes always fall.")),ne.appendChild(l("Halloween",'[halloween intensity="medium" gravestones="off" jackOLanterns="off" spiders="off" scarecrows="off" twinklingStars="off" /]',"intensity: light | medium | heavy. Decoration toggles (off disables): gravestones, jackOLanterns, spiders, scarecrows, twinklingStars. Particles: bats, ghosts, pumpkins (always on). Fixed decorations: haunted-house, cauldron (always present).")),ne.appendChild(l("St Patrick's Day",'[st-patricks intensity="medium" pots="off" twinklingStars="off" /]',"intensity: light | medium | heavy. Decoration toggles (off disables): pots (pots of gold), twinklingStars. Particles: shamrocks, clover petals, gold coins, sparkles (always on). Fixed decorations: rainbow, harp, moon. Leprechaun appears randomly - more likely at higher intensity.")),ne.appendChild(l("Valentine's Day",'[valentines intensity="medium" garlands="off" butterflies="off" /]',"intensity: light | medium | heavy. Decoration toggles (off disables): garlands (heart garlands), butterflies. Particles: hearts, rose petals, sparkles, lips (always on). Fixed decorations: envelopes (always present). Cupid appears randomly - more likely at higher intensity.")),ne.appendChild(l("Guy Fawkes Night",'[guy-fawkes intensity="medium" bonfires="off" /]',"intensity: light | medium | heavy. Decoration toggles (off disables): bonfires. Particles: embers, fireworks, sparks, rockets, bursts, trails (always on). Fixed decorations: guy-effigy, catherine-wheel, roman-candle, sparkler-bundle, moon. Starts at 25% particle density and builds up gradually.")),ne.appendChild(l("St Andrew's Day",'[st-andrews intensity="medium" thistles="off" twinklingStars="off" /]',"intensity: light | medium | heavy. Decoration toggles (off disables): thistles, twinklingStars. Particles: heather petals, saltire sparkles (always on). Fixed decorations: bagpiper, saltire-flag, tartan-pattern, highland-scene (always present). Bagpiper probability increases with intensity.")),ne.appendChild(l("St David's Day",'[st-davids intensity="medium" daffodilFields="off" twinklingStars="off" /]',"intensity: light | medium | heavy. Decoration toggles (off disables): daffodilFields, twinklingStars. Particles: daffodil petals, spring sparkles (always on). Fixed decorations: welsh-dragon, harp, leek-bundle, flag (always present). Flying dragon appears randomly - more likely at higher intensity.")),ne.appendChild(l("St George's Day",'[st-georges intensity="medium" roses="off" twinklingStars="off" /]',"intensity: light | medium | heavy. Decoration toggles (off disables): roses (English roses), twinklingStars. Particles: rose petals, Tudor roses, oak leaves, sparkles (always on). Fixed decorations: st-georges-cross, dragon, shield, castle (always present). Knight appears randomly - more likely at higher intensity.")),ne.appendChild(f("Enable the Domma Effects plugin to activate these shortcodes on your site.")),oe.appendChild(ne);const X=document.createElement("div");X.appendChild(m("Tables")),X.appendChild(f("Wrap a standard Markdown (GFM) table with Domma CSS classes and responsive horizontal scrolling."));const Be=document.createElement("table");Be.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['striped="true"',"Alternate row background (.table-striped)"],['bordered="true"',"Borders on all cells (.table-bordered)"],['compact="true"',"Reduced cell padding (.table-compact)"],['caption="..."',"Caption text above the table"],['class="..."',"Extra CSS classes appended to .table"],['id="..."',"id attribute on the <table> element"]].forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),Be.appendChild(o)}),X.appendChild(Be),X.appendChild(l("Striped + bordered table",`[table striped="true" bordered="true" caption="Product Pricing"]
|
|
78
|
-
| Product | Price |
|
|
79
|
-
| ------- | ----: |
|
|
80
|
-
| Widget | $9.99 |
|
|
81
|
-
| Gadget | $14.99 |
|
|
82
|
-
[/table]`,"The inner content must be a valid GFM Markdown table. The shortcode adds Domma CSS classes and wraps in a responsive scroll container.")),P.appendChild(X);const de=document.createElement("div");de.appendChild(m("Slideover")),de.appendChild(f("A trigger button that opens a slide-in panel with Markdown content. No plugin required."));const Ie=document.createElement("table");Ie.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['title="..."',"Panel header text"],['trigger="..."','Button label (default: "Open")'],['size="sm|md|lg"',"Panel width (default: md)"],['position="right|left"',"Slide direction (default: right)"]].forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),Ie.appendChild(o)}),de.appendChild(Ie),de.appendChild(l("Example",`[slideover title="More Info" trigger="Read more" size="md"]
|
|
83
|
-
## Details
|
|
84
|
-
Markdown content here.
|
|
85
|
-
[/slideover]`,"Nested [card] and [grid] shortcodes work inside the slideover body.")),P.appendChild(de);const ye=document.createElement("div");ye.appendChild(m("DConfig - Declarative Behaviour")),ye.appendChild(f("Define click handlers and class toggles without writing JavaScript. Use the DConfig section above or embed inline with [dconfig]...[/dconfig] in the content body. Inline shortcodes win on selector conflict.")),ye.appendChild(l("Toggle a class on click",'{ "#my-btn": { "events": { "click": { "target": "#panel", "toggleClass": "hidden" } } } }','Selector keys use standard CSS selectors. "target" is optional - defaults to the element itself.')),ye.appendChild(l("Inline shortcode syntax",`[dconfig]
|
|
86
|
-
{ "#my-btn": { "events": { "click": { "target": "#panel", "toggleClass": "hidden" } } } }
|
|
87
|
-
[/dconfig]`,null)),oe.appendChild(ye);const Pe=document.createElement("div");Pe.appendChild(m("CSS Editor")),Pe.appendChild(f("The toolbar's </> button (left of the Split View icon) swaps the left pane between Markdown and Custom CSS. Edits are saved together with the page - one Save, one toast."));const Fe=document.createElement("table");Fe.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[["</> button","Toggle between Markdown editor and Custom CSS editor"],["Split / Write / Preview","Standard view mode buttons to the right of </>"],["Save","Saves both page content and CSS in a single operation"]].forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),Fe.appendChild(o)}),Pe.appendChild(Fe),oe.appendChild(Pe);const Ce=document.createElement("div");Ce.appendChild(m("Colour Picker")),Ce.appendChild(f("Insert \u2192 Colour Picker opens a centred colour picker. Pick a colour, then use Copy to copy the hex code to the clipboard or Insert to drop it at the cursor. Click outside the panel to dismiss.")),oe.appendChild(Ce);const ze=document.createElement("div");ze.appendChild(m("Keyboard Shortcuts"));const $e=document.createElement("table");$e.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[["Ctrl + B","Bold selected text"],["Ctrl + I","Italic selected text"],["Ctrl + K","Insert link dialog"],["Tab","Indent 2 spaces at cursor"],["Shift + Tab","Dedent 2 spaces on current line"],["Ctrl + X","Cut current line (no selection) or cut selection"],["Ctrl + C","Copy current line (no selection) or copy selection"],["Ctrl + V","Paste from clipboard"],["Ctrl + Z","Undo"],["Ctrl + Y","Redo"]].forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;white-space:nowrap;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),$e.appendChild(o)}),ze.appendChild($e),x.appendChild(ze);const De=document.createElement("div");De.appendChild(m("Tips"));const He=document.createElement("table");He.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['class="text-center"',"Centre text and inline elements on any shortcode"],['class="mx-auto"',"Centre a block element horizontally"],["[center]...[/center]","Shorthand wrapper for centred content"]].forEach(([c,r])=>{const o=document.createElement("tr"),i=document.createElement("td");i.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;",i.appendChild(p(c));const n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border);vertical-align:top;color:var(--dm-text-muted);",n.textContent=r,o.appendChild(i),o.appendChild(n),He.appendChild(o)}),De.appendChild(He),De.appendChild(f("Most shortcodes accept a class attribute for custom styling.")),x.appendChild(De),e.setContent(U),e.open(),E.tabs(V),I.scan(U)}function Ct(e,T){const A=e.find("#history-tab").get(0),p=e.find("#version-list").get(0),m=e.find("#version-compare").get(0),U=e.find("#version-compare-old").get(0),V=e.find("#version-compare-new").get(0),F=e.find("#version-compare-label").get(0),u=e.find("#version-compare-back-btn").get(0),v=e.find("#version-restore-btn").get(0),x=e.find("#history-save-named-btn").get(0),G=e.find("#history-delete-selected-btn").get(0);let P=!1,Y=null,oe=null;function h(){p.style.display="",m.style.display="none",Y=null}function ae(){p.style.display="none",m.style.display=""}async function L(){p.textContent="";const k=document.createElement("p");k.style.color="var(--dm-text-muted)",k.textContent="Loading\u2026",p.appendChild(k);let H;try{H=(await N.versions.list(T)).versions||[]}catch(d){p.textContent="";const s=document.createElement("p");s.style.color="var(--dm-danger)",s.textContent="Failed to load versions: "+d.message,p.appendChild(s);return}if(p.textContent="",G.style.display="none",!H.length){const d=document.createElement("p");d.style.color="var(--dm-text-muted)",d.textContent="No versions saved yet. Versions are created automatically on each save.",p.appendChild(d);return}const B=document.createElement("table");B.style.cssText="width:100%;border-collapse:collapse;font-size:.875rem;";const f=document.createElement("thead"),l=document.createElement("tr"),W=document.createElement("th");W.style.cssText="padding:.5rem .75rem;width:2rem;border-bottom:1px solid var(--dm-border);";const q=document.createElement("input");q.type="checkbox",q.setAttribute("aria-label","Select all versions"),W.appendChild(q),l.appendChild(W),["Date","Author","Type","Label","Actions"].forEach(d=>{const s=document.createElement("th");s.style.cssText="text-align:left;padding:.5rem .75rem;border-bottom:1px solid var(--dm-border);font-size:.8rem;color:var(--dm-text-muted);",s.textContent=d,l.appendChild(s)}),f.appendChild(l),B.appendChild(f);const z=[];function Ee(){const d=z.some(K=>K.checked),s=z.length>0&&z.every(K=>K.checked);G.style.display=d?"":"none",q.checked=s,q.indeterminate=d&&!s}q.addEventListener("change",()=>{z.forEach(d=>{d.checked=q.checked}),Ee()});const j=document.createElement("tbody"),ee=new Map;H.forEach(d=>{const s=document.createElement("tr");s.style.borderBottom="1px solid var(--dm-border)",s.dataset.file=d.filename,s.tabIndex=0,s.className="pe-version-row";const K=document.createElement("td");K.style.padding=".5rem .75rem";const ue=document.createElement("input");ue.type="checkbox",ue.value=d.filename,ue.addEventListener("change",Ee),K.appendChild(ue),z.push(ue);const re=document.createElement("td");re.style.padding=".5rem .75rem",re.textContent=D(d.createdAt).format("D MMM YYYY, HH:mm");const ge=document.createElement("td");ge.style.padding=".5rem .75rem",ge.textContent=d.author||"-";const be=document.createElement("td");be.style.padding=".5rem .75rem";const le=document.createElement("span");le.className="badge badge-"+(d.type==="manual"?"primary":"secondary"),le.style.cssText="font-size:.75rem;padding:.2rem .5rem;border-radius:4px;",le.textContent=d.type,be.appendChild(le);const pe=document.createElement("td");pe.style.cssText="padding:.5rem .75rem;color:var(--dm-text-muted);",pe.textContent=d.label||"-";const te=document.createElement("td");te.style.padding=".5rem .75rem";const he=async()=>{try{const J=await N.versions.get(T,d.filename);U.textContent=J.content,V.textContent=e.find("#markdown-editor").get(0).value,F.textContent=D(d.createdAt).format("D MMM YYYY, HH:mm")+(d.label?` - ${d.label}`:""),Y=d.filename,ae()}catch(J){E.toast("Failed to load version: "+J.message,{type:"error"})}},xe=async()=>{if(await E.confirm("Delete this version? This cannot be undone."))try{await N.versions.delete(T,d.filename),E.toast("Version deleted.",{type:"success"}),await L()}catch(J){E.toast("Failed to delete: "+J.message,{type:"error"})}};ee.set(d.filename,{version:d,compare:he,remove:xe,restore:()=>{Y=d.filename,v.click()}});const ie=document.createElement("button");ie.type="button",ie.className="pe-kebab",ie.title="Actions",ie.setAttribute("aria-label","Actions for this version"),ie.textContent="\u22EE",te.appendChild(ie),s.appendChild(K),s.appendChild(re),s.appendChild(ge),s.appendChild(be),s.appendChild(pe),s.appendChild(te),j.appendChild(s)}),B.appendChild(j),p.appendChild(B),I.scan(p);try{oe?.destroy?.()}catch{}oe=E.contextMenu(j,{cursor:"glyph",match:"tr[data-file]",inherit:!1,density:"compact",items:d=>{const s=ee.get(d.dataset.file);return s?[{header:D(s.version.createdAt).format("D MMM YYYY, HH:mm")+(s.version.label?` - ${s.version.label}`:"")},{label:"Compare with the editor",icon:"columns",action:()=>s.compare()},{label:"Restore this version\u2026",icon:"rotate-ccw",action:()=>s.restore()},{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:()=>s.remove()}]:[]}}),j.addEventListener("click",d=>{const s=d.target.closest("tr[data-file]");if(!s||d.target.matches("input"))return;const K=d.target.closest(".pe-kebab");if(K){Ke(K);return}ee.get(s.dataset.file)?.compare()}),j.addEventListener("keydown",d=>{const s=d.target.closest("tr[data-file]");s&&(d.key==="F10"&&d.shiftKey?(d.preventDefault(),Ke(s)):d.key==="Enter"&&d.target===s&&ee.get(s.dataset.file)?.compare())})}A.addEventListener("click",async()=>{P||(P=!0,await L())}),u.addEventListener("click",()=>{h()}),v.addEventListener("click",async()=>{if(!(!Y||!await E.confirm("Restore this version? The current page content will be overwritten (a pre-restore snapshot will be saved automatically).")))try{await N.versions.restore(T,Y),E.toast("Page restored. Reloading editor\u2026",{type:"success"}),R.navigate("/pages"),setTimeout(()=>R.navigate(`/pages/edit${T}`),300)}catch(k){E.toast("Restore failed: "+k.message,{type:"error"})}}),G.addEventListener("click",async()=>{const k=[...p.querySelectorAll("tbody input[type=checkbox]:checked")].map(H=>H.value);if(!(!k.length||!await E.confirm(`Delete ${k.length} version${k.length>1?"s":""}? This cannot be undone.`)))try{await N.versions.bulkDelete(T,k),E.toast(`${k.length} version${k.length>1?"s":""} deleted.`,{type:"success"}),await L()}catch(H){E.toast("Delete failed: "+H.message,{type:"error"})}}),x.addEventListener("click",()=>{const k=E.slideover({title:"Save Named Version",size:"sm",position:"right"}),H=document.createElement("div");H.style.padding="1rem";const B=document.createElement("label");B.className="form-label",B.textContent="Version Label";const f=document.createElement("input");f.type="text",f.className="form-input",f.placeholder="e.g. Initial design, Before restructure",f.style.marginBottom=".75rem";const l=document.createElement("button");l.className="btn btn-primary",l.textContent="Save Version",l.addEventListener("click",async()=>{const W=f.value.trim();try{await N.versions.create(T,W||null),E.toast("Named version saved.",{type:"success"}),k.close(),P=!1,A.classList.contains("active")&&(P=!0,await L())}catch(q){E.toast("Failed to save version: "+q.message,{type:"error"})}}),H.appendChild(B),H.appendChild(f),H.appendChild(l),k.element.appendChild(H),k.open(),setTimeout(()=>f.focus(),100)})}function wt(e){try{return D(e).format("D MMM YYYY, HH:mm")}catch{return new Date(e).toLocaleString()}}async function Et(e){const T=E.slideover({title:"Share preview link",size:"md",position:"right"}),A=document.createElement("div");A.style.padding="1rem";const p=document.createElement("p");p.className="form-hint",p.style.marginTop="0",p.textContent="Anyone with the link can view this page even while it is unpublished, without signing in. The link works for this page only, expires on its own, and can be revoked at any time.",A.appendChild(p);const m=document.createElement("label");m.className="form-label",m.textContent="Expires after";const U=document.createElement("select");U.className="form-select",[["1h","1 hour"],["24h","24 hours"],["7d","7 days"],["30d","30 days"]].forEach(([h,ae])=>{const L=document.createElement("option");L.value=h,L.textContent=ae,h==="7d"&&(L.selected=!0),U.appendChild(L)});const V=document.createElement("label");V.className="form-label",V.style.marginTop=".75rem",V.textContent="Note (optional)";const F=document.createElement("input");F.type="text",F.className="form-input",F.placeholder="e.g. For the client sign-off";const u=document.createElement("button");u.className="btn btn-primary",u.style.marginTop=".75rem",u.textContent="Create link";const v=document.createElement("div");v.style.marginTop=".75rem",v.style.display="none";const x=document.createElement("input");x.type="text",x.className="form-input",x.readOnly=!0,x.style.marginBottom=".4rem";const G=document.createElement("button");G.className="btn btn-secondary btn-sm",G.textContent="Copy link",G.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(x.value),E.toast("Link copied to clipboard.",{type:"success"})}catch{x.select(),E.toast("Press Ctrl+C to copy the selected link.",{type:"info"})}}),v.appendChild(x),v.appendChild(G),A.appendChild(m),A.appendChild(U),A.appendChild(V),A.appendChild(F),A.appendChild(u),A.appendChild(v);const P=document.createElement("h4");P.style.cssText="margin:1.5rem 0 .5rem;font-size:.9rem;",P.textContent="Active links",A.appendChild(P);const Y=document.createElement("div");A.appendChild(Y);const oe=async()=>{Y.textContent="";let h=[];try{h=await N.pages.previewLinks.list(e)}catch(L){const k=document.createElement("p");k.className="form-hint",k.textContent="Could not load existing links: "+(L.message||"unknown error"),Y.appendChild(k);return}const ae=h.filter(L=>!L.revokedAt&&!L.expired);if(!ae.length){const L=document.createElement("p");L.className="form-hint",L.textContent="No active links for this page.",Y.appendChild(L);return}ae.forEach(L=>{const k=document.createElement("div");k.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.5rem 0;border-bottom:1px solid var(--dm-border-light);";const H=document.createElement("div");H.style.cssText="flex:1 1 auto;min-width:0;font-size:.82rem;";const B=document.createElement("div");B.style.fontWeight="600",B.textContent=L.label||"Untitled link";const f=document.createElement("div");f.style.opacity=".7";const l=L.useCount===1?"1 view":`${L.useCount||0} views`;f.textContent=`Expires ${wt(L.expiresAt)} - ${l}`,H.appendChild(B),H.appendChild(f);const W=document.createElement("button");W.className="btn btn-ghost btn-sm",W.textContent="Revoke",W.addEventListener("click",async()=>{if(await E.confirm("Revoke this link? Anyone holding it loses access immediately."))try{await N.pages.previewLinks.revoke(L.id),E.toast("Link revoked.",{type:"success"}),await oe()}catch(z){E.toast("Could not revoke: "+(z.message||"unknown error"),{type:"error"})}}),k.appendChild(H),k.appendChild(W),Y.appendChild(k)})};u.addEventListener("click",async()=>{u.disabled=!0,u.textContent="Creating\u2026";try{const h=await N.pages.previewLinks.create(e,U.value,F.value.trim());x.value=h.url,v.style.display="",F.value="",E.toast("Share link created.",{type:"success"}),await oe(),x.select()}catch(h){E.toast("Could not create link: "+(h.message||"unknown error"),{type:"error"})}finally{u.disabled=!1,u.textContent="Create link"}}),T.element.appendChild(A),T.open(),await oe()}let ce=null,Ze=!1;function xt(){Ze||!window.R?.use||(Ze=!0,R.use((e,T,A)=>{const p=T?.path||"",m=e?.path||"";return!(ht.test(p)&&m!==p)||!ce||ce.path!==p||!ce.dirty()?A():E.confirm("You have unsaved changes to this page. Leave anyway? They are kept as a draft for your next visit.").then(V=>{V?(ce?.keepDraft(),A()):history.replaceState(null,"","#"+p)})}),M.subscribe("router:afterChange",({to:e}={})=>{if(ce&&(e?.path||"")!==ce.path){const T=ce;ce=null,T.dispose()}}))}function kt(e){return e.split(`
|
|
88
|
-
`).map(T=>T.trimEnd()).join(`
|
|
1
|
+
import{api as f}from"../api.js";import{CardBuilder as et}from"../lib/card-builder.js";import{TimelineBuilder as tt}from"../lib/timeline-builder.js";import{attachEditorKeybindings as at,createToolbar as st,insertAtCursor as ot}from"../lib/markdown-toolbar.js";import{EffectsBuilder as nt}from"../lib/effects-builder.js";import{attachContextMenu as rt}from"../lib/shortcode-context-menu.js";import{registerCustomInserters as it}from"../lib/shortcode-insert.js";import{populateThemeSelect as lt}from"../lib/themes.js";import{createFoldingManager as ct}from"../lib/editor-folding.js";import{openScribeComposer as dt}from"../lib/scribe-composer.js";import{openCrudTutorial as pt}from"../lib/crud-tutorial.js";import{addBannerAction as z}from"../lib/plugin-chrome.js";import{helpPopovers as ut}from"../lib/help-popover.js";import{onceClosed as pe,openMenuAt as Re}from"../lib/tool-kit.js";import{openEditorReference as Oe}from"../lib/page-editor-reference.js";import{getProjectFromHash as vt}from"../lib/project-context.js";import{SCREEN as mt,bannerSection as ft,draftIsStale as bt,draftKey as ht,initialProject as gt,makeDraft as yt,problems as Be,readDraft as wt,snapshot as N}from"../lib/page-editor-arrange.js";function U(s){const r=document.createElement("template");return r.innerHTML=s.trim(),r.content.firstElementChild}function kt(s){try{return D(s).format("D MMM YYYY, HH:mm")}catch{return new Date(s).toLocaleString()}}const $e=s=>D(s.createdAt).format("D MMM YYYY, HH:mm")+(s.label?` - ${s.label}`:""),Mt=`
|
|
2
|
+
<div class="pe-share">
|
|
3
|
+
<p class="form-hint" style="margin-top:0">Anyone with the link can view this page even while it is unpublished, without
|
|
4
|
+
signing in. The link works for this page only, expires on its own, and can be revoked at any time.</p>
|
|
5
|
+
<label class="form-label" for="pe-share-expiry">Expires after</label>
|
|
6
|
+
<select id="pe-share-expiry" class="form-select" data-model="expiry.value">
|
|
7
|
+
<option value="1h">1 hour</option><option value="24h">24 hours</option>
|
|
8
|
+
<option value="7d">7 days</option><option value="30d">30 days</option>
|
|
9
|
+
</select>
|
|
10
|
+
<label class="form-label" for="pe-share-note" style="margin-top:.75rem">Note (optional)</label>
|
|
11
|
+
<input id="pe-share-note" type="text" class="form-input" placeholder="e.g. For the client sign-off" data-model="note.value">
|
|
12
|
+
<button type="button" class="btn btn-primary" style="margin-top:.75rem" data-on-click="create" data-bind-disabled="creating.value"
|
|
13
|
+
data-bind-text="creating.value ? 'Creating\u2026' : 'Create link'">Create link</button>
|
|
14
|
+
<div style="margin-top:.75rem" hidden data-bind-hidden="!url.value">
|
|
15
|
+
<input type="text" class="form-input pe-share-url" readonly style="margin-bottom:.4rem" data-bind-value="url.value">
|
|
16
|
+
<button type="button" class="btn btn-secondary btn-sm" data-on-click="copy">Copy link</button>
|
|
17
|
+
</div>
|
|
18
|
+
<h4 style="margin:1.5rem 0 .5rem;font-size:.9rem;">Active links</h4>
|
|
19
|
+
<p class="form-hint" hidden data-bind-hidden="!listError.value" data-bind-text="listError.value"></p>
|
|
20
|
+
<p class="form-hint" hidden data-bind-hidden="listError.value || !loaded.value || links.value.length">No active links for this page.</p>
|
|
21
|
+
<div data-each="links.value key=id">
|
|
22
|
+
<div class="pe-share-row">
|
|
23
|
+
<div class="pe-share-text"><div style="font-weight:600" data-bind-text="title"></div><div style="opacity:.7" data-bind-text="meta"></div></div>
|
|
24
|
+
<button type="button" class="btn btn-ghost btn-sm" data-on-click="$root.revoke(id)">Revoke</button>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
<style>
|
|
29
|
+
.pe-share { padding: 1rem; }
|
|
30
|
+
.pe-share [hidden] { display: none !important; }
|
|
31
|
+
.pe-share-row { display: flex; align-items: center; gap: .6rem; padding: .5rem 0; border-bottom: 1px solid var(--dm-border-light); }
|
|
32
|
+
.pe-share-text { flex: 1 1 auto; min-width: 0; font-size: .82rem; }
|
|
33
|
+
</style>`;async function Et(s){const r=E.slideover({title:"Share preview link",size:"md",position:"right"}),c=U(`<div>${Mt}</div>`),o={expiry:M.observable("7d"),note:M.observable(""),creating:M.observable(!1),url:M.observable(""),links:M.observable([]),loaded:M.observable(!1),listError:M.observable("")},v=async()=>{try{const u=await f.pages.previewLinks.list(s);o.links.value=u.filter(i=>!i.revokedAt&&!i.expired).map(i=>({id:i.id,title:i.label||"Untitled link",meta:`Expires ${kt(i.expiresAt)} - ${i.useCount===1?"1 view":`${i.useCount||0} views`}`})),o.listError.value=""}catch(u){o.listError.value="Could not load existing links: "+(u.message||"unknown error")}finally{o.loaded.value=!0}};o.create=async()=>{o.creating.value=!0;try{const u=await f.pages.previewLinks.create(s,o.expiry.peek(),o.note.peek().trim());o.url.value=u.url,o.note.value="",E.toast("Share link created.",{type:"success"}),await v(),c.querySelector(".pe-share-url")?.select()}catch(u){E.toast("Could not create link: "+(u.message||"unknown error"),{type:"error"})}finally{o.creating.value=!1}},o.copy=async()=>{try{await navigator.clipboard.writeText(o.url.peek()),E.toast("Link copied to clipboard.",{type:"success"})}catch{c.querySelector(".pe-share-url")?.select(),E.toast("Press Ctrl+C to copy the selected link.",{type:"info"})}},o.revoke=async u=>{if(await E.confirm("Revoke this link? Anyone holding it loses access immediately."))try{await f.pages.previewLinks.revoke(u),E.toast("Link revoked.",{type:"success"}),await v()}catch(i){E.toast("Could not revoke: "+(i.message||"unknown error"),{type:"error"})}};const d=M.applyBindings(o,c);pe(r,()=>d.dispose()),r.element.appendChild(c),r.open(),await v()}function St(s){return new Promise(r=>{const c=E.slideover({title:"Save Named Version",size:"sm",position:"right"}),o=U(`
|
|
34
|
+
<div style="padding:1rem">
|
|
35
|
+
<label class="form-label" for="pe-version-label">Version Label</label>
|
|
36
|
+
<input id="pe-version-label" type="text" class="form-input" style="margin-bottom:.75rem"
|
|
37
|
+
placeholder="e.g. Initial design, Before restructure" data-model="label.value" data-on-keydown="key">
|
|
38
|
+
<button type="button" class="btn btn-primary" data-on-click="save" data-bind-disabled="saving.value">Save Version</button>
|
|
39
|
+
</div>`);let v=!1;const d={label:M.observable(""),saving:M.observable(!1)};d.save=async()=>{d.saving.value=!0;try{await f.versions.create(s,d.label.peek().trim()||null),E.toast("Named version saved.",{type:"success"}),v=!0,c.close()}catch(i){E.toast("Failed to save version: "+i.message,{type:"error"})}finally{d.saving.value=!1}},d.key=i=>{i.key==="Enter"&&(i.preventDefault(),d.save())};const u=M.applyBindings(d,o);pe(c,()=>{u.dispose(),r(v)}),c.element.appendChild(o),c.open(),setTimeout(()=>o.querySelector("input")?.focus(),100)})}let k=null,Fe=!1;function Ct(){Fe||!window.R?.use||(Fe=!0,R.use((s,r,c)=>{const o=r?.path||"",v=s?.path||"";return!(mt.test(o)&&v!==o)||!k||k.path!==o||!k.dirty()?c():E.confirm("You have unsaved changes to this page. Leave anyway? They are kept as a draft for your next visit.").then(u=>{u?(k?.keepDraft(),c()):history.replaceState(null,"","#"+o)})}),M.subscribe("router:afterChange",({to:s}={})=>{if(k&&(s?.path||"")!==k.path){const r=k;k=null,r.dispose()}}))}function xt(s){return s.split(`
|
|
40
|
+
`).map(r=>r.trimEnd()).join(`
|
|
89
41
|
`).replace(/\n{3,}/g,`
|
|
90
42
|
|
|
91
|
-
`)}const
|
|
92
|
-
`);for(;d.firstChild;)d.removeChild(d.firstChild);a.forEach((g,b)=>{const y=document.createElement("span");y.className="editor-line-number-row";const C=document.createElement("span");C.className="fold-toggle fold-toggle--empty";const w=document.createElement("span");w.className="editor-line-num",w.textContent=String(b+1),y.appendChild(C),y.appendChild(w),d.appendChild(y)}),d.scrollTop=t.scrollTop}{const t=e.find("#css-editor").get(0);t&&t.addEventListener("scroll",()=>{d.scrollTop=t.scrollTop})}const ue=()=>{s.refresh(),d.scrollTop=ee.scrollTop};ee.addEventListener("input",ue),ee.addEventListener("scroll",()=>{d.scrollTop=ee.scrollTop});let re=null;const ge=()=>q.get(0),be=t=>t.replace(/<\/head>/i,"<style>body.dm-cloaked{opacity:1 !important;transition:none !important}</style></head>"),le=()=>{const t=ge(),a=t&&t.contentDocument;if(!a||!a.head)return;const g=z&&e.find("#css-editor").val()||"";if(!g)return;let b=a.getElementById("preview-custom-css");b||(b=a.createElement("style"),b.id="preview-custom-css",a.head.appendChild(b)),b.textContent=g},pe=()=>{clearTimeout(re),re=setTimeout(async()=>{const t=ge();if(!t||e.find("#editor-body").hasClass("editor-mode-write"))return;const a=s.getUnfoldedContent();try{const g=(e.find("#field-dconfig").val()||"").trim();let b=null;if(g)try{b=JSON.parse(g)}catch{b=null}const y=(e.find("#page-url-path").val()||"").trim()||v||"/",{html:C}=await N.pages.previewFull(y,Ve(b),a),w=be(C);let Q=0;try{Q=t.contentWindow.scrollY||0}catch{}t.addEventListener("load",function me(){t.removeEventListener("load",me);try{t.contentWindow.scrollTo(0,Q)}catch{}le()},{once:!0}),t.srcdoc=w}catch(g){t.srcdoc='<!doctype html><meta charset="utf-8"><body style="font:14px system-ui;padding:1rem;color:#b91c1c;">Preview failed: '+String(g&&g.message||"unknown error").replace(/[<&]/g,b=>b==="<"?"<":"&")+"</body>"}},500)},te=it(W,e.find("#editor-toolbar"),{spacerDefault:L});rt(W.get(0));let he=!1,xe="split",ie=null;const J=document.createElement("button");J.className="editor-view-btn",J.type="button";const Ne=document.createElement("span");Ne.setAttribute("data-icon","css-code"),J.appendChild(Ne);const fe=document.createElement("button");fe.className="editor-view-btn",fe.type="button";const Oe=document.createElement("span");Oe.setAttribute("data-icon","code"),fe.appendChild(Oe);let Me=!1;const _=document.createElement("button");_.className="editor-view-btn",_.type="button",_.innerHTML='<span data-icon="chevrons-down" data-icon-size="10" style="display:inline-flex;align-items:center;vertical-align:middle;"></span>',_.style.cssText="font-size:9px;letter-spacing:-1px;";const ke=e.find(".editor-toolbar-right").get(0);ke.prepend(_),ke.prepend(fe),ke.prepend(J),I.scan(J),I.scan(fe),I.scan(_),E.tooltip(J,{content:"Edit Custom CSS",position:"top"}),E.tooltip(fe,{content:"Format Markdown",position:"top"}),E.tooltip(_,{content:"Fold / unfold all shortcodes",position:"top"}),fe.addEventListener("click",()=>{const t=W.get(0),a=t.selectionStart,g=kt(s.getUnfoldedContent());s.unfoldAll(),j=!0,t.value=g,j=!1,t.selectionStart=t.selectionEnd=Math.min(a,g.length),t.dispatchEvent(new Event("input")),s.refresh(),E.toast("Markdown formatted.",{type:"success"})}),_.addEventListener("click",()=>{Me?(s.unfoldAll(),_.innerHTML='<span data-icon="chevrons-down" data-icon-size="10" style="display:inline-flex;align-items:center;vertical-align:middle;"></span>',I.scan(_),_.classList.remove("active")):(s.foldAll(),_.innerHTML='<span data-icon="chevrons-right" data-icon-size="10" style="display:inline-flex;align-items:center;vertical-align:middle;"></span>',I.scan(_),_.classList.add("active")),Me=!Me});const Se=document.createElement("span");Se.className="editor-toolbar-sep",ke.insertBefore(Se,ke.children[3]),J.addEventListener("click",()=>{he=!he;const t=e.find("#markdown-editor"),a=e.find("#css-editor"),g=e.find("#editor-body");if(he){const b=e.find(".editor-view-btn[data-mode].active").get(0);xe=b?b.getAttribute("data-mode"):"split",e.find(".editor-view-btn[data-mode]").removeClass("active"),e.find('.editor-view-btn[data-mode="split"]').addClass("active"),g.removeClass("editor-mode-write editor-mode-preview").addClass("editor-mode-split"),t.hide(),a.show(),z||(a.val(Ee),z=!0,Ge(a.get(0))),K(),J.classList.add("active")}else e.find(".editor-view-btn[data-mode]").removeClass("active"),e.find(`.editor-view-btn[data-mode="${xe}"]`).addClass("active"),g.removeClass("editor-mode-split editor-mode-write editor-mode-preview").addClass(`editor-mode-${xe}`),a.hide(),t.show(),s.refresh(),J.classList.remove("active")});const Te=We();Te.viewMode&&Te.viewMode!=="split"&&(e.find(".editor-view-btn[data-mode]").removeClass("active"),e.find(`.editor-view-btn[data-mode="${Te.viewMode}"]`).addClass("active"),e.find("#editor-body").removeClass("editor-mode-split editor-mode-write editor-mode-preview").addClass(`editor-mode-${Te.viewMode}`)),Te.fullscreen&&e.find(".editor-card").addClass("editor-fullscreen"),e.find("#css-editor").on("input",()=>{U(),K(),clearTimeout(ie),ie=setTimeout(()=>{le()},400)}),dt({image:async t=>{const a=(await N.media.list().catch(()=>[])).filter(y=>/\.(png|jpe?g|gif|webp|svg)$/i.test(y.name)),g=document.createElement("div");if(g.className="media-picker-grid",a.length)a.forEach(y=>{const C=document.createElement("div");C.className="media-picker-item",C.dataset.url=y.url;const w=document.createElement("img");w.src=y.url,w.alt=y.name;const Q=document.createElement("span");Q.textContent=y.name,C.appendChild(w),C.appendChild(Q),g.appendChild(C)});else{const y=document.createElement("p");y.className="text-muted p-3",y.textContent="No images uploaded yet.",g.appendChild(y)}const b=E.slideover({title:"Insert Image",size:"lg",position:"right",content:g});$(b.element).on("click",".media-picker-item",function(){const y=$(this).data("url"),C=$(this).find("span").text();at(t,``),b.close(),t.dispatchEvent(new Event("input",{bubbles:!0}))}),b.open()},scribe:t=>pt(t),card:t=>nt.open(t,{onMediaBrowse:()=>N.media.list().catch(()=>[])}),timeline:t=>ot.open(t,{onPreview:async a=>{try{return await N.pages.preview(a)}catch{return null}},onListCollections:()=>N.collections.list().catch(()=>[]),onGetCollection:a=>N.collections.get(a).catch(()=>null)})}),te.onEffects(t=>lt.open(t)),te.onHelp(()=>{Xe()}),te.onTutorial(t=>{mt(t)}),e.find(".editor-view-btn").on("click",function(){const t=$(this).data("mode");t&&(e.find(".editor-view-btn").removeClass("active"),$(this).addClass("active"),e.find("#editor-body").removeClass("editor-mode-split editor-mode-write editor-mode-preview").addClass(`editor-mode-${t}`),_e("viewMode",t),t!=="write"&&pe())}),e.find("#fullscreen-btn").on("click",function(){e.find(".editor-card").toggleClass("editor-fullscreen"),_e("fullscreen",e.find(".editor-card").hasClass("editor-fullscreen"))}),W.on("input",()=>{j||(U(),pe())});const ne=e.find("#editor-meta-tabs").get(0);ne&&(ne.addEventListener("input",()=>U()),ne.addEventListener("change",t=>{t.target.id!=="quick-switch"&&U()})),pe();const Ve=t=>({title:e.find("#field-title").val().trim()||"Untitled",description:e.find("#field-description").val().trim(),layout:e.find("#field-layout").val(),status:e.find("#field-status").val(),sortOrder:parseInt(e.find("#field-sort-order").val(),10)||99,showInNav:e.find("#field-show-in-nav").is(":checked"),sidebar:e.find("#field-sidebar").is(":checked"),...e.find("#field-show-breadcrumbs").is(":checked")?{}:{breadcrumbs:!1},...e.find("#field-bundled").is(":checked")?{bundled:!0}:{},tags:f?f.getValue():[],category:e.find("#field-category").val().trim()||null,visibility:e.find("#field-visibility").val()||"public",project:e.find("#field-project").val()||"",seo:{title:e.find("#field-seo-title").val().trim(),description:e.find("#field-seo-desc").val().trim()},dconfig:t,...e.find("#field-theme").val()?{theme:e.find("#field-theme").val()}:{}});(()=>{const t=e.find("#editor-body").get(0),a=e.find("#editor-divider").get(0);if(!t||!a)return;const g=15,b=85,y=O=>Math.min(b,Math.max(g,O));let C=y(parseFloat(We().splitRatio)||50);const w=O=>{t.style.setProperty("--dm-editor-split",C+"%"),a.setAttribute("aria-valuenow",String(Math.round(C))),O&&_e("splitRatio",C)};w(!1);const Q=O=>{const Ae=t.getBoundingClientRect();return Ae.width?y((O-Ae.left)/Ae.width*100):C},me=O=>{C=Q(O.clientX),w(!1)},Ue=()=>{document.removeEventListener("pointermove",me),document.removeEventListener("pointerup",Ue),t.classList.remove("is-resizing"),a.classList.remove("is-dragging"),w(!0)};a.addEventListener("pointerdown",O=>{window.matchMedia("(max-width: 768px)").matches||(O.preventDefault(),t.classList.add("is-resizing"),a.classList.add("is-dragging"),document.addEventListener("pointermove",me),document.addEventListener("pointerup",Ue))}),a.addEventListener("dblclick",()=>{C=50,w(!0)}),a.addEventListener("keydown",O=>{const Ae=O.shiftKey?10:2;let we=C;if(O.key==="ArrowLeft")we=C-Ae;else if(O.key==="ArrowRight")we=C+Ae;else if(O.key==="Home")we=g;else if(O.key==="End")we=b;else if(O.key==="Enter"||O.key===" ")we=50;else return;O.preventDefault(),C=y(we),w(!0)})})(),(()=>{const t=e.find("#live-preview-frame").get(0),a=e.find("#live-preview-status").get(0),g=e.find("#live-preview-stage").get(0);if(!t)return;let b=null,y=!0,C=!1,w=!1;const Q=se=>{a&&(a.textContent=se)},me=()=>{const se=(e.find("#field-dconfig").val()||"").trim();if(!se)return null;try{return JSON.parse(se)}catch{return null}},Ue=async()=>{if(!w){w=!0,Q("Rendering\u2026");try{const se=(e.find("#page-url-path").val()||"").trim()||v||"/",{html:et}=await N.pages.previewFull(se,Ve(me()),s.getUnfoldedContent());let je=0;try{je=t.contentWindow.scrollY||0}catch{}t.addEventListener("load",function tt(){t.removeEventListener("load",tt);try{t.contentWindow.scrollTo(0,je)}catch{}},{once:!0}),t.srcdoc=et,y=!1,Q("Showing unsaved changes")}catch(se){Q("Preview failed - "+(se&&se.message?se.message:"unknown error"))}finally{w=!1}}},O=()=>{y=!0,C&&(clearTimeout(b),b=setTimeout(Ue,600))},Ae=e.find("#live-preview-tab").get(0);Ae&&Ae.addEventListener("click",()=>{C=!0,y&&Ue()}),e.find("#editor-meta-tabs .tab-item").each(function(){this.id!=="live-preview-tab"&&this.addEventListener("click",()=>{C=!1})}),ee.addEventListener("input",O);const we=e.find("#editor-meta-tabs").get(0);we&&(we.addEventListener("input",O),we.addEventListener("change",O)),e.find("#live-preview-refresh").on("click",()=>Ue()),e.find(".live-preview-width").on("click",function(){const se=this.getAttribute("data-width");e.find(".live-preview-width").removeClass("active"),$(this).addClass("active"),se==="full"?t.removeAttribute("data-width"):t.setAttribute("data-width",se),g&&(g.scrollTop=0)})})(),e.find("#share-preview-btn").on("click",async()=>{if(!x){E.toast("Save the page first - a share link needs a page to point at.",{type:"warning"});return}await Et(v)});const ve=()=>({urlPath:(e.find("#page-url-path").val()||"").trim(),title:e.find("#field-title").val(),description:e.find("#field-description").val(),layout:e.find("#field-layout").val(),status:e.find("#field-status").val(),sortOrder:e.find("#field-sort-order").val(),showInNav:e.find("#field-show-in-nav").is(":checked"),sidebar:e.find("#field-sidebar").is(":checked"),breadcrumbs:e.find("#field-show-breadcrumbs").is(":checked"),bundled:e.find("#field-bundled").is(":checked"),tags:f?f.getValue():[],category:e.find("#field-category").val(),visibility:e.find("#field-visibility").val(),project:e.find("#field-project").val(),theme:e.find("#field-theme").val(),seoTitle:e.find("#field-seo-title").val(),seoDesc:e.find("#field-seo-desc").val(),dconfig:e.find("#field-dconfig").val(),body:s.getUnfoldedContent(),css:z?e.find("#css-editor").val():null}),X=(t,a)=>e.find(t).val(a==null?"":String(a)),Be=t=>{if(X("#page-url-path",t.urlPath),X("#field-title",t.title),X("#field-description",t.description),X("#field-layout",t.layout),X("#field-status",t.status),X("#field-sort-order",t.sortOrder),e.find("#field-show-in-nav").prop("checked",!!t.showInNav),e.find("#field-sidebar").prop("checked",!!t.sidebar),e.find("#field-show-breadcrumbs").prop("checked",!!t.breadcrumbs),e.find("#field-bundled").prop("checked",!!t.bundled),f&&f.setValue(Array.isArray(t.tags)?t.tags:[]),X("#field-category",t.category),X("#field-visibility",t.visibility),X("#field-project",t.project),X("#field-theme",t.theme),X("#field-seo-title",t.seoTitle),X("#field-seo-desc",t.seoDesc),X("#field-dconfig",t.dconfig),s.unfoldAll(),j=!0,ee.value=t.body==null?"":String(t.body),j=!1,s.refresh(),t.css!=null){const a=e.find("#css-editor").get(0);z||(z=!0,Ge(a)),a.value=String(t.css)}pe()};let de=qe(ve());const Ie=e.find("#pe-unsaved").get(0),ye=e.find("#discard-btn").get(0),Pe=e.find("#pe-problem").get(0);let Fe=null;const Ce=()=>{const t=ve();m=qe(t)!==de,Ie&&(Ie.hidden=!m),ye&&(ye.hidden=!m),e.find("#quick-switch").prop("disabled",m);const a=Je(t)[0];Pe&&(Pe.hidden=!a||!m,Pe.textContent=a?a.text:"")};U=()=>{clearTimeout(Fe),Fe=setTimeout(Ce,120)},p(()=>clearTimeout(Fe)),p(()=>{clearTimeout(re),clearTimeout(ie)});const ze=bt(v||""),$e=()=>{if(m)try{S.set(ze,vt(ve(),de))}catch{}},De=()=>{try{S.remove?S.remove(ze):S.set(ze,null)}catch{}};V.keepDraft=$e;const He=t=>{Ce(),m&&($e(),t.preventDefault(),t.returnValue="")};window.addEventListener("beforeunload",He),p(()=>window.removeEventListener("beforeunload",He));let c=!1;const r=async()=>{if(c)return;const t=ve(),a=Je(t);if(a.length){E.toast(a[0].text,{type:"warning"});const Q=[...e.find("#editor-meta-tabs .tab-item").get()],me=a[0].field==="dconfig"?3:1;Q[me]&&Q[me].click();return}const g=t.urlPath,b=(t.dconfig||"").trim(),y=b?JSON.parse(b):null,C=Ve(y);c=!0;const w=e.find("#save-btn").get(0);w&&(w.disabled=!0,w.lastChild.textContent=" Saving\u2026");try{const Q=s.getUnfoldedContent();if(x){const me={frontmatter:C,body:Q};g!==v&&(me.newUrlPath=g),await N.pages.update(v,me),z&&await N.settings.saveCustomCss(e.find("#css-editor").val()),E.toast(z?"Page and CSS saved.":"Page saved.",{type:"success"}),De(),de=qe(ve()),Ce(),g!==v&&R.navigate(`/pages/edit${g}`)}else await N.pages.create({urlPath:g,frontmatter:C,body:Q}),z&&await N.settings.saveCustomCss(e.find("#css-editor").val()),E.toast(z?"Page and CSS saved.":"Page saved.",{type:"success"}),De(),de=qe(ve()),m=!1,R.navigate("/pages")}catch(Q){E.toast(`Save failed: ${Q.message||"Unknown error"}`,{type:"error"})}finally{c=!1,w&&(w.disabled=!1,w.lastChild.textContent=" Save")}};e.find("#save-btn").on("click",()=>r());const o=async()=>{!m||!await E.confirm("Discard the changes you have not saved?")||(Be(JSON.parse(de)),De(),Ce())};ye&&ye.addEventListener("click",o);const i=t=>{(t.ctrlKey||t.metaKey)&&!t.shiftKey&&!t.altKey&&t.key.toLowerCase()==="s"&&F()&&(t.preventDefault(),r())};document.addEventListener("keydown",i),p(()=>document.removeEventListener("keydown",i));const n=e.get(0).querySelector(".dm-plugin-banner-name");if(n){const t=n.lastChild,a=ft(x,h?.title,v);t&&t.nodeType===3&&n.querySelector(".dm-plugin-banner-sep")?t.textContent=a:n.textContent=`Pages \u203A ${a}`}Ye(e,{id:"page-editor-back",icon:"arrow-left",title:"Back to Pages",onClick:()=>R.navigate("/pages")}),Ye(e,{id:"page-editor-help",icon:"help-circle",title:"Editor reference",onClick:()=>Xe()}),x&&Ye(e,{id:"page-editor-view",icon:"external-link",title:"View on the site",onClick:()=>window.open(e.find("#page-url-path").val()||v,"_blank","noopener")}),Ye(e,{id:"page-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>r()});const Re=e.get(0).querySelector(".dm-plugin-body")||e.get(0);p(ut(Re,{position:"bottom"})),Domma.icons.scan(),Ce();let Z=null;try{Z=S.get(ze)}catch{}const Le=yt(Z);if(Le&&(De(),qe(Le.state)!==de)){const t=Le.at?` (${D(Le.at).format("D MMM HH:mm")})`:"",a=gt(Le,de)?" The saved page has changed since - restoring puts your version back over it (the saved one stays in History).":"";await E.confirm(`You left unsaved changes to this page${t}. Restore them?${a}`)&&F()&&(Be(Le.state),Ce())}}};async function St(e,T){if(!e)return;let A=[];try{const u=await N.get("/collections/roles/entries?limit=100");A=Array.isArray(u)?u:u?.entries||[]}catch{}const p=A.map(u=>({name:u.data?.name,label:u.data?.label||u.data?.name,level:u.data?.level??99})).filter(u=>u.name&&u.level!==0).sort((u,v)=>v.level-u.level),m=(u,v)=>Object.assign(document.createElement("option"),{value:u,textContent:v}),U=()=>{typeof T=="string"&&T&&![...e.options].some(u=>u.value===T)&&e.append(m(T,`${T} (custom)`))};if(!p.length)return U();const V=document.createElement("optgroup");V.label="Role and above",p.forEach(u=>V.append(m(u.name,`${u.label} and above`)));const F=document.createElement("optgroup");F.label="Role only",p.forEach(u=>F.append(m(`=${u.name}`,`${u.label} only`))),e.replaceChildren(m("public","Public - everyone"),V,F,m("private","Private - super-admin only")),U()}
|
|
43
|
+
`)}const Ne="editor_prefs";function I(){return S.get(Ne)||{viewMode:"split",fullscreen:!1}}function ue(s,r){const c=I();c[s]=r,S.set(Ne,c)}const Tt=["edit","details","seo","dconfig","live","history"],Ie=["split","write","preview"],Pt=s=>s.replace(/<\/head>/i,"<style>body.dm-cloaked{opacity:1 !important;transition:none !important}</style></head>");function je(s,r,c=()=>{}){let o=0;try{o=s.contentWindow.scrollY||0}catch{}s.addEventListener("load",()=>{try{s.contentWindow.scrollTo(0,o)}catch{}c()},{once:!0}),s.srcdoc=r}export const pageEditorView={templateUrl:"/admin/js/templates/page-editor.html",async onMount(s){if(Ct(),k){const e=k;k=null,e.dispose()}const r=window.location.hash.replace(/^#/,""),c=[],o=e=>(c.push(e),e);let v=()=>!1,d=()=>{};const u={path:r,dirty:()=>v(),keepDraft:()=>d(),dispose:()=>{c.splice(0).forEach(e=>{try{e()}catch{}})}};k=u;const i=s.get(0),p=()=>k===u&&i.isConnected,C=window.location.hash.match(/#\/pages\/edit(\/[^?]*)/),l=C?C[1]:null,h=!!l,T=E.loader(i,{type:"dots"}),[Ve,Ye,b,ve,me,He]=await Promise.all([f.layouts.get().catch(()=>({})),f.settings.get().catch(()=>({})),h?f.pages.get(l).catch(()=>null):null,h?f.pages.list().catch(()=>[]):[],f.projects.list().catch(()=>[]),f.pages.tags().catch(()=>[])]);if(!p()){T.destroy();return}if(T.destroy(),h&&!b){E.toast("Page not found.",{type:"error"}),R.navigate("/pages");return}const g=e=>i.querySelector(e);if(lt(g("#field-theme"),{includeDefault:!0}),await Ot(g("#field-visibility"),b?.visibility),!p())return;const qe=Rt(g("#field-layout"),Ve,b?.layout||"default");for(const e of me)g("#field-project").append(new Option(e.name||e.slug,e.slug));const fe=[...Array.isArray(ve)?ve:[]].sort((e,t)=>(e.title||e.urlPath).localeCompare(t.title||t.urlPath));for(const e of fe)g("#quick-switch").append(new Option(`${e.title||"Untitled"} (${e.urlPath})`,e.urlPath,!1,e.urlPath===l));const W=I(),a={urlPath:M.observable(l||""),title:M.observable(b?.title||""),description:M.observable(b?.description||""),layout:M.observable(qe),status:M.observable(b?.status||"draft"),sortOrder:M.observable(String(b?.sortOrder??99)),showInNav:M.observable(!!b?.showInNav),sidebar:M.observable(!!b?.sidebar),breadcrumbs:M.observable(b?b.breadcrumbs!==!1:!0),bundled:M.observable(!!b?.bundled),tags:M.observable(Array.isArray(b?.tags)?b.tags:[]),category:M.observable(b?.category||""),visibility:M.observable(typeof b?.visibility=="string"&&b.visibility?b.visibility:"public"),project:M.observable(gt({isEdit:h,page:b,projects:me,fromHash:vt()||"",urlPath:l||""})),theme:M.observable(b?.theme||""),seoTitle:M.observable(b?.seo?.title||""),seoDesc:M.observable(b?.seo?.description||""),dconfig:M.observable(b?.dconfig?JSON.stringify(b.dconfig,null,2):""),css:M.observable(""),cssOpened:M.observable(!1)},n={bodyRev:M.observable(0),base:M.observable(""),saving:M.observable(!1),tab:M.observable("edit"),viewMode:M.observable(Ie.includes(W.viewMode)?W.viewMode:"split"),fullscreen:M.observable(!!W.fullscreen),cssMode:M.observable(!1),folded:M.observable(!1),liveStatus:M.observable("Showing unsaved changes"),liveWidth:M.observable("full"),versions:M.observable([]),histLoading:M.observable(!1),histError:M.observable(""),compare:M.observable(null)},w=g("#markdown-editor"),j=g("#css-editor");w.value=b?.content||"";const L=document.createElement("div");L.className="editor-line-numbers editor-line-numbers--foldable",w.parentElement.insertBefore(L,w);const x=ct(w,L,{onFoldChange:()=>{}});let O=!1;x.setGuard(e=>{O=e});const F=()=>x.getUnfoldedContent(),B=()=>(n.bodyRev.value,{urlPath:a.urlPath.value.trim(),title:a.title.value,description:a.description.value,layout:a.layout.value,status:a.status.value,sortOrder:a.sortOrder.value,showInNav:a.showInNav.value,sidebar:a.sidebar.value,breadcrumbs:a.breadcrumbs.value,bundled:a.bundled.value,tags:a.tags.value,category:a.category.value,visibility:a.visibility.value,project:a.project.value,theme:a.theme.value,seoTitle:a.seoTitle.value,seoDesc:a.seoDesc.value,dconfig:a.dconfig.value,body:F(),css:a.cssOpened.value?a.css.value:null}),Ke=M.computed(()=>N(B())!==n.base.value),ze=M.computed(()=>Be({urlPath:a.urlPath.value,dconfig:a.dconfig.value})[0]?.text||""),Ue=M.computed(()=>n.versions.value.some(e=>e.checked.value)),We=M.computed(()=>n.versions.value.length>0&&n.versions.value.every(e=>e.checked.value)),_e=M.computed(()=>{const e=n.versions.value.filter(t=>t.checked.value).length;return e>0&&e<n.versions.value.length});v=()=>N(B())!==n.base.peek();const _=e=>({title:a.title.peek().trim()||"Untitled",description:a.description.peek().trim(),layout:a.layout.peek(),status:a.status.peek(),sortOrder:parseInt(String(a.sortOrder.peek()),10)||99,showInNav:a.showInNav.peek(),sidebar:a.sidebar.peek(),...a.breadcrumbs.peek()?{}:{breadcrumbs:!1},...a.bundled.peek()?{bundled:!0}:{},tags:a.tags.peek(),category:a.category.peek().trim()||null,visibility:a.visibility.peek()||"public",project:a.project.peek()||"",seo:{title:a.seoTitle.peek().trim(),description:a.seoDesc.peek().trim()},dconfig:e,...a.theme.peek()?{theme:a.theme.peek()}:{}}),be=()=>{const e=a.dconfig.peek().trim();if(!e)return null;try{return JSON.parse(e)}catch{return null}},he=()=>a.urlPath.peek().trim()||l||"/",V=g("#markdown-preview");let ge="";f.settings.getCustomCss().then(({css:e})=>{ge=e||""}).catch(()=>{});const ye=()=>{const e=V?.contentDocument;if(!e?.head)return;const t=a.cssOpened.peek()?a.css.peek():"";if(!t)return;let m=e.getElementById("preview-custom-css");m||(m=e.createElement("style"),m.id="preview-custom-css",e.head.appendChild(m)),m.textContent=t};let J=null;const Y=()=>{clearTimeout(J),J=setTimeout(async()=>{if(!(!V||n.viewMode.peek()==="write"))try{const{html:e}=await f.pages.previewFull(he(),_(be()),F());p()&&je(V,Pt(e),ye)}catch(e){V.srcdoc='<!doctype html><meta charset="utf-8"><body style="font:14px system-ui;padding:1rem;color:#b91c1c;">Preview failed: '+String(e?.message||"unknown error").replace(/[<&]/g,t=>t==="<"?"<":"&")+"</body>"}},500)},we=g("#live-preview-frame");let G=!0,X=null,Q=!1;const Z=async()=>{if(!(Q||!we)){Q=!0,n.liveStatus.value="Rendering\u2026";try{const{html:e}=await f.pages.previewFull(he(),_(be()),F());if(!p())return;je(we,e),G=!1,n.liveStatus.value="Showing unsaved changes"}catch(e){n.liveStatus.value="Preview failed - "+(e?.message||"unknown error")}finally{Q=!1}}},ke=()=>{G=!0,n.tab.peek()==="live"&&(clearTimeout(X),X=setTimeout(Z,600))};o(()=>{clearTimeout(J),clearTimeout(X)});const ee=st(s.find("#markdown-editor"),s.find("#editor-toolbar"),{spacerDefault:Ye?.layoutOptions?.spacerSize??40});rt(w);const te=i.querySelector(".editor-toolbar-right");te.querySelectorAll(".editor-view-btn[data-mode]").forEach(e=>{const t=e.getAttribute("data-mode");e.classList.remove("active"),e.setAttribute("data-bind-class",`viewMode.value === '${t}' && 'active'`),e.setAttribute("data-on-click",`setMode('${t}')`)}),g("#fullscreen-btn")?.setAttribute("data-on-click","toggleFullscreen");const Je=U(`
|
|
44
|
+
<span class="pe-toolbar-extras" style="display:contents">
|
|
45
|
+
<button type="button" class="editor-view-btn" data-tip="Edit Custom CSS" data-on-click="toggleCss"
|
|
46
|
+
data-bind-class="cssMode.value && 'active'"><span data-icon="css-code"></span></button>
|
|
47
|
+
<button type="button" class="editor-view-btn" data-tip="Format Markdown" data-on-click="formatMarkdown"><span data-icon="code"></span></button>
|
|
48
|
+
<button type="button" class="editor-view-btn" data-tip="Fold / unfold all shortcodes" data-on-click="toggleFold"
|
|
49
|
+
style="font-size:9px;letter-spacing:-1px;" data-bind-class="folded.value && 'active'">
|
|
50
|
+
<!-- I.scan swaps an icon span for an <svg>: the bindings go on a wrapper that stays. -->
|
|
51
|
+
<span class="pe-fold-icon" data-bind-hidden="folded.value"><span data-icon="chevrons-down" data-icon-size="10"></span></span>
|
|
52
|
+
<span class="pe-fold-icon" hidden data-bind-hidden="!folded.value"><span data-icon="chevrons-right" data-icon-size="10"></span></span>
|
|
53
|
+
</button>
|
|
54
|
+
<span class="editor-toolbar-sep"></span>
|
|
55
|
+
</span>`);te.prepend(Je);const Me=g("#field-tags"),ae=Me?E.pillbox(Me,{data:He,value:a.tags.peek(),creatable:!0,searchable:!0,placeholder:"Add tag\u2026",onChange:()=>{a.tags.value=ae?[...ae.getValue()]:[]}}):null;let se=!1,oe=null;const H=async()=>{se=!0,n.histLoading.value=!0,n.histError.value="";try{const e=(await f.versions.list(l)).versions||[];if(!p())return;n.versions.value=e.map(t=>({...t,when:D(t.createdAt).format("D MMM YYYY, HH:mm"),author:t.author||"",label:t.label||"",checked:M.observable(!1)}))}catch(e){n.histError.value="Failed to load versions: "+e.message}finally{n.histLoading.value=!1}},ne=e=>n.versions.peek().find(t=>t.filename===e),re=async e=>{try{const t=await f.versions.get(l,e.filename);oe=e.filename,n.compare.value={label:$e(e),old:t.content,current:F()}}catch(t){E.toast("Failed to load version: "+t.message,{type:"error"})}},Ee=async e=>{if(!(!e||!await E.confirm("Restore this version? The current page content will be overwritten (a pre-restore snapshot will be saved automatically).")))try{await f.versions.restore(l,e),E.toast("Page restored. Reloading editor\u2026",{type:"success"}),R.navigate("/pages"),setTimeout(()=>R.navigate(`/pages/edit${l}`),300)}catch(t){E.toast("Restore failed: "+t.message,{type:"error"})}},Ge=async e=>{if(await E.confirm("Delete this version? This cannot be undone."))try{await f.versions.delete(l,e.filename),E.toast("Version deleted.",{type:"success"}),await H()}catch(t){E.toast("Failed to delete: "+t.message,{type:"error"})}},Se=()=>{n.bodyRev.value=n.bodyRev.peek()+1},Ce=()=>{a.cssOpened.peek()||(a.css.value=ge,a.cssOpened.value=!0,at(j))},xe=e=>{for(const t of["urlPath","title","description","layout","status","sortOrder","category","visibility","project","theme","seoTitle","seoDesc","dconfig"])a[t].value=e[t]==null?"":String(e[t]);for(const t of["showInNav","sidebar","breadcrumbs","bundled"])a[t].value=!!e[t];a.tags.value=Array.isArray(e.tags)?e.tags:[],ae?.setValue(a.tags.peek()),x.unfoldAll(),O=!0,w.value=e.body==null?"":String(e.body),O=!1,x.refresh(),e.css!=null&&(Ce(),a.css.value=String(e.css)),Se()};n.base.value=N(B());const q=ht(l||"");d=()=>{if(v())try{S.set(q,yt(B(),n.base.peek()))}catch{}};const ie=()=>{try{S.remove?S.remove(q):S.set(q,null)}catch{}},Te=e=>{v()&&(d(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",Te),o(()=>window.removeEventListener("beforeunload",Te));const le=async()=>{if(n.saving.peek())return;const e=B(),t=Be(e);if(t.length){E.toast(t[0].text,{type:"warning"}),i.querySelectorAll("#editor-meta-tabs .tab-item")[Tt.indexOf(t[0].field==="dconfig"?"dconfig":"details")]?.click();return}const m=(e.dconfig||"").trim(),y=_(m?JSON.parse(m):null),P=a.cssOpened.peek();n.saving.value=!0;try{if(h){const A={frontmatter:y,body:e.body};e.urlPath!==l&&(A.newUrlPath=e.urlPath),await f.pages.update(l,A)}else await f.pages.create({urlPath:e.urlPath,frontmatter:y,body:e.body});P&&await f.settings.saveCustomCss(a.css.peek()),E.toast(P?"Page and CSS saved.":"Page saved.",{type:"success"}),ie(),n.base.value=N(B()),h?e.urlPath!==l&&R.navigate(`/pages/edit${e.urlPath}`):R.navigate("/pages")}catch(A){E.toast(`Save failed: ${A.message||"Unknown error"}`,{type:"error"})}finally{n.saving.value=!1}},Pe=i.querySelector(".dm-plugin-body")||i,Xe={...a,...n,dirty:Ke,problem:ze,anySelected:Ue,allSelected:We,someSelected:_e,isEdit:h,savedPath:l||"",hasPages:fe.length>0},Qe={save:()=>le(),discard:async()=>{!v()||!await E.confirm("Discard the changes you have not saved?")||(xe(JSON.parse(n.base.peek())),ie())},switchPage:e=>{const t=e?.target?.value;t&&R.navigate("/pages/edit"+t)},showTab:e=>{n.tab.value=e,e==="live"&&G&&Z(),e==="history"&&h&&!se&&H()},setMode:e=>{n.cssMode.peek()&&(n.cssMode.value=!1),n.viewMode.value=e,ue("viewMode",e),e!=="write"&&Y()},toggleFullscreen:()=>{n.fullscreen.value=!n.fullscreen.peek(),ue("fullscreen",n.fullscreen.peek())},toggleCss:()=>{n.cssMode.peek()?(n.cssMode.value=!1,n.viewMode.value=Ie.includes(I().viewMode)?I().viewMode:"split",x.refresh()):(Ce(),n.cssMode.value=!0,n.viewMode.value="split")},formatMarkdown:()=>{const e=w.selectionStart,t=xt(F());x.unfoldAll(),O=!0,w.value=t,O=!1,w.selectionStart=w.selectionEnd=Math.min(e,t.length),w.dispatchEvent(new Event("input")),x.refresh(),E.toast("Markdown formatted.",{type:"success"})},toggleFold:()=>{n.folded.peek()?x.unfoldAll():x.foldAll(),n.folded.value=!n.folded.peek()},setWidth:e=>{n.liveWidth.value=e;const t=g("#live-preview-stage");t&&(t.scrollTop=0)},refreshLive:()=>Z(),share:async()=>{if(!h){E.toast("Save the page first - a share link needs a page to point at.",{type:"warning"});return}await Et(l)},toggleAll:e=>{const t=!!e?.target?.checked;n.versions.peek().forEach(m=>{m.checked.value=t})},deleteSelected:async()=>{const e=n.versions.peek().filter(m=>m.checked.peek()).map(m=>m.filename),t=e.length;if(!(!t||!await E.confirm(`Delete ${t} version${t>1?"s":""}? This cannot be undone.`)))try{await f.versions.bulkDelete(l,e),E.toast(`${t} version${t>1?"s":""} deleted.`,{type:"success"}),await H()}catch(m){E.toast("Delete failed: "+m.message,{type:"error"})}},saveNamed:async()=>{await St(l)&&p()&&(n.tab.peek()==="history"?await H():se=!1)},closeCompare:()=>{n.compare.value=null,oe=null},restoreCompared:()=>Ee(oe)};te.querySelectorAll("[data-tip]").forEach(e=>E.tooltip(e,{content:e.getAttribute("data-tip"),position:"top"}));const Ze=M.applyBindings(Xe,Pe,{methods:Qe});o(()=>Ze.dispose()),E.tabs(g("#editor-meta-tabs"));let ce=null;o(()=>clearTimeout(ce)),w.addEventListener("input",()=>{x.refresh(),L.scrollTop=w.scrollTop,!O&&(clearTimeout(ce),ce=setTimeout(Se,120),Y(),ke())}),w.addEventListener("scroll",()=>{L.scrollTop=w.scrollTop}),j.addEventListener("scroll",()=>{L.scrollTop=j.scrollTop});let Le=!0;o(M.effect(()=>{for(const e of Object.keys(a))e!=="css"&&e!=="cssOpened"&&a[e].value;if(n.bodyRev.value,Le){Le=!1,Y();return}Y(),ke()}));let de=null;if(o(()=>clearTimeout(de)),o(M.effect(()=>{const e=a.css.value;n.cssMode.value&&(Lt(L,e,j),clearTimeout(de),de=setTimeout(ye,400))})),h){const e=g(".pe-version-list"),t=E.contextMenu(e,{cursor:"glyph",match:"tr[data-file]",inherit:!1,density:"compact",items:m=>{const y=ne(m.dataset.file);return y?[{header:$e(y)},{label:"Compare with the editor",icon:"columns",action:()=>re(y)},{label:"Restore this version\u2026",icon:"rotate-ccw",action:()=>Ee(y.filename)},{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:()=>Ge(y)}]:[]}});o(()=>t?.destroy?.()),e.addEventListener("click",m=>{const y=m.target.closest("tr[data-file]");if(!y||m.target.matches("input"))return;const P=m.target.closest(".pe-kebab");if(P){Re(P);return}const A=ne(y.dataset.file);A&&re(A)}),e.addEventListener("keydown",m=>{const y=m.target.closest("tr[data-file]");if(y){if(m.key==="F10"&&m.shiftKey)m.preventDefault(),Re(y);else if(m.key==="Enter"&&m.target===y){const P=ne(y.dataset.file);P&&re(P)}}})}Dt(g("#editor-body"),g("#editor-divider"),o),it({image:e=>At(e),scribe:e=>dt(e),card:e=>et.open(e,{onMediaBrowse:()=>f.media.list().catch(()=>[])}),timeline:e=>tt.open(e,{onPreview:async t=>{try{return await f.pages.preview(t)}catch{return null}},onListCollections:()=>f.collections.list().catch(()=>[]),onGetCollection:t=>f.collections.get(t).catch(()=>null)})}),ee.onEffects(e=>nt.open(e)),ee.onHelp(()=>Oe()),ee.onTutorial(e=>pt(e));const De=e=>{(e.ctrlKey||e.metaKey)&&!e.shiftKey&&!e.altKey&&e.key.toLowerCase()==="s"&&p()&&(e.preventDefault(),le())};document.addEventListener("keydown",De),o(()=>document.removeEventListener("keydown",De));const K=i.querySelector(".dm-plugin-banner-name");if(K){const e=K.lastChild,t=ft(h,b?.title,l);e&&e.nodeType===3&&K.querySelector(".dm-plugin-banner-sep")?e.textContent=t:K.textContent=`Pages \u203A ${t}`}z(s,{id:"page-editor-back",icon:"arrow-left",title:"Back to Pages",onClick:()=>R.navigate("/pages")}),z(s,{id:"page-editor-help",icon:"help-circle",title:"Editor reference",onClick:()=>Oe()}),h&&z(s,{id:"page-editor-view",icon:"external-link",title:"View on the site",onClick:()=>window.open(a.urlPath.peek()||l,"_blank","noopener")}),z(s,{id:"page-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>le()}),o(ut(Pe,{position:"bottom"})),Domma.icons.scan();let Ae=null;try{Ae=S.get(q)}catch{}const $=wt(Ae);if($&&(ie(),N($.state)!==n.base.peek())){const e=$.at?` (${D($.at).format("D MMM HH:mm")})`:"",t=bt($,n.base.peek())?" The saved page has changed since - restoring puts your version back over it (the saved one stays in History).":"";await E.confirm(`You left unsaved changes to this page${e}. Restore them?${t}`)&&p()&&xe($.state)}}};function Lt(s,r,c){s.replaceChildren(...String(r).split(`
|
|
56
|
+
`).map((o,v)=>{const d=document.createElement("span");d.className="editor-line-number-row";const u=document.createElement("span");u.className="fold-toggle fold-toggle--empty";const i=document.createElement("span");return i.className="editor-line-num",i.textContent=String(v+1),d.append(u,i),d})),s.scrollTop=c.scrollTop}function Dt(s,r,c){if(!s||!r)return;const o=15,v=85,d=l=>Math.min(v,Math.max(o,l));let u=d(parseFloat(I().splitRatio)||50);const i=l=>{s.style.setProperty("--dm-editor-split",u+"%"),r.setAttribute("aria-valuenow",String(Math.round(u))),l&&ue("splitRatio",u)};i(!1);const p=l=>{const h=s.getBoundingClientRect();h.width&&(u=d((l.clientX-h.left)/h.width*100)),i(!1)},C=()=>{document.removeEventListener("pointermove",p),document.removeEventListener("pointerup",C),s.classList.remove("is-resizing"),r.classList.remove("is-dragging"),i(!0)};c(C),r.addEventListener("pointerdown",l=>{window.matchMedia("(max-width: 768px)").matches||(l.preventDefault(),s.classList.add("is-resizing"),r.classList.add("is-dragging"),document.addEventListener("pointermove",p),document.addEventListener("pointerup",C))}),r.addEventListener("dblclick",()=>{u=50,i(!0)}),r.addEventListener("keydown",l=>{const h=l.shiftKey?10:2;let T=u;if(l.key==="ArrowLeft")T=u-h;else if(l.key==="ArrowRight")T=u+h;else if(l.key==="Home")T=o;else if(l.key==="End")T=v;else if(l.key==="Enter"||l.key===" ")T=50;else return;l.preventDefault(),u=d(T),i(!0)})}async function At(s){const r=(await f.media.list().catch(()=>[])).filter(d=>/\.(png|jpe?g|gif|webp|svg)$/i.test(d.name)).map(d=>({url:d.url,name:d.name})),c=U(`
|
|
57
|
+
<div>
|
|
58
|
+
<p class="text-muted p-3" hidden data-bind-hidden="images.length">No images uploaded yet.</p>
|
|
59
|
+
<div class="media-picker-grid" data-each="images key=url">
|
|
60
|
+
<div class="media-picker-item" data-on-click="$root.pick($data)">
|
|
61
|
+
<img data-bind-src="url" data-bind-alt="name"><span data-bind-text="name"></span>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>`),o=E.slideover({title:"Insert Image",size:"lg",position:"right"}),v=M.applyBindings({images:r,pick:d=>{ot(s,``),o.close(),s.dispatchEvent(new Event("input",{bubbles:!0}))}},c);pe(o,()=>v.dispose()),o.element.appendChild(c),o.open()}function Rt(s,r,c){const o=Object.entries(r||{});return s?(s.replaceChildren(...o.map(([v,d])=>new Option(d?.label||v,v))),o.some(([v])=>v===c)?c:o[0]?.[0]||c):c}async function Ot(s,r){if(!s)return;let c=[];try{const p=await f.get("/collections/roles/entries?limit=100");c=Array.isArray(p)?p:p?.entries||[]}catch{}const o=c.map(p=>({name:p.data?.name,label:p.data?.label||p.data?.name,level:p.data?.level??99})).filter(p=>p.name&&p.level!==0).sort((p,C)=>C.level-p.level),v=(p,C)=>Object.assign(document.createElement("option"),{value:p,textContent:C}),d=()=>{typeof r=="string"&&r&&![...s.options].some(p=>p.value===r)&&s.append(v(r,`${r} (custom)`))};if(!o.length)return d();const u=document.createElement("optgroup");u.label="Role and above",o.forEach(p=>u.append(v(p.name,`${p.label} and above`)));const i=document.createElement("optgroup");i.label="Role only",o.forEach(p=>i.append(v(`=${p.name}`,`${p.label} only`))),s.replaceChildren(v("public","Public - everyone"),u,i,v("private","Private - super-admin only")),d()}
|