domma-cms 1.8.30 → 1.8.32

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/docs/DommaFeatures.md +5 -4
  3. package/package.json +3 -1
  4. package/plugins/_lib/admin/games/games.css +1 -0
  5. package/plugins/_lib/admin/games/kit.js +241 -0
  6. package/plugins/_lib/games/http.js +82 -0
  7. package/plugins/_lib/games/names.js +42 -0
  8. package/plugins/_lib/games/plays.js +155 -0
  9. package/plugins/_lib/games/ranking.js +68 -0
  10. package/plugins/_lib/games/timing.js +27 -0
  11. package/plugins/_lib/public/games/format.js +38 -0
  12. package/plugins/_lib/public/games/games.css +1 -0
  13. package/plugins/_lib/public/games/games.js +204 -0
  14. package/plugins/crossword/admin/css/index.css +1 -0
  15. package/plugins/crossword/admin/templates/crossword.html +1 -0
  16. package/plugins/crossword/admin/views/crossword.js +317 -0
  17. package/plugins/crossword/collections/crossword-plays.json +20 -0
  18. package/plugins/crossword/collections/crossword-puzzles.json +17 -0
  19. package/plugins/crossword/config.js +6 -0
  20. package/plugins/crossword/inject/body.html +1 -0
  21. package/plugins/crossword/lib/crossword.js +170 -0
  22. package/plugins/crossword/lib/store.js +56 -0
  23. package/plugins/crossword/plugin.js +241 -0
  24. package/plugins/crossword/plugin.json +78 -0
  25. package/plugins/crossword/public/crossword.css +1 -0
  26. package/plugins/crossword/public/crossword.js +57 -0
  27. package/plugins/crossword/public/lib/layout.js +199 -0
  28. package/plugins/quiz/admin/css/index.css +1 -0
  29. package/plugins/quiz/admin/templates/quiz.html +1 -0
  30. package/plugins/quiz/admin/views/quiz.js +352 -0
  31. package/plugins/quiz/collections/quiz-plays.json +20 -0
  32. package/plugins/quiz/collections/quiz-quizzes.json +17 -0
  33. package/plugins/quiz/config.js +6 -0
  34. package/plugins/quiz/inject/body.html +1 -0
  35. package/plugins/quiz/lib/engine.js +115 -0
  36. package/plugins/quiz/lib/quiz.js +225 -0
  37. package/plugins/quiz/lib/store.js +56 -0
  38. package/plugins/quiz/plugin.js +241 -0
  39. package/plugins/quiz/plugin.json +78 -0
  40. package/plugins/quiz/public/quiz.css +1 -0
  41. package/plugins/quiz/public/quiz.js +80 -0
@@ -0,0 +1,80 @@
1
+ import{Countdown as C,addStyles as N,celebrate as Y,configOf as Q,fmtDuration as $,memory as f,mountBoards as W,mountLeaderboard as B,ordinal as I,request as h,uid as D}from"/plugins/_lib/public/games/games.js";const s=window.M,c="/api/plugins/quiz/public",A="games.name",z=n=>`quiz.play.${n}`,F=`
2
+ <p class="gm-muted qz-live" role="status" aria-live="polite" data-bind-text="live.value"></p>
3
+
4
+ <section class="gm-card" hidden data-bind-hidden="screen.value !== 'loading'"><p class="gm-muted">Loading the quiz\u2026</p></section>
5
+
6
+ <section class="gm-card" hidden data-bind-hidden="screen.value !== 'error'">
7
+ <p class="gm-error" data-bind-text="problem.value"></p>
8
+ </section>
9
+
10
+ <section class="gm-card qz-intro" hidden data-bind-hidden="screen.value !== 'intro'">
11
+ <h2 class="gm-title" data-bind-text="info.value.title"></h2>
12
+ <p class="gm-intro" hidden data-bind-hidden="!info.value.intro" data-bind-text="info.value.intro"></p>
13
+ <ul class="gm-facts" data-each="facts.value key=k"><li data-bind-text="k"></li></ul>
14
+ <p class="gm-note" hidden data-bind-hidden="info.value.open" data-bind-text="info.value.reason"></p>
15
+ <form class="gm-start" hidden data-bind-hidden="!info.value.open" data-on-submit="start" novalidate>
16
+ <div class="gm-field">
17
+ <label for="%U%-name">Your name</label>
18
+ <input id="%U%-name" class="gm-input" maxlength="24" autocomplete="nickname" data-model="name.value"
19
+ data-bind-aria-invalid="problem.value ? 'true' : false" aria-describedby="%U%-err">
20
+ </div>
21
+ <div class="gm-hp" aria-hidden="true"><label>Website <input tabindex="-1" autocomplete="off" data-model="website.value"></label></div>
22
+ <button type="submit" class="btn btn-primary" data-bind-disabled="busy.value">Start the quiz</button>
23
+ </form>
24
+ <p class="gm-error" id="%U%-err" hidden data-bind-hidden="!problem.value" data-bind-text="problem.value"></p>
25
+ </section>
26
+
27
+ <section class="gm-card qz-question" hidden data-bind-hidden="screen.value !== 'question'" aria-labelledby="%U%-q">
28
+ <div class="gm-bar">
29
+ <span class="gm-progress" data-bind-text="progress.value"></span>
30
+ <span class="gm-clock" hidden data-bind-hidden="!clockShown.value" data-bind-class="clockLow.value && 'is-low'"
31
+ role="timer" data-bind-aria-label="'Time left ' + clockText.value" data-bind-text="clockText.value"></span>
32
+ </div>
33
+ <div class="gm-track" aria-hidden="true"><span data-bind-style-width="trackWidth.value"></span></div>
34
+ <h2 class="qz-q" id="%U%-q" tabindex="-1" data-bind-text="q.value.text"></h2>
35
+ <img class="qz-image" alt="" hidden data-bind-hidden="!q.value.image" data-bind-src="q.value.image || false">
36
+ <p class="gm-muted qz-hint" hidden data-bind-hidden="q.value.kind !== 'multiple'">Choose every right answer, then press Answer.</p>
37
+ <div class="qz-options" hidden data-bind-hidden="q.value.kind === 'text'" data-each="options.value key=id">
38
+ <button type="button" class="qz-option" data-bind-class="picked && 'is-picked'" data-bind-aria-pressed="picked ? 'true' : 'false'"
39
+ data-bind-disabled="$root.busy.value" data-on-click="$root.choose($data)">
40
+ <span class="qz-letter" data-bind-text="letter"></span><span data-bind-text="text"></span>
41
+ </button>
42
+ </div>
43
+ <form class="qz-typed" hidden data-bind-hidden="q.value.kind !== 'text'" data-on-submit="sendTyped" novalidate>
44
+ <label class="gm-muted" for="%U%-typed">Your answer</label>
45
+ <input id="%U%-typed" class="gm-input" maxlength="200" autocomplete="off" data-model="typed.value">
46
+ </form>
47
+ <div class="gm-actions" hidden data-bind-hidden="q.value.kind !== 'multiple' && q.value.kind !== 'text'">
48
+ <button type="button" class="btn btn-primary" data-bind-disabled="busy.value" data-on-click="sendPicked">Answer</button>
49
+ </div>
50
+ </section>
51
+
52
+ <section class="gm-card qz-feedback" hidden data-bind-hidden="screen.value !== 'feedback'" aria-labelledby="%U%-fb">
53
+ <h2 class="qz-verdict" id="%U%-fb" tabindex="-1" data-bind-class="fb.value.correct ? 'is-right' : 'is-wrong'" data-bind-text="fb.value.title"></h2>
54
+ <p hidden data-bind-hidden="fb.value.correct || !fb.value.rightAnswer">The answer was <strong data-bind-text="fb.value.rightAnswer"></strong>.</p>
55
+ <p class="gm-muted" hidden data-bind-hidden="!fb.value.explanation" data-bind-text="fb.value.explanation"></p>
56
+ <div class="gm-actions"><button type="button" class="btn btn-primary" data-on-click="next" data-bind-text="fb.value.nextLabel"></button></div>
57
+ </section>
58
+
59
+ <section class="gm-card qz-result" hidden data-bind-hidden="screen.value !== 'result'" aria-labelledby="%U%-res">
60
+ <h2 class="gm-title" id="%U%-res" tabindex="-1" data-bind-text="res.value.title"></h2>
61
+ <p class="gm-result-score" data-bind-text="res.value.scoreText"></p>
62
+ <p class="gm-muted" data-bind-text="res.value.detail"></p>
63
+ <p class="gm-result-place" hidden data-bind-hidden="!res.value.placeText" data-bind-text="res.value.placeText"></p>
64
+ <div class="gm-actions">
65
+ <button type="button" class="btn btn-primary" data-on-click="again">Play again</button>
66
+ </div>
67
+ <div class="qz-review" hidden data-bind-hidden="!review.value.length">
68
+ <h3>Your answers</h3>
69
+ <ol class="qz-review-list" data-each="review.value key=k">
70
+ <li data-bind-class="correct ? 'is-right' : 'is-wrong'">
71
+ <p class="qz-review-q" data-bind-text="text"></p>
72
+ <p class="qz-review-a"><span data-bind-text="mark"></span> <span data-bind-text="yoursText"></span></p>
73
+ <p class="qz-review-a" hidden data-bind-hidden="correct">Answer: <strong data-bind-text="right"></strong></p>
74
+ <p class="gm-muted" hidden data-bind-hidden="!explanation" data-bind-text="explanation"></p>
75
+ </li>
76
+ </ol>
77
+ </div>
78
+ </section>
79
+
80
+ <div class="qz-board-slot"></div>`,H="ABCDEFGH";function K(n){const l=[`${n.questions} question${n.questions===1?"":"s"}`];return n.timeLimit&&l.push(`${$(n.timeLimit*1e3)} to finish`),n.questionSeconds?l.push(`${n.questionSeconds} seconds a question`):n.perQuestionTimes&&l.push("Some questions are timed"),n.attemptsPerName===1?l.push("One go each"):n.attemptsPerName>1&&l.push(`${n.attemptsPerName} goes each`),n.leaderboard&&l.push("Fastest top score wins"),l.map(g=>({k:g}))}function G(n){const l=Q(n,"data-quiz");if(!l?.slug||!s)return;N(["/plugins/quiz/public/quiz.css?v=1.0.0"]);const g=D("qz");n.innerHTML=F.replaceAll("%U%",g);const t={screen:s.observable("loading"),live:s.observable(""),problem:s.observable(""),busy:s.observable(!1),info:s.observable({title:"",intro:"",open:!1,reason:""}),facts:s.observable([]),name:s.observable(String(f.get(A)||"")),website:s.observable(""),progress:s.observable(""),q:s.observable({text:"",kind:"single",image:""}),options:s.observable([]),typed:s.observable(""),clockText:s.observable(""),clockShown:s.observable(!1),clockLow:s.observable(!1),trackWidth:s.observable("0%"),fb:s.observable({}),res:s.observable({}),review:s.observable([])};let r=f.get(z(l.slug))||null,k=null,d=null,u=null,m=null;const b=e=>setTimeout(()=>n.querySelector(`#${g}-${e}`)?.focus({preventScroll:!1}),30),q=e=>{t.live.value="",setTimeout(()=>{t.live.value=e},20)};function T(){const e=u?.left.peek()??0,i=m?.left.peek()??0,a=d?.questionLeftMs!=null&&(d?.totalLeftMs==null||e<=i),o=a?e:i,M=a?(d?.question?.seconds||0)*1e3:(t.info.peek().timeLimit||0)*1e3;t.clockShown.value=d?.questionLeftMs!=null||d?.totalLeftMs!=null,t.clockText.value=$(o),t.clockLow.value=o>0&&o<=5e3,t.trackWidth.value=M?`${Math.max(0,Math.min(100,o/M*100)).toFixed(1)}%`:`${(d?.index||0)/(d?.total||1)*100}%`}function y(){u?.stop(),m?.stop()}u=new C(()=>{t.screen.peek()==="question"&&v(null,{timeout:!0})}),m=new C(()=>{["question","feedback"].includes(t.screen.peek())&&setTimeout(p,600)});const R=s.effect(()=>{u.left.value,m.left.value,T()});function x(){f.remove(z(l.slug)),r=null}async function L(){const e=await h(`${c}/quiz/${encodeURIComponent(l.slug)}`);return t.info.value=e,t.facts.value=K(e),e.leaderboard&&!k&&(k=B(n.querySelector(".qz-board-slot"),{url:`${c}/quiz/${encodeURIComponent(l.slug)}/leaderboard`,limit:e.leaderboardSize,title:"Leaderboard"})),e}async function p(){if(!r){t.screen.value="intro";return}try{const e=await h(`${c}/play/${encodeURIComponent(r.playId)}`,{token:r.token});if(d=e,e.status!=="playing")return y(),w();E(e)}catch(e){if(e.code==="gone"||e.status===404){x(),t.screen.value="intro";return}t.problem.value=e.message,t.screen.value="error"}}function E(e){const i=e.question;t.q.value={text:i.text,kind:i.kind,image:i.image||""},t.options.value=i.options.map((a,o)=>({id:a.id,text:a.text,letter:H[o]||"",picked:!1})),t.typed.value="",t.progress.value=`Question ${e.index+1} of ${e.total}`,t.busy.value=!1,t.screen.value="question",u.start(e.questionLeftMs??0),m.start(e.totalLeftMs??0),T(),i.kind==="text"?b("typed"):b("q"),q(`Question ${e.index+1} of ${e.total}. ${i.text}`)}async function v(e,{timeout:i=!1}={}){if(!(t.busy.peek()||!d?.question)){t.busy.value=!0,u.stop();try{const a=await h(`${c}/play/${encodeURIComponent(r.playId)}/answer`,{method:"POST",token:r.token,body:{q:d.question.id,answer:e}});d=a.play;const o=a.play.status!=="playing";if(a.feedback){t.fb.value={correct:a.correct,title:a.correct?"Right!":i||a.late?"Out of time":"Not quite",rightAnswer:a.feedback.rightAnswer,explanation:a.feedback.explanation,nextLabel:o?"See your score":"Next question"},t.screen.value="feedback",q(t.fb.peek().title),b("fb");return}if(o)return y(),w();await p()}catch(a){if(a.code==="not-current"||a.code==="finished")return p();t.busy.value=!1,t.live.value=a.message}}}async function w(){y();try{const e=await h(`${c}/play/${encodeURIComponent(r.playId)}/result`,{token:r.token}),i=e.place?`You came ${I(e.place)} of all time${e.placeToday&&e.placeToday!==e.place?`, ${I(e.placeToday)} today`:""}.`:"";t.res.value={title:e.timeUp?`Time's up, ${e.name}!`:`Well done, ${e.name}!`,scoreText:`${e.score} / ${e.maxScore}`,detail:`${e.correct} of ${e.total} right in ${$(e.timeMs)}.`,placeText:i},t.review.value=(e.review||[]).map((a,o)=>({k:`r${o}`,text:`${o+1}. ${a.text}`,correct:a.correct,right:a.right,explanation:a.explanation,mark:a.correct?"\u2713":"\u2717",yoursText:a.yours?`You said: ${a.yours}`:a.late?"Out of time":"No answer"})),t.screen.value="result",q(`${t.res.peek().title} ${t.res.peek().detail} ${i}`),b("res"),k?.refresh(r.playId),e.place===1&&Y()}catch(e){if(e.status===404){x(),t.screen.value="intro";return}t.problem.value=e.message,t.screen.value="error"}}async function O(){if(!t.busy.peek()){t.problem.value="",t.busy.value=!0;try{const e=await h(`${c}/quiz/${encodeURIComponent(l.slug)}/start`,{method:"POST",body:{name:t.name.peek(),website:t.website.peek()}});f.set(A,e.name),r={playId:e.playId,token:e.token},f.set(z(l.slug),r),await p()}catch(e){t.problem.value=e.message,t.busy.value=!1,b("name")}}}const U={start(){return O(),!1},choose(e){if(!t.busy.peek()){if(t.q.peek().kind==="multiple"){t.options.value=t.options.peek().map(i=>i.id===e.id?{...i,picked:!i.picked}:i);return}v(e.id)}},sendPicked(){if(t.q.peek().kind==="text")return v(t.typed.peek());v(t.options.peek().filter(e=>e.picked).map(e=>e.id))},sendTyped(){return v(t.typed.peek()),!1},next(){if(d?.status!=="playing")return w();p()},async again(){x(),t.problem.value="",t.busy.value=!1,await L().catch(()=>{}),t.screen.value="intro",b("name")}};s.applyBindings({...t,...U},n,{methods:U}),L().then(p).catch(e=>{t.problem.value=e.status===404?"This quiz is not available.":e.message,t.screen.value="error"});const S=new MutationObserver(()=>{n.isConnected||(y(),R?.(),S.disconnect())});S.observe(document.body,{childList:!0,subtree:!0})}function P(){for(const n of document.querySelectorAll("[data-quiz]"))n.dataset.gmMounted||(n.dataset.gmMounted="1",G(n));W("data-quiz-board",n=>`${c}/quiz/${encodeURIComponent(n)}/leaderboard`)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",P):P();