domma-cms 0.115.5 → 0.116.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/js/lib/effect-defs.js +1 -1
- package/package.json +1 -1
- package/plugins/free-tier.lock.json +16 -16
- package/plugins/support/CLAUDE.md +19 -0
- package/plugins/support/admin/css/index.css +4 -0
- package/plugins/support/admin/lib/kit.js +60 -11
- package/plugins/support/admin/views/desk.js +158 -74
- package/plugins/support/admin/views/inbox.js +146 -56
- package/plugins/support/admin/views/support.js +1 -1
- package/plugins/support/collections/support-tickets.json +1 -0
- package/plugins/support/lib/receiver.js +2 -1
- package/plugins/support/lib/sender.js +68 -4
- package/plugins/support/lib/shape.js +10 -1
- package/plugins/support/plugin.js +38 -6
- package/plugins/support/plugin.json +2 -2
- package/plugins/support/tests/api.test.js +94 -0
- package/plugins/support/tests/shape.test.js +68 -1
- package/public/js/celebrations/character-catalog.mjs +1 -0
- package/public/js/celebrations/characters.js +1 -0
- package/public/js/celebrations/index.js +1 -1
- package/public/js/celebrations/themes/christmas.js +1 -1
- package/public/js/celebrations/themes/mooning-antics.js +1 -0
- package/public/js/celebrations/themes/mooning-drawing.js +1 -0
- package/public/js/celebrations/themes/st-patricks.js +1 -1
- package/public/js/celebrations/trait-data.mjs +1 -1
- package/public/js/effects.js +1 -1
- package/server/services/markdown.js +18 -2
- package/server/templates/page.html +1 -1
|
@@ -1 +1 @@
|
|
|
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
|
+
import{getCharacters as f}from"../../../public/js/celebrations/character-catalog.mjs";import{CELEBRATION_THEMES as i,traitAttribute as p}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:"A full-page seasonal celebration, or one inline character at any time of year.",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(o=>o.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:"character",label:"Standalone character",type:"select",default:"",options:[["","Full celebration"],...f().map(e=>[e.name,e.label])]},{name:"size",label:"Character height",type:"text",default:"medium",placeholder:"medium or 200",hint:"small (96px), medium (160px), large (240px), xlarge (360px), or a height in pixels."},{name:"animate",label:"Animate character",type:"checkbox",default:!0},{name:"facing",label:"Character direction",type:"select",default:"right",options:[["right","Right"],["left","Left"]]}),n.attrs.push({name:"falling-only",label:"Falling particles only",type:"checkbox",default:!1}),n.attrs.push({name:"mooning-elf",label:"Mooning elf (opt-in)",type:"checkbox",default:!1,themes:["christmas"]}),EFFECTS.find(e=>e.name==="christmas").attrs.push({name:"mooning-elf",label:"Mooning elf (opt-in)",type:"checkbox",default:!1});const s=new Map(n.attrs.map(e=>[e.name,e]));for(const[e,t]of Object.entries(i))if(e!=="christmas")for(const[o,r]of Object.entries(t.traits)){const a=p(o);let l=s.get(a);l||(l=r.enabled===!1?{name:a,label:`${r.label} (opt-in)`,type:"checkbox",default:!1,themes:[]}:{name:a,label:r.label,type:"select",default:"",options:[["","Shown"],["off","Off"]],themes:[]},s.set(a,l)),l.themes.includes(e)||l.themes.push(e),l.themes.length>1&&(l.label=l.label.replace(/^Christmas: /,""))}for(const e of s.values())n.attrs.includes(e)||n.attrs.push(e);export function getEffectAttrs(e,t={}){if(e.name!=="celebrate")return e.attrs||[];const o=["character","size","animate","facing"];if(t.character)return e.attrs.filter(a=>o.includes(a.name));const r=t.theme||"auto";return e.attrs.filter(a=>(a.name==="character"||!o.includes(a.name))&&(!a.themes||r==="auto"||a.themes.includes(r)))}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"note": "Written by `make sync-free-tier` in dcms-marketplace. Do not edit these plugins here - edit them there and sync.",
|
|
3
|
-
"source": "dcms-marketplace@
|
|
3
|
+
"source": "dcms-marketplace@149aff8",
|
|
4
4
|
"plugins": {
|
|
5
5
|
"blog": {
|
|
6
6
|
"version": "1.9.4",
|
|
@@ -224,25 +224,25 @@
|
|
|
224
224
|
}
|
|
225
225
|
},
|
|
226
226
|
"support": {
|
|
227
|
-
"version": "1.0
|
|
227
|
+
"version": "1.1.0",
|
|
228
228
|
"files": {
|
|
229
|
-
"CLAUDE.md": "
|
|
230
|
-
"admin/css/index.css": "
|
|
231
|
-
"admin/lib/kit.js": "
|
|
229
|
+
"CLAUDE.md": "ff42515f210dfdbeb1b14992012177ad8513323a340a0d4fe69a6ec13773db5a",
|
|
230
|
+
"admin/css/index.css": "bad02a487ebb131b64ee55e2a4533c8b79d6ada80fffd34b0952bf3b68204a36",
|
|
231
|
+
"admin/lib/kit.js": "22f02768625bcad9d9c8e64bf0a8fe95f7c5ff6dcf8abb189df8203362a7a4e1",
|
|
232
232
|
"admin/templates/support.html": "2bb69d0443a367a402e6160fe68f4af3e8983b578cfff9575b4df9a534fcb480",
|
|
233
|
-
"admin/views/desk.js": "
|
|
234
|
-
"admin/views/inbox.js": "
|
|
235
|
-
"admin/views/support.js": "
|
|
236
|
-
"collections/support-tickets.json": "
|
|
233
|
+
"admin/views/desk.js": "ac043630ad08c8f2053f3f908be18586d9a4798976d20427cf5215ee042c807c",
|
|
234
|
+
"admin/views/inbox.js": "f570595d7b2c831d151e0aaf85ff903e6e2b7066124b3f9d23c2c24f5b1734cf",
|
|
235
|
+
"admin/views/support.js": "360a50ff54b81f93fcd6fb6949710e0cff5d342bb8573408415ef3faf3fd29ff",
|
|
236
|
+
"collections/support-tickets.json": "55a0856bacd66c2e8f1e3c85bdf3638e5ff826560943cda854a8f2d336a88905",
|
|
237
237
|
"config.js": "eb7ccc1169fa8b2be52a213536825492fedaec5fa73fd749cba9f498dd54b503",
|
|
238
238
|
"lib/mail.js": "d14aae3a59aad6af61451863cdaaf94fc4a87810b16e1dcb9c655dda6c803b3b",
|
|
239
|
-
"lib/receiver.js": "
|
|
240
|
-
"lib/sender.js": "
|
|
241
|
-
"lib/shape.js": "
|
|
242
|
-
"plugin.js": "
|
|
243
|
-
"plugin.json": "
|
|
244
|
-
"tests/api.test.js": "
|
|
245
|
-
"tests/shape.test.js": "
|
|
239
|
+
"lib/receiver.js": "b95c569932c261a48849f0a6b7005f649d9c2a50d319b098a47d02da9363050c",
|
|
240
|
+
"lib/sender.js": "b20aaee34ade89814a897f68467f9902693843fa6b5c8eb0090c4059193c8227",
|
|
241
|
+
"lib/shape.js": "04d840b589d20700ccb2cfa4c72072f8d9f8e03f0573e2ea1177a7a83a383bea",
|
|
242
|
+
"plugin.js": "e3c50a05fa99cb24375782bbb3ca8c58cf5ddc3036937e6115f4d963715c120e",
|
|
243
|
+
"plugin.json": "e6efaf06f7f2c99b196909e56f0a0b6d65b438ffd6d616aa4207769861f9bd66",
|
|
244
|
+
"tests/api.test.js": "1fa87f6ae12c48fc34a5fb2b5bbeed6cfaa36329610674d4fae05d64734dbb15",
|
|
245
|
+
"tests/shape.test.js": "364c6b7680699f59fcd449cd448184486488838300f155135daea4c05387cf74"
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
}
|
|
@@ -39,6 +39,25 @@ registration by where it runs.
|
|
|
39
39
|
- **The CMS version comes from the manager's registry** (`sites.json` `cmsVersion`), not the payload: a
|
|
40
40
|
child site's package.json is the manager's, so its own answer is wrong.
|
|
41
41
|
|
|
42
|
+
## Archive, remove and the row menus (1.1.0)
|
|
43
|
+
|
|
44
|
+
Both screens are domma-reactive throughout (lists AND the request panel: the thread is a `data-each` over
|
|
45
|
+
`threadRows()`, status/urgent/archived are computeds over the list's copy, so a move from a row menu shows in
|
|
46
|
+
an open panel at once). Every row has ONE menu definition (`menuItems`) reached by right-click, the ⋮ kebab
|
|
47
|
+
and Shift+F10 (`rowKeys()` in kit.js); click/Enter opens. The panel's thread has its own menu (copy; on the
|
|
48
|
+
manager also "Quote in reply").
|
|
49
|
+
|
|
50
|
+
- **Manager archive** = `data.archived` (PUT `/tickets/:id {archived}`), Domma's own filing: the site never
|
|
51
|
+
sees it, `/count` ignores archived requests, and `addTurn()` takes a request OUT of the archive when the
|
|
52
|
+
customer writes again (a close does not).
|
|
53
|
+
- **Site shelf** = the outbox's `shelf` (`{ref: {state: archived|removed, dommaAt}}`), this site's own tidying
|
|
54
|
+
- Domma keeps every request. `applyShelf()` (pure) marks archived and drops removed, and either comes back
|
|
55
|
+
the moment `lastDommaAt` moves past the recorded `dommaAt`: a reply is never hidden. `remember()` prunes
|
|
56
|
+
entries Domma deleted or has written to since. Shelving marks the reply read. Removing a request that
|
|
57
|
+
never reached Domma (`discard()`) cancels it - it and its follow-ups leave the outbox. Writing to an
|
|
58
|
+
archived request unshelves it.
|
|
59
|
+
- Manager **Delete** stays a real delete (with `deleted.json`); the confirm suggests archiving instead.
|
|
60
|
+
|
|
42
61
|
## Files
|
|
43
62
|
|
|
44
63
|
- `plugin.js` - both route sets (see its header); `options.mode` / `mail` / `fetch` / `env` /
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
.sp-seg button { border: 1px solid var(--dm-border); background: none; color: var(--dm-text-secondary); border-radius: var(--dm-radius-full);
|
|
37
37
|
padding: .2rem .7rem; font-size: var(--dm-font-size-sm); cursor: pointer; }
|
|
38
38
|
.sp-seg button b { font-weight: 600; margin-left: .2rem; }
|
|
39
|
+
.sp-seg button:focus-visible { outline: 2px solid var(--dm-primary); outline-offset: 1px; }
|
|
39
40
|
.sp-seg button.is-on { background: var(--dm-primary-light); color: var(--dm-text); border-color: var(--dm-primary); }
|
|
40
41
|
.sp-search { flex: 1; min-width: 12rem; max-width: 20rem; }
|
|
41
42
|
|
|
@@ -48,6 +49,8 @@
|
|
|
48
49
|
.sp-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
49
50
|
.sp-main strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
50
51
|
.sp-main small { color: var(--dm-text-secondary); font-size: var(--dm-font-size-xs); }
|
|
52
|
+
.sp-kebab { border: 0; background: none; color: var(--dm-text-secondary); cursor: pointer; font-size: 1.1rem; line-height: 1; padding: .2rem .4rem; border-radius: var(--dm-radius-md); flex: none; }
|
|
53
|
+
.sp-kebab:hover, .sp-kebab:focus-visible { background: var(--dm-hover-bg); color: var(--dm-text); outline: none; }
|
|
51
54
|
.sp-dot { width: .55rem; height: .55rem; border-radius: 50%; background: var(--dm-primary); flex: none; }
|
|
52
55
|
.sp-urgent { font-size: var(--dm-font-size-xs); font-weight: 700; color: var(--dm-danger); text-transform: uppercase; letter-spacing: .03em; }
|
|
53
56
|
.sp-chip { font-size: var(--dm-font-size-xs); font-weight: 600; padding: .12rem .55rem; border-radius: var(--dm-radius-full); white-space: nowrap;
|
|
@@ -55,6 +58,7 @@
|
|
|
55
58
|
.sp-chip.is-info { background: color-mix(in srgb, var(--dm-info) 18%, transparent); }
|
|
56
59
|
.sp-chip.is-warning { background: color-mix(in srgb, var(--dm-warning) 22%, transparent); }
|
|
57
60
|
.sp-chip.is-success { background: color-mix(in srgb, var(--dm-success) 20%, transparent); }
|
|
61
|
+
.sp-chip.is-muted { color: var(--dm-text-secondary); }
|
|
58
62
|
.sp-chip.is-bad { background: color-mix(in srgb, var(--dm-danger) 20%, transparent); }
|
|
59
63
|
|
|
60
64
|
/* Forms */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Support admin - what both screens share: the admin's helpers (loaded at run
|
|
3
3
|
* time; a plugin never imports admin/ statically - the updater replaces it),
|
|
4
|
-
* the API, escaping, dates, slideovers and the conversation thread.
|
|
4
|
+
* the API, escaping, dates, slideovers, the row menus and the conversation thread.
|
|
5
5
|
*
|
|
6
6
|
* @module support/admin/lib/kit
|
|
7
7
|
*/
|
|
@@ -21,6 +21,10 @@ export function loadKit() {
|
|
|
21
21
|
import('/admin/js/lib/help-popover.js').catch(() => ({}))
|
|
22
22
|
]).then(([tk, help]) => ({
|
|
23
23
|
onceClosed: tk.onceClosed || ((so, fn) => { const c = so.close.bind(so); so.close = (...a) => { const r = c(...a); fn?.(); return r; }; }),
|
|
24
|
+
openMenuAt: tk.openMenuAt || ((el) => {
|
|
25
|
+
const r = el.getBoundingClientRect();
|
|
26
|
+
el.dispatchEvent(new MouseEvent('contextmenu', {bubbles: true, cancelable: true, clientX: r.left, clientY: r.bottom, view: window}));
|
|
27
|
+
}),
|
|
24
28
|
viewLifetime: tk.viewLifetime || (() => ({add() {}, reset() {}})),
|
|
25
29
|
helpPopovers: help.helpPopovers || (() => () => {})
|
|
26
30
|
}));
|
|
@@ -38,6 +42,8 @@ export const api = {
|
|
|
38
42
|
write: (ref, body) => H.post(`${BASE}/tickets/${seg(ref)}/messages`, {body}),
|
|
39
43
|
close: (ref) => H.post(`${BASE}/tickets/${seg(ref)}/close`, {}),
|
|
40
44
|
seen: (ref, at) => H.put(`${BASE}/tickets/${seg(ref)}/seen`, {at}),
|
|
45
|
+
/** `at`: Domma's latest reply as the screen showed it (omitted for an offline copy, which has none). */
|
|
46
|
+
shelf: (ref, state, at) => H.put(`${BASE}/tickets/${seg(ref)}/shelf`, {state, ...(at !== undefined && {at})}),
|
|
41
47
|
// manager
|
|
42
48
|
ticket: (id) => H.get(`${BASE}/tickets/${seg(id)}`),
|
|
43
49
|
reply: (id, d) => H.post(`${BASE}/tickets/${seg(id)}/reply`, d),
|
|
@@ -88,17 +94,60 @@ export const telHref = (phone) => `tel:${String(phone || '').replace(/[^\d+]/g,
|
|
|
88
94
|
export const TONE = {open: 'warning', answered: 'info', closed: 'success', waiting: 'warning', refused: 'bad'};
|
|
89
95
|
|
|
90
96
|
/**
|
|
91
|
-
* The conversation, oldest first, as
|
|
92
|
-
*
|
|
97
|
+
* The conversation, oldest first, as rows for THREAD. `mine` says which side is
|
|
98
|
+
* "us" (customer on a site, domma on the manager).
|
|
93
99
|
*/
|
|
94
|
-
export function
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
export function threadRows(messages = [], mine) {
|
|
101
|
+
return messages.map((m, i) => ({
|
|
102
|
+
key: m.id || `m${i}`,
|
|
103
|
+
cls: `${m.from === mine ? 'is-mine' : 'is-theirs'}${m.from === 'domma' ? ' is-domma' : ''}`,
|
|
104
|
+
who: m.from === 'domma' ? `${m.name || 'Domma'} · Domma Support` : (m.name || 'Customer'),
|
|
105
|
+
when: `${stamp(m.at)}${m.pending ? ' · waiting to send' : ''}${m.refused ? ' · not accepted' : ''}`,
|
|
106
|
+
body: m.body || ''
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The thread, bound to a `thread` observable of threadRows(). Text bindings only: nothing is parsed as HTML. */
|
|
111
|
+
export const THREAD = `
|
|
112
|
+
<p class="sp-note" data-bind-hidden="thread.value.length">Nothing here yet.</p>
|
|
113
|
+
<ol class="sp-thread" data-each="thread.value key=key"><li class="sp-msg" data-bind-class="cls" data-key="{{key}}" tabindex="-1">
|
|
114
|
+
<div class="sp-msg-head"><strong data-bind-text="who"></strong><small data-bind-text="when"></small></div>
|
|
115
|
+
<div class="sp-msg-body" data-bind-text="body"></div>
|
|
116
|
+
</li></ol>`;
|
|
117
|
+
|
|
118
|
+
/** Copy text, with a toast either way. */
|
|
119
|
+
export async function copyText(text, what = 'Copied') {
|
|
120
|
+
try {
|
|
121
|
+
await navigator.clipboard.writeText(String(text ?? ''));
|
|
122
|
+
E.toast(`${what}.`, {type: 'success'});
|
|
123
|
+
} catch {
|
|
124
|
+
E.toast('Your browser would not let us copy that.', {type: 'warning'});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The one way into a list's row menu besides right-click: the row's ⋮ button
|
|
130
|
+
* and Shift+F10. Click (or Enter) on the row itself does `primary`.
|
|
131
|
+
*
|
|
132
|
+
* @returns {Function} removes the listeners
|
|
133
|
+
*/
|
|
134
|
+
export function rowKeys(listEl, {rowSel, kebabSel, openMenuAt, primary}) {
|
|
135
|
+
const onClick = (e) => {
|
|
136
|
+
const row = e.target.closest(rowSel);
|
|
137
|
+
if (!row) return;
|
|
138
|
+
const kebab = e.target.closest(kebabSel);
|
|
139
|
+
if (kebab) { e.stopPropagation(); openMenuAt(kebab); return; }
|
|
140
|
+
primary(row.dataset.id);
|
|
141
|
+
};
|
|
142
|
+
const onKey = (e) => {
|
|
143
|
+
const row = e.target.closest(rowSel);
|
|
144
|
+
if (!row) return;
|
|
145
|
+
if ((e.key === 'F10' && e.shiftKey) || e.key === 'ContextMenu') { e.preventDefault(); openMenuAt(row); }
|
|
146
|
+
else if ((e.key === 'Enter' || e.key === ' ') && e.target === row) { e.preventDefault(); primary(row.dataset.id); }
|
|
147
|
+
};
|
|
148
|
+
listEl.addEventListener('click', onClick);
|
|
149
|
+
listEl.addEventListener('keydown', onKey);
|
|
150
|
+
return () => { listEl.removeEventListener('click', onClick); listEl.removeEventListener('keydown', onKey); };
|
|
102
151
|
}
|
|
103
152
|
|
|
104
153
|
/**
|