domma-cms 0.107.0 → 0.108.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/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +2 -2
- package/admin/js/lib/shortcode-context-menu.js +2 -2
- package/admin/js/templates/docs/usage-tools.html +7 -0
- package/package.json +2 -2
- package/plugins/_lib/mail/render.js +14 -1
- package/plugins/_lib/public/call/blur-clock.js +7 -0
- package/plugins/_lib/public/call/blur.js +225 -0
- package/plugins/_lib/public/call/call.css +1 -1
- package/plugins/_lib/public/call/call.js +103 -9
- package/plugins/_lib/public/call/vendor/mediapipe/selfie_segmenter_landscape.tflite +0 -0
- package/plugins/_lib/public/call/vendor/mediapipe/vision_bundle.mjs +2 -0
- package/plugins/_lib/public/call/vendor/mediapipe/vision_wasm_internal.js +8841 -0
- package/plugins/_lib/public/call/vendor/mediapipe/vision_wasm_internal.wasm +0 -0
- package/public/js/celebrations/index.js +1 -1
- package/public/js/celebrations/themes/butterfly-drawing.js +1 -0
- package/public/js/celebrations/themes/halloween-drawing.js +1 -0
- package/public/js/celebrations/themes/halloween.js +1 -1
- package/public/js/celebrations/themes/valentines.js +1 -1
- package/public/js/celebrations/trait-data.mjs +1 -0
- package/public/js/effects.js +1 -1
- package/scripts/build.js +6 -0
- package/server/server.js +3 -1
- package/server/templates/page.html +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Domma Tools CSS v0.51.
|
|
2
|
+
* Domma Tools CSS v0.51.5
|
|
3
3
|
* Dynamic Object Manipulation & Modeling API
|
|
4
4
|
* (c) 2026 Darryl Waterhouse & DCBW-IT
|
|
5
|
-
* Built: 2026-
|
|
6
|
-
* Commit:
|
|
5
|
+
* Built: 2026-10-01T08:33:07.263Z
|
|
6
|
+
* Commit: 094a9d3
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/*!
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Domma Tools v0.51.
|
|
2
|
+
* Domma Tools v0.51.5
|
|
3
3
|
* Developer tools: Theme Roller & Page Roller
|
|
4
4
|
* (c) 2026 Darryl Waterhouse & DCBW-IT
|
|
5
|
-
* Built: 2026-
|
|
6
|
-
* Commit:
|
|
5
|
+
* Built: 2026-10-01T08:32:52.270Z
|
|
6
|
+
* Commit: 094a9d3
|
|
7
7
|
*
|
|
8
8
|
* Requires: domma.min.js
|
|
9
9
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const EFFECTS=[{name:"reveal",label:"Reveal",category:"Entrance",description:"Scroll-triggered entrance animation when the element enters the viewport.",attrs:[{name:"animation",label:"Animation",type:"select",default:"fade",options:[["fade","Fade"],["slide-up","Slide up"],["slide-down","Slide down"],["zoom","Zoom"],["flip","Flip"]]},{name:"duration",label:"Duration (ms)",type:"number",default:600},{name:"delay",label:"Delay (ms)",type:"number",default:0},{name:"threshold",label:"Threshold",type:"number",default:.1,step:.05,min:0,max:1},{name:"once",label:"Animate once",type:"checkbox",default:!0}],hosts:["card","box","hero","banner","col"],previewable:!0},{name:"breathe",label:"Breathe",category:"Animation",description:"Continuous gentle scaling - makes elements feel alive.",attrs:[{name:"amplitude",label:"Amplitude (px)",type:"number",default:8},{name:"duration",label:"Duration (ms)",type:"number",default:3e3}],hosts:["card","box","hero","col","button"],previewable:!0},{name:"pulse",label:"Pulse",category:"Animation",description:"A repeating scale pulse - stronger than breathe.",attrs:[{name:"scale",label:"Scale",type:"number",default:1.05,step:.01},{name:"duration",label:"Duration (ms)",type:"number",default:1e3}],hosts:["card","box","hero","banner","button"],previewable:!0},{name:"shake",label:"Shake",category:"Animation",description:"One-shot horizontal or vertical shake.",attrs:[{name:"intensity",label:"Intensity (px)",type:"number",default:5},{name:"duration",label:"Duration (ms)",type:"number",default:500},{name:"direction",label:"Direction",type:"select",default:"horizontal",options:[["horizontal","Horizontal"],["vertical","Vertical"]]}],hosts:["card","box","banner","button"],previewable:!0},{name:"animate",label:"CSS Animate",category:"Animation",description:"Applies a Domma CSS animation utility class - no JavaScript.",attrs:[{name:"type",label:"Type",type:"select",default:"fade-in-up",options:[["fade-in","fade-in"],["fade-in-up","fade-in-up"],["fade-in-down","fade-in-down"],["fade-in-left","fade-in-left"],["fade-in-right","fade-in-right"],["slide-in-up","slide-in-up"],["slide-in-down","slide-in-down"],["zoom-in","zoom-in"],["zoom-out","zoom-out"],["bounce","bounce"],["pulse","pulse"],["shake","shake"],["spin","spin"],["flip","flip"]]},{name:"duration",label:"Duration",type:"select",default:"normal",options:[["fast","fast"],["normal","normal"],["slow","slow"],["slower","slower"]]},{name:"delay",label:"Delay (ms)",type:"number",default:0},{name:"repeat",label:"Repeat",type:"select",default:"once",options:[["once","Once"],["infinite","Infinite"]]}],hosts:["card","box","hero","banner","col","button"],previewable:!0},{name:"scribe",label:"Scribe (typewriter)",category:"Text",description:"Types text character by character. Simple mode only - for the action-script composer use Insert \u2192 Scribe.",attrs:[{name:"speed",label:"Speed (ms/char)",type:"number",default:50},{name:"cursor",label:"Show cursor",type:"checkbox",default:!0},{name:"loop",label:"Loop",type:"checkbox",default:!1}],hosts:[],previewable:!0},{name:"scramble",label:"Scramble",category:"Text",description:"Reveals text through a character-scramble animation.",attrs:[{name:"speed",label:"Speed (ms/char)",type:"number",default:50},{name:"loop",label:"Loop",type:"checkbox",default:!1}],hosts:[],previewable:!0},{name:"counter",label:"Counter",category:"Text",description:"Animates a number from a starting value to a target. Self-closing shortcode.",attrs:[{name:"to",label:"To",type:"number",default:100},{name:"from",label:"From",type:"number",default:0},{name:"duration",label:"Duration (ms)",type:"number",default:2e3},{name:"prefix",label:"Prefix",type:"text",default:""},{name:"suffix",label:"Suffix",type:"text",default:""},{name:"decimals",label:"Decimal places",type:"number",default:0},{name:"separator",label:"Thousand separator",type:"text",default:""}],hosts:[],previewable:!0,selfClosing:!0},{name:"ripple",label:"Ripple",category:"Visual",description:"Click-triggered ripple effect on the element.",attrs:[{name:"colour",label:"Colour",type:"colour",default:"#7c6af7"},{name:"duration",label:"Duration (ms)",type:"number",default:600},{name:"opacity",label:"Opacity",type:"number",default:.3,step:.05,min:0,max:1}],hosts:["card","box","col","button"],previewable:!0},{name:"twinkle",label:"Twinkle",category:"Visual",description:"Generates animated particle effects inside the element.",attrs:[{name:"count",label:"Particle count",type:"number",default:80},{name:"shape",label:"Shape",type:"select",default:"star",options:[["star","Star"],["circle","Circle"],["square","Square"]]},{name:"colour",label:"Colour",type:"colour",default:"#ffffff"},{name:"min-size",label:"Min size (px)",type:"number",default:2},{name:"max-size",label:"Max size (px)",type:"number",default:6}],hosts:["card","box","hero"],previewable:!0},{name:"ambient",label:"Ambient background",category:"Visual",description:"Applies an animated CSS background class to the element.",attrs:[{name:"type",label:"Type",type:"select",default:"float-blobs",options:[["float-blobs","Float blobs"],["float-orbs","Float orbs"],["float-particles","Float particles"],["rotate-glow","Rotate glow"],["rotate-dual","Rotate dual"],["rotate-spotlight","Rotate spotlight"],["wave","Wave"],["ripple","Ripple"],["aurora","Aurora"]]},{name:"speed",label:"Speed",type:"select",default:"normal",options:[["slow","Slow"],["normal","Normal"],["fast","Fast"]]},{name:"intensity",label:"Intensity",type:"select",default:"subtle",options:[["subtle","Subtle"],["intense","Intense"]]}],hosts:["card","box","hero","banner"],previewable:!0},{name:"firework",label:"Firework",category:"Celebrations",description:"CSS-only firework particle. Self-closing or wrapping form.",attrs:[{name:"type",label:"Type",type:"select",default:"burst",options:[["burst","Burst"],["sparkle","Sparkle"],["trail","Trail"]]},{name:"colour",label:"Colour theme",type:"select",default:"rainbow",options:[["rainbow","Rainbow"],["primary","Primary"],["info","Info"],["success","Success"],["warning","Warning"],["danger","Danger"]]},{name:"size",label:"Size",type:"select",default:"md",options:[["sm","Small"],["md","Medium"],["lg","Large"]]},{name:"continuous",label:"Continuous",type:"checkbox",default:!1},{name:"hover",label:"On hover only",type:"checkbox",default:!1}],hosts:[],previewable:!0},{name:"fireworks",label:"Fireworks show",category:"Celebrations",description:"A container with multiple firework particles inside.",attrs:[],hosts:[],previewable:!1},{name:"celebrate",label:"Celebrate",category:"Celebrations",description:"Canvas-based seasonal particle system. Runs full-page on the published site.",attrs:[{name:"theme",label:"Theme",type:"select",default:"auto",options:[["auto","Auto (date-detected)"],["christmas","Christmas"],["halloween","Halloween"],["valentines","Valentines"],["guy-fawkes","Guy Fawkes"],["st-patricks","St Patrick's"],["st-davids","St David's"],["st-georges","St George's"],["st-andrews","St Andrew's"]]},{name:"intensity",label:"Intensity",type:"select",default:"medium",options:[["light","Light"],["medium","Medium"],["heavy","Heavy"]]},{name:"snow-only",label:"Snow only (Christmas)",type:"checkbox",default:!1,hint:"Just the falling snow - every Christmas decoration off."},{name:"trees",label:"Christmas: trees",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"wreaths",label:"Christmas: wreaths",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"northstars",label:"Christmas: North Star",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"snowmen",label:"Christmas: snowmen",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"sleigh",label:"Christmas: Santa's sleigh",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"elf",label:"Christmas: elf",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"train",label:"Christmas: train",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"firework",label:"Christmas: fireworks",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"robin",label:"Christmas: robin",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"peeing-elf",label:"Peeing elf (opt-in)",type:"checkbox",default:!1,hint:"A clothed rear-view gag. Off by default; Snow only keeps it off."},{name:"thieving-elf",label:"Thieving elf (opt-in)",type:"checkbox",default:!1,hint:"Runs to a tree, takes a present and escapes. Needs a visible tree."}],hosts:[],previewable:!1,selfClosing:!0},{name:"christmas",label:"Christmas",category:"Celebrations",description:"Christmas on the published site: snow, trees, wreaths, snowmen, Santa's sleigh and more. Snow only for just the snow.",attrs:[{name:"intensity",label:"Intensity",type:"select",default:"medium",options:[["light","Light"],["medium","Medium"],["heavy","Heavy"]]},{name:"snow-only",label:"Snow only",type:"checkbox",default:!1,hint:"Just the falling snow - every decoration off."},{name:"trees",label:"trees",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"wreaths",label:"wreaths",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"northstars",label:"North Star",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"snowmen",label:"snowmen",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"sleigh",label:"Santa's sleigh",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"elf",label:"elf",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"train",label:"train",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"firework",label:"fireworks",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"robin",label:"robin",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"peeing-elf",label:"Peeing elf (opt-in)",type:"checkbox",default:!1,hint:"A clothed rear-view gag. Off by default; Snow only keeps it off."},{name:"thieving-elf",label:"Thieving elf (opt-in)",type:"checkbox",default:!1,hint:"Runs to a tree, takes a present and escapes. Needs a visible tree."}],hosts:[],previewable:!1,selfClosing:!0},{name:"ticker-tape",label:"Ticker tape",category:"Celebrations",description:"Coloured rectangular strips fall from the top, sway, rotate, and fade. Inserted self-closing (full-page); hand-write the wrapping form for container-scoped.",attrs:[{name:"palette",label:"Palette",type:"select",default:"theme",options:[["theme","Theme"],["rainbow","Rainbow"],["gold","Gold"],["silver","Silver"],["festive","Festive"],["pastel","Pastel"],["mono","Mono"]]},{name:"density",label:"Density",type:"number",default:50},{name:"speed",label:"Speed multiplier",type:"number",default:1,step:.1},{name:"sway",label:"Sway (px)",type:"number",default:60},{name:"burst",label:"Burst mode (one-shot)",type:"checkbox",default:!1},{name:"burst-count",label:"Burst count",type:"number",default:150}],hosts:["hero"],previewable:!0,selfClosing:!0},{name:"butterflies",label:"Butterflies",category:"Celebrations",description:"Procedurally drawn butterflies wander and rise with flapping wings. Self-closing for full-page; wrapping form for container-scoped.",attrs:[{name:"palette",label:"Palette",type:"select",default:"meadow",options:[["meadow","Meadow"],["theme","Theme"],["rainbow","Rainbow"],["pastel","Pastel"],["festive","Festive"],["gold","Gold"],["silver","Silver"],["mono","Mono"],["sunset","Sunset"],["ocean","Ocean"],["forest","Forest"],["bridal","Bridal"],["firefly","Firefly"]]},{name:"density",label:"Density",type:"number",default:18},{name:"speed",label:"Speed multiplier",type:"number",default:1,step:.1},{name:"wander",label:"Wander",type:"number",default:1,step:.1,hint:"Steering erraticness"},{name:"rise-speed",label:"Rise speed",type:"number",default:.4,step:.1,hint:"Upward drift bias"},{name:"flap-speed",label:"Flap speed",type:"number",default:1,step:.1},{name:"min-size",label:"Min wingspan (px)",type:"number",default:14},{name:"max-size",label:"Max wingspan (px)",type:"number",default:26},{name:"two-tone",label:"Two-tone wings",type:"checkbox",default:!0},{name:"burst",label:"Burst mode (one-shot)",type:"checkbox",default:!1},{name:"burst-count",label:"Burst count",type:"number",default:40}],hosts:["hero"],previewable:!0},{name:"strobe",label:"Strobe lighting",category:"Celebrations",description:"Light beams sweep from the corners and brighten where they cross - club, concert, or searchlight moods. Self-closing for full-page; wrapping form for container-scoped.",attrs:[{name:"preset",label:"Preset",type:"select",default:"club",options:[["club","Club"],["concert","Concert"],["police","Police"],["searchlight","Searchlight"],["scanner","Scanner"],["mood","Mood"]],hint:"Other fields override the preset when changed"},{name:"motion",label:"Motion",type:"select",default:"sweep",options:[["sweep","Sweep (fan back and forth)"],["rotate","Rotate (full circle)"]]},{name:"speed",label:"Speed multiplier",type:"number",default:1,step:.1},{name:"sweep-arc",label:"Sweep arc (deg)",type:"number",default:100},{name:"beam-width",label:"Beam width (deg)",type:"number",default:16},{name:"intensity",label:"Intensity",type:"number",default:.7,step:.05,min:0,max:1},{name:"flicker",label:"Flicker",type:"checkbox",default:!0},{name:"hz",label:"Flicker rate (Hz)",type:"number",default:10,hint:"Rates above 5 flashes/sec can trigger photosensitive seizures"},{name:"colours",label:"Colours",type:"text",default:"",placeholder:"#ffffff, #38bdf8",hint:"Comma-separated hex colours - overrides the preset palette"},{name:"duration",label:"Auto-stop (ms)",type:"number",default:"",hint:"Stop after this many ms - blank runs forever"}],hosts:["hero"],previewable:!0}];export function getEffect(t){return EFFECTS.find(e=>e.name===t)||null}export function effectsByCategory(){return["Entrance","Animation","Text","Visual","Celebrations"].map(e=>({category:e,effects:EFFECTS.filter(a=>a.category===e)}))}export function effectsInjectableInto(t){return EFFECTS.filter(e=>e.hosts.includes(t))}
|
|
1
|
+
import{CELEBRATION_THEMES as i,traitAttribute as f}from"../../../public/js/celebrations/trait-data.mjs";export{i as CELEBRATION_THEMES};export const EFFECTS=[{name:"reveal",label:"Reveal",category:"Entrance",description:"Scroll-triggered entrance animation when the element enters the viewport.",attrs:[{name:"animation",label:"Animation",type:"select",default:"fade",options:[["fade","Fade"],["slide-up","Slide up"],["slide-down","Slide down"],["zoom","Zoom"],["flip","Flip"]]},{name:"duration",label:"Duration (ms)",type:"number",default:600},{name:"delay",label:"Delay (ms)",type:"number",default:0},{name:"threshold",label:"Threshold",type:"number",default:.1,step:.05,min:0,max:1},{name:"once",label:"Animate once",type:"checkbox",default:!0}],hosts:["card","box","hero","banner","col"],previewable:!0},{name:"breathe",label:"Breathe",category:"Animation",description:"Continuous gentle scaling - makes elements feel alive.",attrs:[{name:"amplitude",label:"Amplitude (px)",type:"number",default:8},{name:"duration",label:"Duration (ms)",type:"number",default:3e3}],hosts:["card","box","hero","col","button"],previewable:!0},{name:"pulse",label:"Pulse",category:"Animation",description:"A repeating scale pulse - stronger than breathe.",attrs:[{name:"scale",label:"Scale",type:"number",default:1.05,step:.01},{name:"duration",label:"Duration (ms)",type:"number",default:1e3}],hosts:["card","box","hero","banner","button"],previewable:!0},{name:"shake",label:"Shake",category:"Animation",description:"One-shot horizontal or vertical shake.",attrs:[{name:"intensity",label:"Intensity (px)",type:"number",default:5},{name:"duration",label:"Duration (ms)",type:"number",default:500},{name:"direction",label:"Direction",type:"select",default:"horizontal",options:[["horizontal","Horizontal"],["vertical","Vertical"]]}],hosts:["card","box","banner","button"],previewable:!0},{name:"animate",label:"CSS Animate",category:"Animation",description:"Applies a Domma CSS animation utility class - no JavaScript.",attrs:[{name:"type",label:"Type",type:"select",default:"fade-in-up",options:[["fade-in","fade-in"],["fade-in-up","fade-in-up"],["fade-in-down","fade-in-down"],["fade-in-left","fade-in-left"],["fade-in-right","fade-in-right"],["slide-in-up","slide-in-up"],["slide-in-down","slide-in-down"],["zoom-in","zoom-in"],["zoom-out","zoom-out"],["bounce","bounce"],["pulse","pulse"],["shake","shake"],["spin","spin"],["flip","flip"]]},{name:"duration",label:"Duration",type:"select",default:"normal",options:[["fast","fast"],["normal","normal"],["slow","slow"],["slower","slower"]]},{name:"delay",label:"Delay (ms)",type:"number",default:0},{name:"repeat",label:"Repeat",type:"select",default:"once",options:[["once","Once"],["infinite","Infinite"]]}],hosts:["card","box","hero","banner","col","button"],previewable:!0},{name:"scribe",label:"Scribe (typewriter)",category:"Text",description:"Types text character by character. Simple mode only - for the action-script composer use Insert \u2192 Scribe.",attrs:[{name:"speed",label:"Speed (ms/char)",type:"number",default:50},{name:"cursor",label:"Show cursor",type:"checkbox",default:!0},{name:"loop",label:"Loop",type:"checkbox",default:!1}],hosts:[],previewable:!0},{name:"scramble",label:"Scramble",category:"Text",description:"Reveals text through a character-scramble animation.",attrs:[{name:"speed",label:"Speed (ms/char)",type:"number",default:50},{name:"loop",label:"Loop",type:"checkbox",default:!1}],hosts:[],previewable:!0},{name:"counter",label:"Counter",category:"Text",description:"Animates a number from a starting value to a target. Self-closing shortcode.",attrs:[{name:"to",label:"To",type:"number",default:100},{name:"from",label:"From",type:"number",default:0},{name:"duration",label:"Duration (ms)",type:"number",default:2e3},{name:"prefix",label:"Prefix",type:"text",default:""},{name:"suffix",label:"Suffix",type:"text",default:""},{name:"decimals",label:"Decimal places",type:"number",default:0},{name:"separator",label:"Thousand separator",type:"text",default:""}],hosts:[],previewable:!0,selfClosing:!0},{name:"ripple",label:"Ripple",category:"Visual",description:"Click-triggered ripple effect on the element.",attrs:[{name:"colour",label:"Colour",type:"colour",default:"#7c6af7"},{name:"duration",label:"Duration (ms)",type:"number",default:600},{name:"opacity",label:"Opacity",type:"number",default:.3,step:.05,min:0,max:1}],hosts:["card","box","col","button"],previewable:!0},{name:"twinkle",label:"Twinkle",category:"Visual",description:"Generates animated particle effects inside the element.",attrs:[{name:"count",label:"Particle count",type:"number",default:80},{name:"shape",label:"Shape",type:"select",default:"star",options:[["star","Star"],["circle","Circle"],["square","Square"]]},{name:"colour",label:"Colour",type:"colour",default:"#ffffff"},{name:"min-size",label:"Min size (px)",type:"number",default:2},{name:"max-size",label:"Max size (px)",type:"number",default:6}],hosts:["card","box","hero"],previewable:!0},{name:"ambient",label:"Ambient background",category:"Visual",description:"Applies an animated CSS background class to the element.",attrs:[{name:"type",label:"Type",type:"select",default:"float-blobs",options:[["float-blobs","Float blobs"],["float-orbs","Float orbs"],["float-particles","Float particles"],["rotate-glow","Rotate glow"],["rotate-dual","Rotate dual"],["rotate-spotlight","Rotate spotlight"],["wave","Wave"],["ripple","Ripple"],["aurora","Aurora"]]},{name:"speed",label:"Speed",type:"select",default:"normal",options:[["slow","Slow"],["normal","Normal"],["fast","Fast"]]},{name:"intensity",label:"Intensity",type:"select",default:"subtle",options:[["subtle","Subtle"],["intense","Intense"]]}],hosts:["card","box","hero","banner"],previewable:!0},{name:"firework",label:"Firework",category:"Celebrations",description:"CSS-only firework particle. Self-closing or wrapping form.",attrs:[{name:"type",label:"Type",type:"select",default:"burst",options:[["burst","Burst"],["sparkle","Sparkle"],["trail","Trail"]]},{name:"colour",label:"Colour theme",type:"select",default:"rainbow",options:[["rainbow","Rainbow"],["primary","Primary"],["info","Info"],["success","Success"],["warning","Warning"],["danger","Danger"]]},{name:"size",label:"Size",type:"select",default:"md",options:[["sm","Small"],["md","Medium"],["lg","Large"]]},{name:"continuous",label:"Continuous",type:"checkbox",default:!1},{name:"hover",label:"On hover only",type:"checkbox",default:!1}],hosts:[],previewable:!0},{name:"fireworks",label:"Fireworks show",category:"Celebrations",description:"A container with multiple firework particles inside.",attrs:[],hosts:[],previewable:!1},{name:"celebrate",label:"Celebrate",category:"Celebrations",description:"Canvas-based seasonal particle system. Runs full-page on the published site.",attrs:[{name:"theme",label:"Theme",type:"select",default:"auto",options:[["auto","Auto (date-detected)"],["christmas","Christmas"],["halloween","Halloween"],["valentines","Valentines"],["guy-fawkes","Guy Fawkes"],["st-patricks","St Patrick's"],["st-davids","St David's"],["st-georges","St George's"],["st-andrews","St Andrew's"]]},{name:"intensity",label:"Intensity",type:"select",default:"medium",options:[["light","Light"],["medium","Medium"],["heavy","Heavy"]]},{name:"snow-only",label:"Snow only (Christmas)",type:"checkbox",default:!1,hint:"Just the falling snow - every Christmas decoration off."},{name:"trees",label:"Christmas: trees",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"wreaths",label:"Christmas: wreaths",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"northstars",label:"Christmas: North Star",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"snowmen",label:"Christmas: snowmen",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"sleigh",label:"Christmas: Santa's sleigh",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"elf",label:"Christmas: elf",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"train",label:"Christmas: train",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"firework",label:"Christmas: fireworks",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"robin",label:"Christmas: robin",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"peeing-elf",label:"Peeing elf (opt-in)",type:"checkbox",default:!1,hint:"A clothed rear-view gag. Off by default; Snow only keeps it off."},{name:"thieving-elf",label:"Thieving elf (opt-in)",type:"checkbox",default:!1,hint:"Runs to a tree, takes a present and escapes. Needs a visible tree."}],hosts:[],previewable:!1,selfClosing:!0},{name:"christmas",label:"Christmas",category:"Celebrations",description:"Christmas on the published site: snow, trees, wreaths, snowmen, Santa's sleigh and more. Snow only for just the snow.",attrs:[{name:"intensity",label:"Intensity",type:"select",default:"medium",options:[["light","Light"],["medium","Medium"],["heavy","Heavy"]]},{name:"snow-only",label:"Snow only",type:"checkbox",default:!1,hint:"Just the falling snow - every decoration off."},{name:"trees",label:"trees",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"wreaths",label:"wreaths",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"northstars",label:"North Star",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"snowmen",label:"snowmen",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"sleigh",label:"Santa's sleigh",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"elf",label:"elf",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"train",label:"train",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"firework",label:"fireworks",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"robin",label:"robin",type:"select",default:"",options:[["","Shown"],["off","Off"]]},{name:"peeing-elf",label:"Peeing elf (opt-in)",type:"checkbox",default:!1,hint:"A clothed rear-view gag. Off by default; Snow only keeps it off."},{name:"thieving-elf",label:"Thieving elf (opt-in)",type:"checkbox",default:!1,hint:"Runs to a tree, takes a present and escapes. Needs a visible tree."}],hosts:[],previewable:!1,selfClosing:!0},{name:"ticker-tape",label:"Ticker tape",category:"Celebrations",description:"Coloured rectangular strips fall from the top, sway, rotate, and fade. Inserted self-closing (full-page); hand-write the wrapping form for container-scoped.",attrs:[{name:"palette",label:"Palette",type:"select",default:"theme",options:[["theme","Theme"],["rainbow","Rainbow"],["gold","Gold"],["silver","Silver"],["festive","Festive"],["pastel","Pastel"],["mono","Mono"]]},{name:"density",label:"Density",type:"number",default:50},{name:"speed",label:"Speed multiplier",type:"number",default:1,step:.1},{name:"sway",label:"Sway (px)",type:"number",default:60},{name:"burst",label:"Burst mode (one-shot)",type:"checkbox",default:!1},{name:"burst-count",label:"Burst count",type:"number",default:150}],hosts:["hero"],previewable:!0,selfClosing:!0},{name:"butterflies",label:"Butterflies",category:"Celebrations",description:"Procedurally drawn butterflies wander and rise with flapping wings. Self-closing for full-page; wrapping form for container-scoped.",attrs:[{name:"palette",label:"Palette",type:"select",default:"meadow",options:[["meadow","Meadow"],["theme","Theme"],["rainbow","Rainbow"],["pastel","Pastel"],["festive","Festive"],["gold","Gold"],["silver","Silver"],["mono","Mono"],["sunset","Sunset"],["ocean","Ocean"],["forest","Forest"],["bridal","Bridal"],["firefly","Firefly"]]},{name:"density",label:"Density",type:"number",default:18},{name:"speed",label:"Speed multiplier",type:"number",default:1,step:.1},{name:"wander",label:"Wander",type:"number",default:1,step:.1,hint:"Steering erraticness"},{name:"rise-speed",label:"Rise speed",type:"number",default:.4,step:.1,hint:"Upward drift bias"},{name:"flap-speed",label:"Flap speed",type:"number",default:1,step:.1},{name:"min-size",label:"Min wingspan (px)",type:"number",default:14},{name:"max-size",label:"Max wingspan (px)",type:"number",default:26},{name:"two-tone",label:"Two-tone wings",type:"checkbox",default:!0},{name:"burst",label:"Burst mode (one-shot)",type:"checkbox",default:!1},{name:"burst-count",label:"Burst count",type:"number",default:40}],hosts:["hero"],previewable:!0},{name:"strobe",label:"Strobe lighting",category:"Celebrations",description:"Light beams sweep from the corners and brighten where they cross - club, concert, or searchlight moods. Self-closing for full-page; wrapping form for container-scoped.",attrs:[{name:"preset",label:"Preset",type:"select",default:"club",options:[["club","Club"],["concert","Concert"],["police","Police"],["searchlight","Searchlight"],["scanner","Scanner"],["mood","Mood"]],hint:"Other fields override the preset when changed"},{name:"motion",label:"Motion",type:"select",default:"sweep",options:[["sweep","Sweep (fan back and forth)"],["rotate","Rotate (full circle)"]]},{name:"speed",label:"Speed multiplier",type:"number",default:1,step:.1},{name:"sweep-arc",label:"Sweep arc (deg)",type:"number",default:100},{name:"beam-width",label:"Beam width (deg)",type:"number",default:16},{name:"intensity",label:"Intensity",type:"number",default:.7,step:.05,min:0,max:1},{name:"flicker",label:"Flicker",type:"checkbox",default:!0},{name:"hz",label:"Flicker rate (Hz)",type:"number",default:10,hint:"Rates above 5 flashes/sec can trigger photosensitive seizures"},{name:"colours",label:"Colours",type:"text",default:"",placeholder:"#ffffff, #38bdf8",hint:"Comma-separated hex colours - overrides the preset palette"},{name:"duration",label:"Auto-stop (ms)",type:"number",default:"",hint:"Stop after this many ms - blank runs forever"}],hosts:["hero"],previewable:!0}];export function getEffect(e){return EFFECTS.find(t=>t.name===e)||null}export function effectsByCategory(){return["Entrance","Animation","Text","Visual","Celebrations"].map(t=>({category:t,effects:EFFECTS.filter(l=>l.category===t)}))}export function effectsInjectableInto(e){return EFFECTS.filter(t=>t.hosts.includes(e))}const n=EFFECTS.find(e=>e.name==="celebrate");for(const e of n.attrs.slice(2))e.themes=["christmas"];n.attrs.push({name:"falling-only",label:"Falling particles only",type:"checkbox",default:!1});const r=new Map(n.attrs.map(e=>[e.name,e]));for(const[e,t]of Object.entries(i))if(e!=="christmas")for(const[l,o]of Object.entries(t.traits)){const s=f(l);let a=r.get(s);a||(a={name:s,label:o.label,type:"select",default:"",options:[["","Shown"],["off","Off"]],themes:[]},r.set(s,a)),a.themes.includes(e)||a.themes.push(e),a.themes.length>1&&(a.label=a.label.replace(/^Christmas: /,""))}for(const e of r.values())n.attrs.includes(e)||n.attrs.push(e);export function getEffectAttrs(e,t={}){if(e.name!=="celebrate")return e.attrs||[];const l=t.theme||"auto";return e.attrs.filter(o=>!o.themes||l==="auto"||o.themes.includes(l))}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{EFFECTS as
|
|
1
|
+
import{EFFECTS as A,effectsByCategory as V,effectsInjectableInto as R,getEffect as u,getEffectAttrs as g}from"./effect-defs.js";import{insertAtCursor as j,replaceRange as v}from"./markdown-toolbar.js";let h=null;function L(e,t,n=14){e.textContent="";const r=document.createElement("span");return r.setAttribute("data-icon",t),r.setAttribute("data-icon-size",String(n)),r.style.cssText="display:inline-flex;align-items:center;vertical-align:middle;",e.appendChild(r),I.scan(e),e}function O(e,t,n,r=14){return L(e,t,r),e.appendChild(document.createTextNode(" "+n)),e}export const EffectsBuilder={open(e){if(h)return;const t={textarea:e,activeEffect:S.get("effects-builder-last")||"reveal",scope:"standalone",host:null,editing:null,fieldValues:{}};if(t.host=D(e),t.host&&R(t.host.name).map(c=>c.name).includes(t.activeEffect)&&!(t.activeEffect in t.host.attrs)&&(t.scope="inject"),t.editing=P(e),t.editing){t.activeEffect=t.editing.name,t.scope="standalone";const i=u(t.editing.name);for(const c of g(i,t.fieldValues))if(c.name in t.editing.attrs){const s=t.editing.attrs[c.name];c.type==="number"?t.fieldValues[c.name]=Number(s):c.type==="checkbox"?t.fieldValues[c.name]=s==="true":t.fieldValues[c.name]=s}}const n=E.slideover({title:"Effects",size:"lg",position:"right",onClose:()=>{h=null}}),r=N(t,n);n.element.appendChild(r),n.open(),I.scan(r),h={state:t,slideover:n}},close(){h&&(h.slideover.close(),h=null)}};function N(e,t){const n=document.createElement("div");n.style.cssText="display:grid;grid-template-columns:170px 1fr;height:100%;min-height:520px;";const r=document.createElement("div");r.className="effects-rail",r.style.cssText="background:var(--dm-background-alt,#15152a);border-right:1px solid var(--dm-border,#2a2a40);padding:.5rem .35rem;font-size:.8rem;overflow-y:auto;";const i=document.createElement("div");i.className="effects-pane",i.style.cssText="padding:1rem;display:flex;flex-direction:column;gap:.75rem;overflow-y:auto;";function c(){K(i,e,t)}return y(r,e,c),c(),n.appendChild(r),n.appendChild(i),n}function y(e,t,n){e.replaceChildren();for(const r of V()){const i=document.createElement("div");i.style.cssText="color:var(--dm-text-muted,#888);text-transform:uppercase;font-size:.65rem;letter-spacing:.05em;margin:.4rem .35rem .25rem;",i.textContent=r.category,e.appendChild(i);for(const c of r.effects){const s=document.createElement("button");s.type="button",s.dataset.effect=c.name,s.style.cssText="display:block;width:100%;text-align:left;padding:.3rem .5rem;border:none;background:transparent;color:var(--dm-text,#ddd);border-radius:3px;font-size:.8rem;cursor:pointer;margin-bottom:.1rem;",s.textContent=c.label,c.name===t.activeEffect&&(s.style.background="var(--dm-primary,#4338ca)",s.style.color="var(--dm-primary-text,#fff)"),s.addEventListener("click",()=>{t.activeEffect=c.name,S.set("effects-builder-last",c.name),t.fieldValues={},y(e,t,n),n()}),e.appendChild(s)}}}function B(e,t){if(e.type==="select"){const r=document.createElement("select");r.style.cssText="width:100%;padding:.35rem .5rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:3px;color:var(--dm-text,#eee);font-size:.85rem;";for(const[i,c]of e.options){const s=document.createElement("option");s.value=i,s.textContent=c,r.appendChild(s)}return r.value=t??e.default??"",r}if(e.type==="checkbox"){const r=document.createElement("input");return r.type="checkbox",r.checked=t??e.default??!1,r}const n=document.createElement("input");return n.type=e.type==="number"?"number":e.type==="colour"?"color":"text",n.value=t??e.default??"",e.placeholder&&(n.placeholder=e.placeholder),e.type==="number"&&(e.min!=null&&(n.min=e.min),e.max!=null&&(n.max=e.max),e.step!=null&&(n.step=e.step)),n.style.cssText="width:100%;padding:.35rem .5rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:3px;color:var(--dm-text,#eee);font-size:.85rem;",n}function C(e,t){return t.type==="checkbox"?e.checked:t.type==="number"?e.value===""?"":Number(e.value):e.value}function x(e,t,n){const r=u(t.activeEffect);if(e.replaceChildren(),r.attrs.length===0){const c=document.createElement("div");c.style.cssText="color:var(--dm-text-muted,#888);font-size:.8rem;font-style:italic;",c.textContent="This effect takes no parameters.",e.appendChild(c);return}const i=document.createElement("div");i.style.cssText="display:grid;grid-template-columns:1fr 1fr;gap:.6rem .7rem;";for(const c of g(r,t.fieldValues)){const s=document.createElement("div"),l=document.createElement("label");l.style.cssText="display:block;color:var(--dm-text-muted,#888);font-size:.7rem;text-transform:uppercase;letter-spacing:.05em;margin-bottom:.2rem;",l.textContent=c.label,s.appendChild(l);const d=B(c,t.fieldValues[c.name]);if(d.addEventListener("input",()=>{t.fieldValues[c.name]=C(d,c),n(),c.name==="theme"&&x(e,t,n)}),d.addEventListener("change",()=>{t.fieldValues[c.name]=C(d,c),n(),c.name==="theme"&&x(e,t,n)}),s.appendChild(d),c.hint){const o=document.createElement("div");o.style.cssText="color:var(--dm-text-muted,#666);font-size:.7rem;margin-top:.15rem;",o.textContent=c.hint,s.appendChild(o)}i.appendChild(s)}e.appendChild(i)}function k(e){const t={},n=/(\w[\w-]*)(?:\s*=\s*"([^"]*)"|\s*=\s*'([^']*)'|\s*=\s*(\S+))?/g;for(const r of e.matchAll(n)){const i=r[1],c=r[2]??r[3]??r[4]??"";t[i]=c}return t}const q=A.filter(e=>!e.selfClosing).map(e=>e.name);function P(e){const t=e.value,n=e.selectionStart;for(const r of q){const i=new RegExp(`\\[${r}([^\\]]*)\\]`,"g"),c=new RegExp(`\\[/${r}\\]`,"g"),s=[];for(const o of t.matchAll(i)){if(o.index>=n)break;s.push({type:"open",start:o.index,end:o.index+o[0].length,attrStr:o[1]})}for(const o of t.matchAll(c)){if(o.index>=n)break;s.push({type:"close",start:o.index,end:o.index+o[0].length})}s.sort((o,a)=>o.start-a.start);let l=0,d=null;for(const o of s)o.type==="open"?(l++,d=o):(l--,l<0&&(l=0,d=null));if(l>0&&d){const o=new RegExp(`\\[/${r}\\]`,"g"),a=t.slice(n),m=a.match(o);if(m){const f=n+a.indexOf(m[0]);return{name:r,blockStart:d.start,blockEnd:f+m[0].length,openTagEnd:d.end,attrs:k(d.attrStr)}}}}return null}const F=["card","box","hero","banner","col","button"];function D(e){const t=e.value,n=e.selectionStart;for(const r of F){const i=new RegExp(`\\[${r}([^\\]]*)\\]`,"g"),c=new RegExp(`\\[/${r}\\]`,"g"),s=[];for(const o of t.matchAll(i)){if(o.index>=n)break;s.push({type:"open",start:o.index,end:o.index+o[0].length,attrStr:o[1]})}for(const o of t.matchAll(c)){if(o.index>=n)break;s.push({type:"close",start:o.index,end:o.index+o[0].length})}s.sort((o,a)=>o.start-a.start);let l=0,d=null;for(const o of s)o.type==="open"?(l++,d=o):(l--,l<0&&(l=0,d=null));if(l>0&&d){const o=new RegExp(`\\[/${r}\\]`,"g"),a=t.slice(n),m=a.match(o);if(m){const f=n+a.indexOf(m[0]);return{name:r,openTagStart:d.start,openTagEnd:d.end,closeTagStart:f,closeTagEnd:f+m[0].length,attrs:k(d.attrStr)}}}}return null}function T(e,t){const n=[];for(const r of g(t,e.fieldValues)){const i=e.fieldValues[r.name];i===void 0||i===""||i===null||i!==r.default&&(r.type==="checkbox"?n.push(`${r.name}="${i?"true":"false"}"`):n.push(`${r.name}="${String(i).replace(/"/g,""")}"`))}return n.join(" ")}function $(e){const t=u(e.activeEffect),n=T(e,t),r=n?" ":"";if(e.scope==="inject"&&e.host){const i=e.host.attrs,c=t.name,s=[];for(const l of Object.keys(i)){if(l===c||l.startsWith(c+"-"))continue;const d=i[l];s.push(d===""?l:`${l}="${String(d).replace(/"/g,""")}"`)}s.push(c);for(const l of g(t,e.fieldValues)){const d=e.fieldValues[l.name];if(d===void 0||d===""||d===null||d===l.default)continue;const o=l.type==="checkbox"?d?"true":"false":String(d).replace(/"/g,""");s.push(`${c}-${l.name}="${o}"`)}return`[${e.host.name} ${s.join(" ")}]\u2026[/${e.host.name}]`}return t.selfClosing?`[${t.name}${r}${n} /]`:`[${t.name}${r}${n}]
|
|
2
2
|
Content
|
|
3
|
-
[/${t.name}]`}function
|
|
3
|
+
[/${t.name}]`}function w(e,t){e.replaceChildren();const n=document.createElement("div");n.style.cssText="color:var(--dm-text-muted,#888);font-size:.7rem;text-transform:uppercase;letter-spacing:.05em;margin-bottom:.25rem;",n.textContent="Shortcode (live)",e.appendChild(n);const r=document.createElement("pre");r.style.cssText="background:var(--dm-code-bg,#000);border:1px solid var(--dm-border,#333);padding:.5rem .65rem;border-radius:3px;font-family:var(--dm-font-mono,monospace);font-size:.75rem;color:var(--dm-code-text,#7c6af7);line-height:1.5;white-space:pre-wrap;word-break:break-all;margin:0;",r.textContent=$(t),e.appendChild(r)}function H(e,t,n){if(e.replaceChildren(),t.editing)return;const r=u(t.activeEffect),i=t.host&&r.hosts.includes(t.host.name),c=document.createElement("div");c.style.cssText="background:var(--dm-background-alt,#15152a);border:1px solid var(--dm-border,#2a2a40);border-radius:4px;padding:.5rem .6rem;";const s=document.createElement("div");if(s.style.cssText="color:var(--dm-text-muted,#888);text-transform:uppercase;font-size:.65rem;letter-spacing:.05em;margin-bottom:.3rem;",s.textContent="Apply as",c.appendChild(s),!i){const o=document.createElement("div");o.style.cssText="color:var(--dm-text-muted,#888);font-size:.75rem;font-style:italic;",t.host?o.textContent=`Inserted as a standalone block (${r.label} cannot be injected into [${t.host.name}]).`:o.textContent="Inserted as a standalone block.",c.appendChild(o),e.appendChild(c),t.scope="standalone";return}const l=document.createElement("div");l.style.cssText="display:flex;gap:.3rem;";function d(o,a){const m=document.createElement("button");return m.type="button",m.style.cssText="flex:1;padding:.35rem;border:1px solid var(--dm-border,#333);border-radius:3px;font-size:.75rem;font-weight:600;cursor:pointer;",t.scope===o?(m.style.background="var(--dm-primary,#4338ca)",m.style.color="var(--dm-primary-text,#fff)",m.style.borderColor="var(--dm-primary,#4338ca)"):(m.style.background="transparent",m.style.color="var(--dm-text-muted,#bbb)"),m.textContent=a,m.addEventListener("click",()=>{t.scope=o,n()}),m}l.appendChild(d("standalone","Standalone block")),l.appendChild(d("inject",`Inject into [${t.host.name}]`)),c.appendChild(l),e.appendChild(c)}let b=0;function _(e){return e.replace(/-([a-z])/g,(t,n)=>n.toUpperCase())}function z(e,t){e.replaceChildren();const n=u(t.activeEffect),r=document.createElement("div");r.style.cssText="display:flex;justify-content:space-between;align-items:center;margin-bottom:.25rem;";const i=document.createElement("div");if(i.style.cssText="color:var(--dm-text-muted,#888);text-transform:uppercase;font-size:.65rem;letter-spacing:.05em;",i.textContent="Live preview",r.appendChild(i),!n.previewable){const d=document.createElement("div");d.style.cssText="background:var(--dm-background-alt,#15152a);border:1px solid var(--dm-border,#2a2a40);border-radius:4px;padding:.6rem;color:var(--dm-text-muted,#888);font-size:.8rem;font-style:italic;text-align:center;",d.textContent="Runs full-page on the published site - no inline preview.",e.appendChild(d);return}const c=document.createElement("button");c.type="button",c.style.cssText="background:transparent;border:1px solid var(--dm-border,#333);color:var(--dm-text-muted,#888);font-size:.65rem;padding:.15rem .4rem;border-radius:2px;cursor:pointer;",O(c,"refresh-cw","Replay",12),r.appendChild(c),e.appendChild(r);const s=document.createElement("div");s.style.cssText="background:var(--dm-background-alt,#1a1a2e);border:1px solid var(--dm-border,#2a2a40);border-radius:4px;padding:1rem;text-align:center;min-height:80px;position:relative;overflow:hidden;",e.appendChild(s);function l(){b++;const d=b;s.replaceChildren();const o=document.createElement("div");o.style.cssText="background:linear-gradient(135deg,#6366f1,#a855f7);color:#fff;padding:.6rem;border-radius:4px;font-size:.8rem;font-weight:600;",o.textContent="Sample content",s.appendChild(o);const a=window.Domma&&window.Domma.effects;if(!a)return;const m={};for(const f of g(n,t.fieldValues)){const p=t.fieldValues[f.name];p===void 0||p===""||p===null||(m[_(f.name)]=p)}try{const f=a[_(n.name)];if(typeof f=="function"){if(d!==b)return;f(o,m)}}catch(f){console.warn("Effects preview failed:",f)}}c.addEventListener("click",l),l()}function K(e,t,n){const r=u(t.activeEffect);e.replaceChildren();const i=document.createElement("h3");i.style.cssText="margin:0;font-size:1rem;color:var(--dm-text,#eee);",i.textContent=r.label,e.appendChild(i);const c=document.createElement("p");c.style.cssText="margin:0;color:var(--dm-text-muted,#999);font-size:.8rem;line-height:1.4;",c.textContent=r.description,e.appendChild(c);const s=document.createElement("div");e.appendChild(s);const l=document.createElement("div");e.appendChild(l);const d=document.createElement("div");e.appendChild(d);const o=document.createElement("div");e.appendChild(o);function a(){H(s,t,a),x(l,t,()=>{w(d,t),z(o,t)}),w(d,t),z(o,t)}a();const m=document.createElement("div");m.style.cssText="display:flex;gap:.5rem;margin-top:auto;padding-top:.75rem;border-top:1px solid var(--dm-border,#2a2a40);";const f=document.createElement("button");f.type="button",f.className="btn btn-ghost",f.textContent="Cancel",f.addEventListener("click",()=>n.close()),m.appendChild(f);const p=document.createElement("button");p.type="button",p.className="btn btn-primary",p.style.flex="2",p.textContent=t.editing?"Update":"Insert effect",p.addEventListener("click",()=>M(t,n)),m.appendChild(p),e.appendChild(m)}function M(e,t){const n=e.textarea,r=$(e);if(e.editing){const i=u(e.editing.name),c=n.value,s=`[/${i.name}]`.length,l=e.editing.openTagEnd,d=e.editing.blockEnd-s,o=c.substring(l,d),a=T(e,i),m=a?" ":"",f=i.selfClosing?`[${i.name}${m}${a} /]`:`[${i.name}${m}${a}]${o}[/${i.name}]`;v(n,e.editing.blockStart,e.editing.blockEnd,f);const p=i.selfClosing?e.editing.blockStart+f.length:e.editing.blockStart+f.indexOf("]")+1;n.setSelectionRange(p,p),t.close();return}if(e.scope==="inject"&&e.host){const i=u(e.activeEffect),c=e.host.attrs,s=i.name,l=[];for(const o of Object.keys(c)){if(o===s||o.startsWith(s+"-"))continue;const a=c[o];l.push(a===""?o:`${o}="${String(a).replace(/"/g,""")}"`)}l.push(s);for(const o of g(i,e.fieldValues)){const a=e.fieldValues[o.name];if(a===void 0||a===""||a===null||a===o.default)continue;const m=o.type==="checkbox"?a?"true":"false":String(a).replace(/"/g,""");l.push(`${s}-${o.name}="${m}"`)}const d=`[${e.host.name} ${l.join(" ")}]`;v(n,e.host.openTagStart,e.host.openTagEnd,d),t.close();return}j(n,r),t.close()}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{SHORTCODES as $}from"./shortcode-registry.js";import{EFFECTS as C}from"./effect-defs.js";import{boolState as V,boolValue as G,findEnclosingShortcode as M,setAttr as N,setAttrs as X,effectAttrKeys as q}from"./shortcode-tags.js";import{insertAtCursor as J}from"./markdown-toolbar.js";import{clearLookupCache as Q,lookupOptions as Z}from"./shortcode-lookups.js";import{insertMenuGroups as ee,insertShortcodeByName as te}from"./shortcode-insert.js";import{openShortcodeForm as R}from"./shortcode-form.js";function ne(e){return Z(e,c?.span?.attrs||{})}const oe=$.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),se=new Map($.map(e=>[e.name,e])),ie=new Map(C.map(e=>[e.name,e])),ce=C.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),z=[...oe,...ce],D=new Set(["card","box","hero","banner","col","button"]);let c=null;const P="position:fixed;z-index:10000;min-width:210px;max-height:70vh;overflow-y:auto;background:var(--dm-surface,#1c1c30);border:1px solid var(--dm-border,#2a2a40);border-radius:6px;box-shadow:var(--dm-shadow-lg);padding:.3rem;font-size:.83rem;color:var(--dm-text,#e5e5e5);",p="display:flex;align-items:center;gap:.5rem;width:100%;text-align:left;padding:.35rem .55rem;border:none;background:transparent;color:inherit;border-radius:4px;cursor:pointer;font-size:.83rem;",S="padding:.3rem .55rem;color:var(--dm-text-muted,#8a8a9a);font-size:.68rem;text-transform:uppercase;letter-spacing:.06em;",H="flex:1;min-width:0;padding:.2rem .4rem;background:var(--dm-input-bg,#12121e);border:1px solid var(--dm-border,#333);border-radius:3px;color:var(--dm-text,#eee);font-size:.8rem;",ae="height:1px;background:var(--dm-border,#2a2a40);margin:.3rem .2rem;";function m(e){return e.addEventListener("mouseenter",()=>{e.style.background="var(--dm-primary,#4338ca)",e.style.color="var(--dm-primary-text,#fff)"}),e.addEventListener("mouseleave",()=>{e.style.background="transparent",e.style.color="inherit"}),e}export function attachContextMenu(e){return typeof Domma>"u"||!Domma.elements?.contextMenu?(e.addEventListener("contextmenu",n=>{n.preventDefault(),f();const t=M(e,z);F(e,t,n.clientX,n.clientY)}),null):Domma.elements.contextMenu(e,{inherit:!1,cursor:!1,render:n=>{f();const t=M(e,z);F(e,t,n.x,n.y)}})}function f(){c&&(v(),c.root.remove(),document.removeEventListener("mousedown",W,!0),document.removeEventListener("keydown",j,!0),window.removeEventListener("scroll",Y,!0),c=null)}const W=e=>{c&&!c.root.contains(e.target)&&!(c.submenu&&c.submenu.contains(e.target))&&f()},Y=e=>{c&&!c.root.contains(e.target)&&!(c.submenu&&c.submenu.contains(e.target))&&f()},j=e=>{e.key==="Escape"&&f()};function F(e,n,t,o){Q();const s=document.createElement("div");s.className="dm-sc-menu",s.style.cssText=P,c={root:s,textarea:e,span:n,submenu:null};const i=n&&(se.get(n.name)||ie.get(n.name)),l=n&&D.has(n.name)?C.filter(r=>r.name in n.attrs):[];i&&re(s,i);for(const r of l)xe(s,r);(i||l.length)&&s.appendChild(le()),s.appendChild(y("Add effect\u2026",()=>be())),s.appendChild(y("Insert\u2026",()=>ye()));const a=e.selectionStart!==e.selectionEnd;(n||a)&&s.appendChild(y(a?"Wrap selection in\u2026":"Wrap in\u2026",()=>Ee())),document.body.appendChild(s),U(s,t,o),document.addEventListener("mousedown",W,!0),document.addEventListener("keydown",j,!0),window.addEventListener("scroll",Y,!0),window.I&&I.scan(s)}function le(){const e=document.createElement("div");return e.style.cssText=ae,e}function k(e){const n=document.createElement("div");return n.style.cssText=S,n.textContent=e,n}function re(e,n){e.appendChild(k(`${n.label} options`));const t=m(document.createElement("button"));t.type="button",t.style.cssText=p,t.innerHTML='<span data-icon="edit" data-icon-size="13" style="display:inline-flex;align-items:center;"></span>',t.appendChild(document.createTextNode(`Edit ${n.label}\u2026`)),t.addEventListener("click",()=>{const{textarea:o,span:s}=c;f(),R(o,n,{span:s})}),e.appendChild(t);for(const o of n.attrs||[])e.appendChild(O(o))}function O(e,n=e.name){const t=c.span.attrs[n];switch(e.type){case"checkbox":return de(e,n,V(t,e.default));case"select":return me(e,n,t);case"lookup":return he(e,n,t);case"colour":return pe(e,n,t);case"icon":return fe(e,n,t);default:return ue(e,n,t)}}function b(e,n){c.span=N(c.textarea,c.span,e,n)}function de(e,n,t){const o=m(document.createElement("label"));o.style.cssText=p;const s=document.createElement("input");return s.type="checkbox",s.checked=t,s.addEventListener("change",()=>b(n,G(e,s.checked))),o.appendChild(s),o.appendChild(document.createTextNode(e.label)),o}function ue(e,n,t){const o=document.createElement("div");o.style.cssText=p+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.cssText="white-space:nowrap;color:var(--dm-text-muted,#9a9aac);";const i=document.createElement("input");i.type=e.type==="number"?"number":"text",i.value=t===!0||t==null?"":t,e.default!=null&&e.default!==""&&(i.placeholder=String(e.default)),i.style.cssText=H;let l=null;return i.addEventListener("input",()=>{clearTimeout(l),l=setTimeout(()=>b(n,i.value.trim()),180)}),i.addEventListener("mousedown",a=>a.stopPropagation()),o.appendChild(s),o.appendChild(i),o}function pe(e,n,t){const o=document.createElement("div");o.style.cssText=p+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.flex="1";const i=document.createElement("input");return i.type="color",typeof t=="string"&&/^#/.test(t)?i.value=t:typeof e.default=="string"&&/^#/.test(e.default)&&(i.value=e.default),i.addEventListener("input",()=>b(n,i.value)),o.appendChild(s),o.appendChild(i),o}function me(e,n,t){const o=t==null||t===!0?e.default??"":t;return y(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=g();for(const[i,l]of e.options||[]){const a=m(document.createElement("button"));a.type="button",a.style.cssText=p,a.textContent=l,i===o&&(a.style.fontWeight="700"),a.addEventListener("click",()=>{b(n,i),v()}),s.appendChild(a)}return s})}function fe(e,n,t){const o=t==null||t===!0?"":String(t),s=o?`${e.label}: ${o}`:e.label;return y(s,()=>{const i=g();i.style.width="232px";const l=document.createElement("input");l.type="text",l.placeholder="Search icons\u2026",l.style.cssText=H+"margin:6px;width:calc(100% - 12px);",l.addEventListener("mousedown",T=>T.stopPropagation()),i.appendChild(l);const a=m(document.createElement("button"));a.type="button",a.style.cssText=p,a.textContent="- none -",o||(a.style.fontWeight="700"),a.addEventListener("click",()=>{b(n,null),v()}),i.appendChild(a);const r=document.createElement("div");r.style.cssText="display:flex;flex-wrap:wrap;gap:2px;padding:4px 6px;max-height:220px;overflow-y:auto;",i.appendChild(r);const u=96,w=()=>{const T=typeof I<"u"&&typeof I.list=="function"?I.list():[],A=l.value.trim().toLowerCase(),_=A?T.filter(d=>d.toLowerCase().includes(A)):T;r.replaceChildren();for(const d of _.slice(0,u)){const x=m(document.createElement("button"));x.type="button",x.title=d,x.style.cssText="width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;background:none;border:1px solid transparent;border-radius:4px;cursor:pointer;color:inherit;",d===o&&(x.style.borderColor="var(--dm-primary,#5b8cff)");const L=document.createElement("span");L.setAttribute("data-icon",d),L.setAttribute("data-icon-size","15"),L.style.cssText="display:inline-flex;align-items:center;",x.appendChild(L),x.addEventListener("click",()=>{b(n,d),v()}),r.appendChild(x)}if(_.length>u){const d=document.createElement("div");d.style.cssText=S,d.textContent=`${_.length-u} more - refine search`,r.appendChild(d)}if(!_.length){const d=document.createElement("div");d.style.cssText=S,d.textContent="No icons match",r.appendChild(d)}window.I&&I.scan(r)};let h=null;return l.addEventListener("input",()=>{clearTimeout(h),h=setTimeout(w,120)}),w(),setTimeout(()=>l.focus(),0),i})}function he(e,n,t){const o=t==null||t===!0?e.default??"":t;return y(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=g();s.style.minHeight="48px";const i=typeof E<"u"&&E.loader?E.loader(s,{type:"dots"}):null;return ne(e.source).then(l=>{if(i&&i.destroy(),!c||c.submenu!==s)return;if(s.style.minHeight="",!l.length){const u=document.createElement("div");u.style.cssText=S,u.textContent="Nothing found",s.appendChild(u)}const a=l.some(([u])=>u===o),r=[["","- none -"],...l];o!==""&&!a&&r.push([o,`${o} (missing)`]);for(const[u,w]of r){const h=m(document.createElement("button"));h.type="button",h.style.cssText=p,h.textContent=w,u===o&&(h.style.fontWeight="700"),h.addEventListener("click",()=>{b(n,u===""?null:u),v()}),s.appendChild(h)}}),s})}function xe(e,n){e.appendChild(k(`Effect: ${n.label}`));for(const o of n.attrs||[])e.appendChild(O(o,`${n.name}-${o.name}`));const t=m(document.createElement("button"));t.type="button",t.style.cssText=p,t.innerHTML='<span data-icon="trash" data-icon-size="13" style="display:inline-flex;align-items:center;"></span> Remove effect',t.addEventListener("click",()=>{const o={};for(const s of q(c.span.attrs,n.name))o[s]=null;X(c.textarea,c.span,o),f()}),e.appendChild(t)}function be(){const e=g(),n=c.span,t=n&&D.has(n.name)?n.name:null;for(const o of[...new Set(C.map(s=>s.category))]){const s=C.filter(i=>i.category===o);if(s.length){e.appendChild(k(o));for(const i of s){const l=t&&(i.hosts||[]).includes(t)&&!(i.name in n.attrs),a=m(document.createElement("button"));if(a.type="button",a.style.cssText=p+"justify-content:space-between;",a.appendChild(document.createTextNode(i.label)),l){const r=document.createElement("span");r.textContent=`inject \u2192 [${t}]`,r.style.cssText="opacity:.6;font-size:.7rem;",a.appendChild(r)}a.addEventListener("click",()=>{if(l)N(c.textarea,c.span,i.name,!0);else{const r=i.selfClosing?`[${i.name} /]`:`[${i.name}]
|
|
1
|
+
import{SHORTCODES as $}from"./shortcode-registry.js";import{EFFECTS as w,CELEBRATION_THEMES as J,getEffectAttrs as R}from"./effect-defs.js";import{boolState as Q,boolValue as Z,findEnclosingShortcode as z,setAttr as P,setAttrs as ee,effectAttrKeys as te}from"./shortcode-tags.js";import{insertAtCursor as D}from"./markdown-toolbar.js";import{clearLookupCache as ne,lookupOptions as oe}from"./shortcode-lookups.js";import{insertMenuGroups as se,insertShortcodeByName as ie}from"./shortcode-insert.js";import{openShortcodeForm as A}from"./shortcode-form.js";function ce(e){return oe(e,c?.span?.attrs||{})}const ae=$.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),le=new Map($.map(e=>[e.name,e])),H=new Map(w.map(e=>[e.name,e])),re=w.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),O=[...ae,...re],j=new Set(["card","box","hero","banner","col","button"]);let c=null;const W="position:fixed;z-index:10000;min-width:210px;max-height:70vh;overflow-y:auto;background:var(--dm-surface,#1c1c30);border:1px solid var(--dm-border,#2a2a40);border-radius:6px;box-shadow:var(--dm-shadow-lg);padding:.3rem;font-size:.83rem;color:var(--dm-text,#e5e5e5);",p="display:flex;align-items:center;gap:.5rem;width:100%;text-align:left;padding:.35rem .55rem;border:none;background:transparent;color:inherit;border-radius:4px;cursor:pointer;font-size:.83rem;",k="padding:.3rem .55rem;color:var(--dm-text-muted,#8a8a9a);font-size:.68rem;text-transform:uppercase;letter-spacing:.06em;",Y="flex:1;min-width:0;padding:.2rem .4rem;background:var(--dm-input-bg,#12121e);border:1px solid var(--dm-border,#333);border-radius:3px;color:var(--dm-text,#eee);font-size:.8rem;",de="height:1px;background:var(--dm-border,#2a2a40);margin:.3rem .2rem;";function f(e){return e.addEventListener("mouseenter",()=>{e.style.background="var(--dm-primary,#4338ca)",e.style.color="var(--dm-primary-text,#fff)"}),e.addEventListener("mouseleave",()=>{e.style.background="transparent",e.style.color="inherit"}),e}export function attachContextMenu(e){return typeof Domma>"u"||!Domma.elements?.contextMenu?(e.addEventListener("contextmenu",n=>{n.preventDefault(),m();const t=z(e,O);M(e,t,n.clientX,n.clientY)}),null):Domma.elements.contextMenu(e,{inherit:!1,cursor:!1,render:n=>{m();const t=z(e,O);M(e,t,n.x,n.y)}})}function m(){c&&(v(),c.root.remove(),document.removeEventListener("mousedown",B,!0),document.removeEventListener("keydown",K,!0),window.removeEventListener("scroll",F,!0),c=null)}const B=e=>{c&&!c.root.contains(e.target)&&!(c.submenu&&c.submenu.contains(e.target))&&m()},F=e=>{c&&!c.root.contains(e.target)&&!(c.submenu&&c.submenu.contains(e.target))&&m()},K=e=>{e.key==="Escape"&&m()};function M(e,n,t,o){ne();const s=document.createElement("div");s.className="dm-sc-menu",s.style.cssText=W,c={root:s,textarea:e,span:n,submenu:null};const i=n&&(le.get(n.name)||H.get(n.name)),l=n&&j.has(n.name)?w.filter(r=>r.name in n.attrs):[];i&&ue(s,i);for(const r of l)ye(s,r);(i||l.length)&&s.appendChild(U()),s.appendChild(y("Celebrations\u2026",()=>Ee())),s.appendChild(y("Add effect\u2026",()=>ge())),s.appendChild(y("Insert\u2026",()=>Ce()));const a=e.selectionStart!==e.selectionEnd;(n||a)&&s.appendChild(y(a?"Wrap selection in\u2026":"Wrap in\u2026",()=>ve())),document.body.appendChild(s),q(s,t,o),document.addEventListener("mousedown",B,!0),document.addEventListener("keydown",K,!0),window.addEventListener("scroll",F,!0),window.I&&I.scan(s)}function U(){const e=document.createElement("div");return e.style.cssText=de,e}function C(e){const n=document.createElement("div");return n.style.cssText=k,n.textContent=e,n}function ue(e,n){e.appendChild(C(`${n.label} options`));const t=f(document.createElement("button"));t.type="button",t.style.cssText=p,t.innerHTML='<span data-icon="edit" data-icon-size="13" style="display:inline-flex;align-items:center;"></span>',t.appendChild(document.createTextNode(`Edit ${n.label}\u2026`)),t.addEventListener("click",()=>{const{textarea:o,span:s}=c;m(),A(o,{...n,attrs:R(n,s.attrs)},{span:s})}),e.appendChild(t);for(const o of R(n,c.span.attrs))e.appendChild(V(o))}function V(e,n=e.name){const t=c.span.attrs[n];switch(e.type){case"checkbox":return pe(e,n,Q(t,e.default));case"select":return he(e,n,t);case"lookup":return xe(e,n,t);case"colour":return fe(e,n,t);case"icon":return be(e,n,t);default:return me(e,n,t)}}function x(e,n){if(c.span=P(c.textarea,c.span,e,n),e==="theme"){const{textarea:t,span:o,root:s}=c,i=s.getBoundingClientRect();m(),M(t,o,i.left,i.top)}}function pe(e,n,t){const o=f(document.createElement("label"));o.style.cssText=p;const s=document.createElement("input");return s.type="checkbox",s.checked=t,s.addEventListener("change",()=>x(n,Z(e,s.checked))),o.appendChild(s),o.appendChild(document.createTextNode(e.label)),o}function me(e,n,t){const o=document.createElement("div");o.style.cssText=p+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.cssText="white-space:nowrap;color:var(--dm-text-muted,#9a9aac);";const i=document.createElement("input");i.type=e.type==="number"?"number":"text",i.value=t===!0||t==null?"":t,e.default!=null&&e.default!==""&&(i.placeholder=String(e.default)),i.style.cssText=Y;let l=null;return i.addEventListener("input",()=>{clearTimeout(l),l=setTimeout(()=>x(n,i.value.trim()),180)}),i.addEventListener("mousedown",a=>a.stopPropagation()),o.appendChild(s),o.appendChild(i),o}function fe(e,n,t){const o=document.createElement("div");o.style.cssText=p+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.flex="1";const i=document.createElement("input");return i.type="color",typeof t=="string"&&/^#/.test(t)?i.value=t:typeof e.default=="string"&&/^#/.test(e.default)&&(i.value=e.default),i.addEventListener("input",()=>x(n,i.value)),o.appendChild(s),o.appendChild(i),o}function he(e,n,t){const o=t==null||t===!0?e.default??"":t;return y(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=g();for(const[i,l]of e.options||[]){const a=f(document.createElement("button"));a.type="button",a.style.cssText=p,a.textContent=l,i===o&&(a.style.fontWeight="700"),a.addEventListener("click",()=>{x(n,i),v()}),s.appendChild(a)}return s})}function be(e,n,t){const o=t==null||t===!0?"":String(t),s=o?`${e.label}: ${o}`:e.label;return y(s,()=>{const i=g();i.style.width="232px";const l=document.createElement("input");l.type="text",l.placeholder="Search icons\u2026",l.style.cssText=Y+"margin:6px;width:calc(100% - 12px);",l.addEventListener("mousedown",L=>L.stopPropagation()),i.appendChild(l);const a=f(document.createElement("button"));a.type="button",a.style.cssText=p,a.textContent="- none -",o||(a.style.fontWeight="700"),a.addEventListener("click",()=>{x(n,null),v()}),i.appendChild(a);const r=document.createElement("div");r.style.cssText="display:flex;flex-wrap:wrap;gap:2px;padding:4px 6px;max-height:220px;overflow-y:auto;",i.appendChild(r);const u=96,T=()=>{const L=typeof I<"u"&&typeof I.list=="function"?I.list():[],N=l.value.trim().toLowerCase(),_=N?L.filter(d=>d.toLowerCase().includes(N)):L;r.replaceChildren();for(const d of _.slice(0,u)){const b=f(document.createElement("button"));b.type="button",b.title=d,b.style.cssText="width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;background:none;border:1px solid transparent;border-radius:4px;cursor:pointer;color:inherit;",d===o&&(b.style.borderColor="var(--dm-primary,#5b8cff)");const S=document.createElement("span");S.setAttribute("data-icon",d),S.setAttribute("data-icon-size","15"),S.style.cssText="display:inline-flex;align-items:center;",b.appendChild(S),b.addEventListener("click",()=>{x(n,d),v()}),r.appendChild(b)}if(_.length>u){const d=document.createElement("div");d.style.cssText=k,d.textContent=`${_.length-u} more - refine search`,r.appendChild(d)}if(!_.length){const d=document.createElement("div");d.style.cssText=k,d.textContent="No icons match",r.appendChild(d)}window.I&&I.scan(r)};let h=null;return l.addEventListener("input",()=>{clearTimeout(h),h=setTimeout(T,120)}),T(),setTimeout(()=>l.focus(),0),i})}function xe(e,n,t){const o=t==null||t===!0?e.default??"":t;return y(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=g();s.style.minHeight="48px";const i=typeof E<"u"&&E.loader?E.loader(s,{type:"dots"}):null;return ce(e.source).then(l=>{if(i&&i.destroy(),!c||c.submenu!==s)return;if(s.style.minHeight="",!l.length){const u=document.createElement("div");u.style.cssText=k,u.textContent="Nothing found",s.appendChild(u)}const a=l.some(([u])=>u===o),r=[["","- none -"],...l];o!==""&&!a&&r.push([o,`${o} (missing)`]);for(const[u,T]of r){const h=f(document.createElement("button"));h.type="button",h.style.cssText=p,h.textContent=T,u===o&&(h.style.fontWeight="700"),h.addEventListener("click",()=>{x(n,u===""?null:u),v()}),s.appendChild(h)}}),s})}function ye(e,n){e.appendChild(C(`Effect: ${n.label}`));for(const o of n.attrs||[])e.appendChild(V(o,`${n.name}-${o.name}`));const t=f(document.createElement("button"));t.type="button",t.style.cssText=p,t.innerHTML='<span data-icon="trash" data-icon-size="13" style="display:inline-flex;align-items:center;"></span> Remove effect',t.addEventListener("click",()=>{const o={};for(const s of te(c.span.attrs,n.name))o[s]=null;ee(c.textarea,c.span,o),m()}),e.appendChild(t)}function Ee(){const e=g();e.appendChild(C("Seasonal themes"));for(const[n,t]of[["auto","Auto (date-detected)"],...Object.entries(J).map(([o,s])=>[o,s.label])]){const o=f(document.createElement("button"));o.type="button",o.style.cssText=p,o.textContent=t,o.addEventListener("click",()=>{D(c.textarea,`[celebrate theme="${n}" /]`),m()}),e.appendChild(o)}e.appendChild(U()),e.appendChild(C("Celebration effects"));for(const n of["butterflies","ticker-tape","firework","fireworks"]){const t=H.get(n),o=f(document.createElement("button"));o.type="button",o.style.cssText=p,o.textContent=t.label,o.addEventListener("click",()=>{const s=c.textarea;m(),A(s,t)}),e.appendChild(o)}return e}function ge(){const e=g(),n=c.span,t=n&&j.has(n.name)?n.name:null;for(const o of[...new Set(w.map(s=>s.category))]){const s=w.filter(i=>i.category===o);if(s.length){e.appendChild(C(o));for(const i of s){const l=t&&(i.hosts||[]).includes(t)&&!(i.name in n.attrs),a=f(document.createElement("button"));if(a.type="button",a.style.cssText=p+"justify-content:space-between;",a.appendChild(document.createTextNode(i.label)),l){const r=document.createElement("span");r.textContent=`inject \u2192 [${t}]`,r.style.cssText="opacity:.6;font-size:.7rem;",a.appendChild(r)}a.addEventListener("click",()=>{if(l)P(c.textarea,c.span,i.name,!0);else{const r=i.selfClosing?`[${i.name} /]`:`[${i.name}]
|
|
2
2
|
Content
|
|
3
|
-
[/${i.name}]`;
|
|
3
|
+
[/${i.name}]`;D(c.textarea,r)}m()}),e.appendChild(a)}}}return e}function y(e,n){const t=f(document.createElement("button"));t.type="button",t.style.cssText=p+"justify-content:space-between;",t.appendChild(document.createTextNode(e));const o=document.createElement("span");return o.setAttribute("data-icon","chevron-right"),o.setAttribute("data-icon-size","12"),o.style.cssText="opacity:.6;display:inline-flex;align-items:center;flex-shrink:0;",t.appendChild(o),t.addEventListener("mouseenter",()=>G(t,n)),t.addEventListener("click",s=>{s.stopPropagation(),G(t,n)}),t}function g(){const e=document.createElement("div");return e.className="dm-sc-submenu",e.style.cssText=W,e}function G(e,n){v();const t=n();c.submenu=t,document.body.appendChild(t);const o=e.getBoundingClientRect();q(t,o.right+2,o.top,o.left-2),window.I&&I.scan(t)}function v(){c&&c.submenu&&(c.submenu.remove(),c.submenu=null)}function X(e){const n=f(document.createElement("button"));if(n.type="button",n.style.cssText=p,e.icon){const t=document.createElement("span");t.setAttribute("data-icon",e.icon),t.setAttribute("data-icon-size","14"),t.style.cssText="opacity:.75;display:inline-flex;align-items:center;flex-shrink:0;",n.appendChild(t)}return n.appendChild(document.createTextNode(e.label)),n}function Ce(){const e=g();for(const{category:n,items:t}of se()){e.appendChild(C(n));for(const o of t){const s=X(o);s.addEventListener("click",()=>{const i=c.textarea;m(),ie(o.name,i)}),e.appendChild(s)}}return e}function ve(){const e=g();for(const n of $.filter(t=>t.wrappable||t.container)){const t=X(n);t.addEventListener("click",()=>{const o=c.textarea;m(),A(o,n)}),e.appendChild(t)}return e}function q(e,n,t,o){e.style.left="0px",e.style.top="0px";const s=e.getBoundingClientRect();let i=n;n+s.width>window.innerWidth-8&&(i=o!=null?o-s.width:n-s.width);let l=t;t+s.height>window.innerHeight-8&&(l=Math.max(8,window.innerHeight-s.height-8)),e.style.left=`${Math.max(8,i)}px`,e.style.top=`${Math.max(8,l)}px`}
|
|
@@ -48,6 +48,13 @@
|
|
|
48
48
|
you let them in. <strong>Copy link</strong> gives you a link to pass on yourself. The same
|
|
49
49
|
button is in online bookings' calls (Booking Pro) and online meetings (Calendar); in a
|
|
50
50
|
Messages call it rings in another team member.</p>
|
|
51
|
+
<p><strong>Blur your background</strong>: click <strong>Blur</strong> on the join screen or in the
|
|
52
|
+
call's control bar, and everyone sees you sharp against a blurred room. It works out what is
|
|
53
|
+
you inside your own browser - no picture is sent anywhere to do it, and nothing goes to Google
|
|
54
|
+
or anyone else. The first time takes a few seconds while it loads; after that your browser
|
|
55
|
+
remembers the choice for your next call. It needs a reasonably recent computer: if yours
|
|
56
|
+
cannot keep up, blur turns itself off and says so. Everyone in any call has it - your guests
|
|
57
|
+
too.</p>
|
|
51
58
|
<p>Who may start a call is the <strong>Calls</strong> permission under <strong>System >
|
|
52
59
|
Roles</strong>; Admin is given it once, the first time a Tool with calls runs. A site without
|
|
53
60
|
any of those Tools has no instant calls at all.</p>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domma-cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.108.0",
|
|
4
4
|
"description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server/server.js",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"@fastify/rate-limit": "^10.3.0",
|
|
85
85
|
"@fastify/static": "9.1.1",
|
|
86
86
|
"bcryptjs": "^3.0.3",
|
|
87
|
-
"domma-js": "0.51.
|
|
87
|
+
"domma-js": "0.51.5",
|
|
88
88
|
"dotenv": "^17.2.3",
|
|
89
89
|
"fastify": "5.8.5",
|
|
90
90
|
"gray-matter": "^4.0.3",
|
|
@@ -99,7 +99,20 @@ function sanitiseBody(html, options) {
|
|
|
99
99
|
'border': [/^.*$/],
|
|
100
100
|
'border-collapse': [/^.*$/],
|
|
101
101
|
'width': [/^.*$/],
|
|
102
|
-
'max-width': [/^.*$/]
|
|
102
|
+
'max-width': [/^.*$/],
|
|
103
|
+
// Marketing mail hides its inbox preview text ("preheader")
|
|
104
|
+
// with these. Strip them and keep its width:0, and the hidden
|
|
105
|
+
// text renders one letter per line under a tall run of
|
|
106
|
+
// invisible padding characters. None of them can position,
|
|
107
|
+
// load or overlay anything.
|
|
108
|
+
'display': [/^(none|block|inline|inline-block|table|table-row|table-cell|list-item)$/i],
|
|
109
|
+
'visibility': [/^(hidden|visible)$/i],
|
|
110
|
+
'height': [/^.*$/],
|
|
111
|
+
'max-height': [/^.*$/],
|
|
112
|
+
'line-height': [/^.*$/],
|
|
113
|
+
'overflow': [/^(hidden|visible)$/i],
|
|
114
|
+
'opacity': [/^[0-9.]+$/],
|
|
115
|
+
'vertical-align': [/^.*$/]
|
|
103
116
|
}
|
|
104
117
|
},
|
|
105
118
|
allowedSchemes: ['http', 'https', 'mailto', 'tel'],
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The beat for background blur (blur.js): a Worker, because a background tab slows the page's own timers to once
|
|
3
|
+
* a second and the others in the call would see a frozen picture. Post it a delay in ms; it answers once, then.
|
|
4
|
+
*/
|
|
5
|
+
self.onmessage = (e) => {
|
|
6
|
+
setTimeout(() => self.postMessage(0), Math.max(0, Number(e.data) || 0));
|
|
7
|
+
};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background blur for the call page - the camera's picture with everything but the person blurred.
|
|
3
|
+
*
|
|
4
|
+
* Runs entirely in this browser: MediaPipe's selfie segmenter (vendored under ./vendor/mediapipe, Apache 2.0,
|
|
5
|
+
* its usage logging patched out) marks which pixels are the person, and a canvas draws the frame blurred with the
|
|
6
|
+
* person sharp on top. The canvas becomes a video track (captureStream) that call.js sends instead of the
|
|
7
|
+
* camera's. No frame leaves the browser except as the call's own video, and nothing is fetched from anyone but
|
|
8
|
+
* this site.
|
|
9
|
+
*
|
|
10
|
+
* Loaded on demand (import()) the first time blur is turned on - about 12 MB, compressed in transit - so a call
|
|
11
|
+
* that never blurs costs nothing.
|
|
12
|
+
*
|
|
13
|
+
* const blur = await startBlur(cameraTrack); // throws when this browser cannot run it (or not within 20 s)
|
|
14
|
+
* blur.track // the blurred track to send and show
|
|
15
|
+
* blur.paused = true // skip the work (a screen is being shared)
|
|
16
|
+
* blur.stop()
|
|
17
|
+
*
|
|
18
|
+
* blur.track ends by itself - an 'ended' event, then call.js puts the camera back - when this machine cannot keep
|
|
19
|
+
* up (a frame costing over SLOW_MS for SLOW_FOR) or the segmenter keeps failing; `blur.reason` says which.
|
|
20
|
+
*
|
|
21
|
+
* The beat comes from a tiny Worker (blur-clock.js), not requestAnimationFrame or the page's own timers: a
|
|
22
|
+
* background tab stops rAF and slows timers to once a second, and the others would see a frozen picture whenever
|
|
23
|
+
* the caller looked at another tab. Each frame leaves at least as long idle as the last one took, so a slow machine
|
|
24
|
+
* drops frames rather than its buttons and chat.
|
|
25
|
+
*/
|
|
26
|
+
import {ImageSegmenter} from './vendor/mediapipe/vision_bundle.mjs';
|
|
27
|
+
|
|
28
|
+
const HERE = new URL('./vendor/mediapipe/', import.meta.url);
|
|
29
|
+
const MAX_WIDTH = 1280; // the camera is asked for 720p; anything larger is drawn at this width
|
|
30
|
+
const BG_SCALE = 6; // the background is blurred at 1/6 size, then scaled back up (cheap and smooth)
|
|
31
|
+
const KEEP = 0.35; // how much of the last frame's mask carries over (steadier edges, less flicker)
|
|
32
|
+
const SLOW_MS = 250; // a frame this slow on average (under 4 a second, with the idle gap)...
|
|
33
|
+
const SLOW_FOR = 5000; // ...for this long, and blur gives up
|
|
34
|
+
const START_LIMIT = 20000; // loading and the first frame, or it is not going to work here
|
|
35
|
+
|
|
36
|
+
let segmenterReady = null;
|
|
37
|
+
|
|
38
|
+
/** One segmenter for the page: the GPU where it works, the CPU otherwise. Dropped (and rebuilt next time) if it breaks. */
|
|
39
|
+
function segmenter() {
|
|
40
|
+
segmenterReady ??= (async () => {
|
|
41
|
+
const fileset = {
|
|
42
|
+
wasmLoaderPath: new URL('vision_wasm_internal.js', HERE).href,
|
|
43
|
+
wasmBinaryPath: new URL('vision_wasm_internal.wasm', HERE).href
|
|
44
|
+
};
|
|
45
|
+
const options = (delegate) => ({
|
|
46
|
+
baseOptions: {modelAssetPath: new URL('selfie_segmenter_landscape.tflite', HERE).href, delegate},
|
|
47
|
+
runningMode: 'VIDEO',
|
|
48
|
+
outputCategoryMask: false,
|
|
49
|
+
outputConfidenceMasks: true
|
|
50
|
+
});
|
|
51
|
+
try {
|
|
52
|
+
return await ImageSegmenter.createFromOptions(fileset, options('GPU'));
|
|
53
|
+
} catch {
|
|
54
|
+
return ImageSegmenter.createFromOptions(fileset, options('CPU'));
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
segmenterReady.catch(() => { segmenterReady = null; }); // a failed load may be tried again
|
|
58
|
+
return segmenterReady;
|
|
59
|
+
}
|
|
60
|
+
function dropSegmenter(seg) {
|
|
61
|
+
segmenterReady = null;
|
|
62
|
+
try { seg?.close?.(); } catch { /* already gone */ }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const canvas = (w, h) => Object.assign(document.createElement('canvas'), {width: w, height: h});
|
|
66
|
+
const smooth = (x) => (x <= 0.25 ? 0 : x >= 0.75 ? 1 : (x - 0.25) * 2); // a soft threshold: firm edges, no halo of half-person
|
|
67
|
+
const timeout = (ms, what) => new Promise((_, reject) => setTimeout(() => reject(new Error(`${what} took too long`)), ms));
|
|
68
|
+
|
|
69
|
+
/** The beat: a Worker's timer, which a background tab does not slow down. The page's own timer where there is none. */
|
|
70
|
+
function clock(tick) {
|
|
71
|
+
let worker = null;
|
|
72
|
+
try {
|
|
73
|
+
worker = new Worker(new URL(`./blur-clock.js${new URL(import.meta.url).search}`, import.meta.url));
|
|
74
|
+
worker.onmessage = () => tick();
|
|
75
|
+
} catch { worker = null; }
|
|
76
|
+
let timer = null;
|
|
77
|
+
return {
|
|
78
|
+
after(ms) {
|
|
79
|
+
if (worker) worker.postMessage(ms);
|
|
80
|
+
else timer = setTimeout(tick, ms);
|
|
81
|
+
},
|
|
82
|
+
stop() { worker?.terminate(); clearTimeout(timer); }
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Start blurring a camera track.
|
|
88
|
+
*
|
|
89
|
+
* @param {MediaStreamTrack} source - the camera's video track (left running; the caller owns it)
|
|
90
|
+
* @returns {Promise<{track: MediaStreamTrack, stop: () => void, paused: boolean, reason: string}>}
|
|
91
|
+
*/
|
|
92
|
+
export async function startBlur(source) {
|
|
93
|
+
const video = document.createElement('video');
|
|
94
|
+
video.muted = true;
|
|
95
|
+
video.playsInline = true;
|
|
96
|
+
try {
|
|
97
|
+
const seg = await Promise.race([(async () => {
|
|
98
|
+
const s = await segmenter();
|
|
99
|
+
video.srcObject = new MediaStream([source]);
|
|
100
|
+
await video.play();
|
|
101
|
+
if (!video.videoWidth) await new Promise(r => video.addEventListener('loadeddata', r, {once: true}));
|
|
102
|
+
return s;
|
|
103
|
+
})(), timeout(START_LIMIT, 'Starting blur')]);
|
|
104
|
+
return run(seg, source, video);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
video.pause();
|
|
107
|
+
video.srcObject = null;
|
|
108
|
+
throw err;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function run(seg, source, video) {
|
|
113
|
+
const out = canvas(16, 16);
|
|
114
|
+
const g = out.getContext('2d');
|
|
115
|
+
const bg = canvas(16, 9);
|
|
116
|
+
const bgc = bg.getContext('2d');
|
|
117
|
+
const filters = 'filter' in bgc;
|
|
118
|
+
const person = canvas(16, 16);
|
|
119
|
+
const pc = person.getContext('2d');
|
|
120
|
+
let W = 0; let H = 0;
|
|
121
|
+
let maskCanvas = null; let mc = null; let maskImage = null; let last = null;
|
|
122
|
+
|
|
123
|
+
/** Size everything to the camera's picture - again whenever it changes (a phone turned, the browser adapting it). */
|
|
124
|
+
function fit() {
|
|
125
|
+
if (!video.videoWidth || !video.videoHeight) return false;
|
|
126
|
+
const scale = Math.min(1, MAX_WIDTH / video.videoWidth);
|
|
127
|
+
const w = Math.round(video.videoWidth * scale); const h = Math.round(video.videoHeight * scale);
|
|
128
|
+
if (w === W && h === H) return true;
|
|
129
|
+
W = w; H = h;
|
|
130
|
+
out.width = person.width = W; out.height = person.height = H;
|
|
131
|
+
bg.width = Math.max(16, Math.round(W / BG_SCALE)); bg.height = Math.max(9, Math.round(H / BG_SCALE));
|
|
132
|
+
if (filters) bgc.filter = 'blur(2px)';
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
fit();
|
|
136
|
+
// The canvas paints once before the first mask, so the track never starts with an empty frame.
|
|
137
|
+
g.drawImage(video, 0, 0, W, H);
|
|
138
|
+
const track = out.captureStream(30).getVideoTracks()[0];
|
|
139
|
+
|
|
140
|
+
function drawMask(mask) {
|
|
141
|
+
const w = mask.width; const h = mask.height;
|
|
142
|
+
if (!maskCanvas || maskCanvas.width !== w || maskCanvas.height !== h) {
|
|
143
|
+
maskCanvas = canvas(w, h);
|
|
144
|
+
mc = maskCanvas.getContext('2d');
|
|
145
|
+
maskImage = mc.createImageData(w, h);
|
|
146
|
+
last = new Float32Array(w * h);
|
|
147
|
+
}
|
|
148
|
+
const data = mask.getAsFloat32Array();
|
|
149
|
+
const px = maskImage.data;
|
|
150
|
+
for (let i = 0; i < data.length; i++) {
|
|
151
|
+
const v = last[i] = data[i] * (1 - KEEP) + last[i] * KEEP;
|
|
152
|
+
px[i * 4 + 3] = smooth(v) * 255;
|
|
153
|
+
}
|
|
154
|
+
mc.putImageData(maskImage, 0, 0);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function compose() {
|
|
158
|
+
// The background: shrunk and blurred, stretched back over the whole frame. Drawn a little past each edge,
|
|
159
|
+
// because a blurred picture fades out at its own border and the last frame would show through.
|
|
160
|
+
const pad = 2;
|
|
161
|
+
bgc.drawImage(video, -pad, -pad, bg.width + pad * 2, bg.height + pad * 2);
|
|
162
|
+
g.imageSmoothingEnabled = true;
|
|
163
|
+
g.imageSmoothingQuality = 'high';
|
|
164
|
+
g.drawImage(bg, 0, 0, W, H);
|
|
165
|
+
// The person: the mask (stretched, which feathers it) filled with the sharp frame.
|
|
166
|
+
pc.globalCompositeOperation = 'copy';
|
|
167
|
+
pc.imageSmoothingEnabled = true;
|
|
168
|
+
pc.drawImage(maskCanvas, 0, 0, W, H);
|
|
169
|
+
pc.globalCompositeOperation = 'source-in';
|
|
170
|
+
pc.drawImage(video, 0, 0, W, H);
|
|
171
|
+
g.drawImage(person, 0, 0);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const self = {track, paused: false, reason: '', stop};
|
|
175
|
+
let stopped = false;
|
|
176
|
+
let lastTs = 0;
|
|
177
|
+
let cost = 16; // a running average of one frame's work, in ms
|
|
178
|
+
let slowSince = 0;
|
|
179
|
+
let failures = 0;
|
|
180
|
+
const beat = clock(frame);
|
|
181
|
+
|
|
182
|
+
function frame() {
|
|
183
|
+
if (stopped) return;
|
|
184
|
+
const started = performance.now();
|
|
185
|
+
if (source.readyState === 'ended') { stop(); return; }
|
|
186
|
+
if (!self.paused && source.enabled && video.readyState >= 2 && fit()) {
|
|
187
|
+
let result = null;
|
|
188
|
+
try {
|
|
189
|
+
const ts = Math.max(lastTs + 1, started);
|
|
190
|
+
lastTs = ts;
|
|
191
|
+
result = seg.segmentForVideo(video, ts);
|
|
192
|
+
const mask = result?.confidenceMasks?.[0];
|
|
193
|
+
if (mask) { drawMask(mask); compose(); failures = 0; }
|
|
194
|
+
} catch (err) {
|
|
195
|
+
failures += 1;
|
|
196
|
+
if (failures === 1) console.warn('[call] blur:', err?.message || err);
|
|
197
|
+
if (failures > 30) { dropSegmenter(seg); stop('failed'); return; }
|
|
198
|
+
} finally {
|
|
199
|
+
try { result?.close?.(); } catch { /* nothing to free */ }
|
|
200
|
+
}
|
|
201
|
+
cost = cost * 0.9 + (performance.now() - started) * 0.1;
|
|
202
|
+
if (cost > SLOW_MS) {
|
|
203
|
+
slowSince ||= started;
|
|
204
|
+
if (started - slowSince > SLOW_FOR) { stop('slow'); return; }
|
|
205
|
+
} else slowSince = 0;
|
|
206
|
+
}
|
|
207
|
+
const spent = performance.now() - started;
|
|
208
|
+
const every = cost > 28 ? 1000 / 15 : 1000 / 30;
|
|
209
|
+
beat.after(Math.max(cost, every - spent, 4));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function stop(reason = '') {
|
|
213
|
+
if (stopped) return;
|
|
214
|
+
stopped = true;
|
|
215
|
+
self.reason = reason;
|
|
216
|
+
beat.stop();
|
|
217
|
+
video.pause();
|
|
218
|
+
video.srcObject = null;
|
|
219
|
+
track.stop();
|
|
220
|
+
track.dispatchEvent(new Event('ended'));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
frame();
|
|
224
|
+
return self;
|
|
225
|
+
}
|