domma-cms 0.31.0 → 0.32.2
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 +4 -4
- package/admin/js/lib/themes.js +1 -1
- package/admin/js/views/settings.js +1 -1
- package/config/site.json +1 -0
- package/package.json +2 -2
- package/plugins/surveys/admin/templates/audience.html +47 -0
- package/plugins/surveys/admin/templates/results.html +58 -0
- package/plugins/surveys/admin/templates/survey-editor.html +141 -0
- package/plugins/surveys/admin/templates/surveys.html +25 -0
- package/plugins/surveys/admin/views/audience.js +301 -0
- package/plugins/surveys/admin/views/results.js +172 -0
- package/plugins/surveys/admin/views/survey-editor.js +211 -0
- package/plugins/surveys/admin/views/surveys.js +161 -0
- package/plugins/surveys/collections/survey-contacts/schema.json +13 -0
- package/plugins/surveys/collections/survey-groups/schema.json +11 -0
- package/plugins/surveys/collections/survey-invites/schema.json +16 -0
- package/plugins/surveys/collections/surveys/schema.json +23 -0
- package/plugins/surveys/config.js +8 -0
- package/plugins/surveys/plugin.js +174 -0
- package/plugins/surveys/plugin.json +65 -0
- package/plugins/surveys/public/dist-shared.mjs +1 -0
- package/plugins/surveys/public/survey.css +1 -0
- package/plugins/surveys/public/survey.mjs +1 -0
- package/plugins/surveys/templates/survey-page.html +63 -0
- package/server/server.js +3 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "surveys",
|
|
3
|
+
"displayName": "Surveys",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Targeted surveys over the Forms engine: shared audiences, tokenised invites, reminders, and per-survey statistics.",
|
|
6
|
+
"author": "Darryl Waterhouse",
|
|
7
|
+
"date": "2026-06-26",
|
|
8
|
+
"icon": "clipboard-list",
|
|
9
|
+
"admin": {
|
|
10
|
+
"sidebar": [
|
|
11
|
+
{
|
|
12
|
+
"id": "surveys",
|
|
13
|
+
"text": "Surveys",
|
|
14
|
+
"icon": "clipboard-list",
|
|
15
|
+
"url": "#/plugins/surveys",
|
|
16
|
+
"section": "#/plugins/surveys"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"routes": [
|
|
20
|
+
{
|
|
21
|
+
"path": "/plugins/surveys",
|
|
22
|
+
"view": "plugin-surveys",
|
|
23
|
+
"title": "Surveys - Domma CMS"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"path": "/plugins/surveys/new",
|
|
27
|
+
"view": "plugin-survey-editor",
|
|
28
|
+
"title": "New Survey - Domma CMS"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"path": "/plugins/surveys/edit/:slug",
|
|
32
|
+
"view": "plugin-survey-editor",
|
|
33
|
+
"title": "Edit Survey - Domma CMS"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"path": "/plugins/surveys/audience",
|
|
37
|
+
"view": "plugin-survey-audience",
|
|
38
|
+
"title": "Survey Audience - Domma CMS"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"path": "/plugins/surveys/results/:slug",
|
|
42
|
+
"view": "plugin-survey-results",
|
|
43
|
+
"title": "Survey Results - Domma CMS"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"views": {
|
|
47
|
+
"plugin-surveys": {
|
|
48
|
+
"entry": "surveys/admin/views/surveys.js?v=62e94063",
|
|
49
|
+
"exportName": "surveysView"
|
|
50
|
+
},
|
|
51
|
+
"plugin-survey-editor": {
|
|
52
|
+
"entry": "surveys/admin/views/survey-editor.js?v=322a3ad4",
|
|
53
|
+
"exportName": "surveyEditorView"
|
|
54
|
+
},
|
|
55
|
+
"plugin-survey-audience": {
|
|
56
|
+
"entry": "surveys/admin/views/audience.js?v=66eea1d5",
|
|
57
|
+
"exportName": "audienceView"
|
|
58
|
+
},
|
|
59
|
+
"plugin-survey-results": {
|
|
60
|
+
"entry": "surveys/admin/views/results.js?v=13ceda01",
|
|
61
|
+
"exportName": "resultsView"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function renderDistribution(a){return'<div class="survey-dist">'+a.map(s=>`<div class="survey-dist-row"><span class="survey-dist-label">${e(s.label)}</span><span class="survey-dist-bar"><span class="survey-dist-fill" data-pct="${s.pct}" style="width:${s.pct}%"></span></span><span class="survey-dist-val">${s.value} (${s.pct}%)</span></div>`).join("")+"</div>"}function e(a){return String(a??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.survey-container{max-width:720px;margin:0 auto;padding:2rem 1rem 4rem}.survey-title{margin-bottom:1.5rem}.survey-code{margin-bottom:1.5rem;padding:1rem 1.25rem;border:1px solid var(--dm-border-color, #d0d0d0);border-radius:var(--dm-radius, 8px);background:var(--dm-surface, #fafafa)}.survey-code label{display:block;margin-bottom:.5rem;font-weight:600}.survey-code-row{display:flex;gap:.5rem;align-items:stretch;flex-wrap:wrap}.survey-code-row input{flex:1 1 200px;padding:.5rem .75rem;border:1px solid var(--dm-border-color, #d0d0d0);border-radius:var(--dm-radius, 6px);font:inherit}.survey-state{padding:1.5rem;border-radius:var(--dm-radius, 8px);text-align:center;font-size:1.05rem;line-height:1.5}.survey-state-success{background:var(--dm-success-bg, #e8f6ec);color:var(--dm-success-text, #1c6b34);border:1px solid var(--dm-success-border, #b6e0c2)}.survey-state-error{background:var(--dm-danger-bg, #fcebec);color:var(--dm-danger-text, #8a1f28);border:1px solid var(--dm-danger-border, #f0bfc4)}.survey-state-info{background:var(--dm-surface, #f4f4f4);color:var(--dm-text-muted, #555);border:1px solid var(--dm-border-color, #d8d8d8)}.survey-dist{display:flex;flex-direction:column;gap:.5rem;margin:.75rem 0}.survey-dist-row{display:grid;grid-template-columns:minmax(80px,30%) 1fr auto;align-items:center;gap:.75rem}.survey-dist-label{font-size:.9rem;color:var(--dm-text, #333);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.survey-dist-bar{position:relative;height:.9rem;background:var(--dm-surface-alt, #ececec);border-radius:var(--dm-radius, 6px);overflow:hidden}.survey-dist-fill{position:absolute;inset:0 auto 0 0;height:100%;background:var(--dm-primary, #3b6ef0);border-radius:inherit;transition:width .3s ease}.survey-dist-val{font-size:.85rem;color:var(--dm-text-muted, #666);min-width:3.5rem;text-align:right}.survey-intro{margin:0 0 1.5rem}.survey-footer{margin:1.5rem 0 0;padding-top:1rem;border-top:1px solid var(--dm-border, #e5e7eb);color:var(--dm-text-muted, #6b7280);font-size:.92rem}.survey-intro:empty,.survey-footer:empty{display:none;border:0;margin:0;padding:0}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const SKIP_TYPES=new Set(["page-break","spacer","heading","paragraph","html"]);function mapType(e){switch(e){case"textarea":return"textarea";case"select":case"dropdown":return"select";case"radio":return"radio";case"checkbox":case"checkboxes":return"checkbox";case"email":return"email";case"number":return"number";case"date":return"date";case"tel":case"phone":return"tel";default:return"text"}}function buildBlueprint(e){const r={};return(e||[]).forEach(t=>{!t||!t.name||SKIP_TYPES.has(t.type)||(r[t.name]={type:mapType(t.type),label:t.label||t.name,required:!!t.required,options:t.options})}),r}function toast(e,r){typeof E<"u"&&E&&typeof E.toast=="function"&&E.toast(e,{type:r||"info"})}function clear(e){for(;e.firstChild;)e.removeChild(e.firstChild)}function showState(e,r,t){clear(e);const o=document.createElement("div");o.className="survey-state survey-state-"+(t||"info"),o.textContent=r,e.appendChild(o)}async function submitResponse(e,r,t){if(typeof H<"u"&&H&&typeof H.post=="function")return H.post("/api/plugins/surveys/submit/"+e,{token:r,data:t});const o=await fetch("/api/plugins/surveys/submit/"+e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:r,data:t})}),s=await o.json().catch(()=>({}));if(!o.ok){const n=new Error(s.error||"Submission failed");throw n.body=s,n}return s}async function renderForm(e,r,t,o){let s;try{const i=await fetch("/api/forms/"+r+"/public");if(!i.ok)throw new Error("Form not found");s=await i.json()}catch{showState(e,"This survey form could not be loaded.","error");return}const n=s.fields||[],u=s.settings||{},c=buildBlueprint(n),f=async i=>{try{const a=await submitResponse(t,o,i||{});showState(e,a.message||"Thank you for completing this survey.","success"),toast(a.message||"Response recorded","success")}catch(a){const d=a.body&&a.body.error||a.message||"Submission failed";toast(d,"error"),showState(e,d,"error")}};clear(e),typeof Domma<"u"&&Domma.forms&&typeof Domma.forms.render=="function"?Domma.forms.render(e,c,{},{submitText:u.submitText||"Submit",onSubmit:f}):showState(e,"This survey requires JavaScript components that failed to load.","error")}function startMount(e){const r=e.getAttribute("data-survey-slug"),t=e.getAttribute("data-form-slug");if(!r||!t){showState(e,"This survey is misconfigured.","error");return}let s=new URLSearchParams(window.location.search).get("t");if(s){renderForm(e,t,r,s);return}const n=document.getElementById("survey-code");if(!n){showState(e,"Please use the personalised link from your invitation, or enter your code via the survey page.","info");return}n.style.display="",showState(e,"Enter your code above to begin.","info"),n.addEventListener("submit",u=>{u.preventDefault();const c=n.querySelector('[name="code"]');s=c?c.value.trim():"",s&&(n.style.display="none",clear(e),renderForm(e,t,r,s))})}function start(){const e=new Set,r=document.getElementById("survey-mount");r&&e.add(r),document.querySelectorAll(".survey-mount").forEach(t=>e.add(t)),e.forEach(startMount)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",start):start();
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-GB">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{title}}</title>
|
|
7
|
+
<meta name="robots" content="noindex">
|
|
8
|
+
|
|
9
|
+
<!-- DommaJS CSS -->
|
|
10
|
+
<link rel="stylesheet" href="/dist/domma/domma.css">
|
|
11
|
+
<link rel="stylesheet" href="/dist/domma/grid.css">
|
|
12
|
+
<link rel="stylesheet" href="/dist/domma/elements.css">
|
|
13
|
+
<link rel="stylesheet" href="/dist/domma/themes/domma-themes.css">
|
|
14
|
+
|
|
15
|
+
<!-- Site CSS -->
|
|
16
|
+
<link rel="stylesheet" href="/public/css/site.css">
|
|
17
|
+
<link rel="stylesheet" href="/public/css/forms.css">
|
|
18
|
+
|
|
19
|
+
<!-- Survey CSS -->
|
|
20
|
+
<link rel="stylesheet" href="/plugins/surveys/public/survey.css">
|
|
21
|
+
</head>
|
|
22
|
+
<body class="dm-cloaked">
|
|
23
|
+
|
|
24
|
+
<main class="site-main">
|
|
25
|
+
<article class="site-content">
|
|
26
|
+
<div class="container survey-container">
|
|
27
|
+
<h1 class="survey-title">{{title}}</h1>
|
|
28
|
+
|
|
29
|
+
<form id="survey-code" class="survey-code" style="display:none">
|
|
30
|
+
<label for="survey-code-input">Enter your code</label>
|
|
31
|
+
<div class="survey-code-row">
|
|
32
|
+
<input id="survey-code-input" name="code" type="text" placeholder="Enter your code" autocomplete="off">
|
|
33
|
+
<button type="submit" class="btn btn-primary">Start</button>
|
|
34
|
+
</div>
|
|
35
|
+
</form>
|
|
36
|
+
|
|
37
|
+
<div class="survey-intro">{{intro}}</div>
|
|
38
|
+
|
|
39
|
+
<div id="survey-mount" data-survey-slug="{{slug}}" data-form-slug="{{formSlug}}"></div>
|
|
40
|
+
|
|
41
|
+
<div class="survey-footer">{{footer}}</div>
|
|
42
|
+
</div>
|
|
43
|
+
</article>
|
|
44
|
+
</main>
|
|
45
|
+
|
|
46
|
+
<!-- DOMPurify - must load before DommaJS -->
|
|
47
|
+
<script src="https://cdn.jsdelivr.net/npm/dompurify@3/dist/purify.min.js"></script>
|
|
48
|
+
|
|
49
|
+
<!-- DommaJS -->
|
|
50
|
+
<script src="/dist/domma/domma.min.js"></script>
|
|
51
|
+
<script>
|
|
52
|
+
if (window.Domma && typeof window.Domma.init === 'function') {
|
|
53
|
+
window.Domma.init();
|
|
54
|
+
}
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<!-- Core Forms (logic engine must load before renderer) -->
|
|
58
|
+
<script src="/public/js/form-logic-engine.js"></script>
|
|
59
|
+
|
|
60
|
+
<!-- Survey client -->
|
|
61
|
+
<script type="module" src="/plugins/surveys/public/survey.mjs"></script>
|
|
62
|
+
</body>
|
|
63
|
+
</html>
|
package/server/server.js
CHANGED
|
@@ -364,7 +364,9 @@ for (const [name, plugin] of Object.entries(getLoadedPlugins())) {
|
|
|
364
364
|
await app.register(publicPlugin, {
|
|
365
365
|
settings,
|
|
366
366
|
auth: { authenticate: _authenticate, requireAdmin: _requireAdmin, requireVisibility: _requireVisibility },
|
|
367
|
-
|
|
367
|
+
// getConfig() requires a name; pass the startup config singleton instead.
|
|
368
|
+
// (Without this every enabled plugin.public.js fails to register.)
|
|
369
|
+
config
|
|
368
370
|
});
|
|
369
371
|
} catch (err) {
|
|
370
372
|
app.log.error(`[plugins] Failed to register public plugin ${name}: ${err.message}`);
|