@webwriter/quiz 1.1.3 → 2.0.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.
Files changed (167) hide show
  1. package/.github/workflows/publish.yml +8 -0
  2. package/LICENSE +6 -6
  3. package/README.md +778 -0
  4. package/assets/icons/grip-horizontal.svg +8 -0
  5. package/assets/icons/grip-vertical.svg +8 -0
  6. package/assets/icons/truefalse.svg +6 -0
  7. package/custom-elements.json +2779 -0
  8. package/custom.d.ts +180 -176
  9. package/dist/api/index.d.ts +27 -0
  10. package/dist/api/index.js +40 -0
  11. package/dist/migrate.js +1434 -0
  12. package/dist/widgets/webwriter-choice-item.js +4036 -3895
  13. package/dist/widgets/webwriter-choice.js +2269 -5821
  14. package/dist/widgets/webwriter-gap-item.js +3987 -0
  15. package/dist/widgets/webwriter-gap.js +2908 -0
  16. package/dist/widgets/webwriter-mark.js +4335 -3489
  17. package/dist/widgets/webwriter-order-item.js +4014 -3306
  18. package/dist/widgets/webwriter-order.js +2944 -4386
  19. package/dist/widgets/webwriter-pairing-item.js +1058 -2421
  20. package/dist/widgets/webwriter-pairing.js +2993 -4383
  21. package/dist/widgets/webwriter-quiz-hint.js +2469 -0
  22. package/dist/widgets/webwriter-quiz.js +4933 -6099
  23. package/dist/widgets/webwriter-speech.js +6077 -5230
  24. package/dist/widgets/webwriter-task-prompt.js +4775 -2165
  25. package/dist/widgets/webwriter-task.js +2364 -8382
  26. package/dist/widgets/webwriter-text.js +4169 -4337
  27. package/dist/widgets/webwriter-true-false.js +4674 -0
  28. package/editing-config.json +106 -0
  29. package/lit-localize.json +21 -15
  30. package/localization/de.xlf +407 -74
  31. package/localization/es.xlf +407 -69
  32. package/localization/fr.xlf +407 -69
  33. package/localization/generated/de.ts +112 -29
  34. package/localization/generated/es.ts +112 -29
  35. package/localization/generated/fr.ts +112 -29
  36. package/localization/generated/index.d.ts +5 -0
  37. package/localization/generated/index.js +2 -2
  38. package/localization/generated/it.ts +112 -29
  39. package/localization/generated/locale-codes.js +31 -79
  40. package/localization/generated/nl.ts +112 -29
  41. package/localization/it.xlf +407 -69
  42. package/localization/nl.xlf +407 -69
  43. package/package.json +119 -252
  44. package/scripts/apply-translations.mjs +97 -0
  45. package/src/api/index.ts +72 -0
  46. package/src/lib/artboard.ts +686 -0
  47. package/src/lib/draggable-grid.ts +432 -0
  48. package/src/lib/encrypted-property.ts +202 -0
  49. package/src/lib/hint-popup.ts +152 -0
  50. package/src/lib/utils.ts +97 -0
  51. package/src/lib/webwriter-quirks.ts +121 -0
  52. package/src/migrate/index.ts +622 -0
  53. package/src/migrate/legacy-solution.ts +362 -0
  54. package/src/snippets/order.html +9 -8
  55. package/src/widgets/choice/webwriter-choice-item.ts +316 -0
  56. package/src/widgets/choice/webwriter-choice.ts +328 -0
  57. package/src/widgets/choice/webwriter-true-false.ts +113 -0
  58. package/src/widgets/dnd/webwriter-dnd-draggable.ts +28 -0
  59. package/src/widgets/dnd/webwriter-dnd-dropzone.ts +9 -0
  60. package/src/widgets/dnd/webwriter-dnd.ts +36 -0
  61. package/src/widgets/gap/webwriter-gap-item.ts +250 -0
  62. package/src/widgets/gap/webwriter-gap.ts +447 -0
  63. package/src/widgets/order/webwriter-order-item.ts +243 -0
  64. package/src/widgets/order/webwriter-order.ts +282 -0
  65. package/src/widgets/pairing/webwriter-pairing-item.ts +46 -0
  66. package/src/widgets/pairing/webwriter-pairing.ts +934 -0
  67. package/src/widgets/speech/audio-player.ts +325 -0
  68. package/src/widgets/speech/audio-recorder.ts +285 -0
  69. package/src/widgets/speech/waveform.ts +113 -0
  70. package/src/widgets/speech/webwriter-speech.ts +87 -0
  71. package/src/widgets/webwriter-mark.ts +374 -432
  72. package/src/widgets/webwriter-quiz-hint.ts +36 -0
  73. package/src/widgets/webwriter-quiz.ts +320 -282
  74. package/src/widgets/webwriter-task-prompt.ts +49 -24
  75. package/src/widgets/webwriter-task.ts +216 -547
  76. package/src/widgets/webwriter-text.ts +291 -156
  77. package/tsconfig.api.json +12 -0
  78. package/tsconfig.json +8 -7
  79. package/dist/widgets/webwriter-choice.css +0 -444
  80. package/dist/widgets/webwriter-cloze-gap.css +0 -444
  81. package/dist/widgets/webwriter-cloze-gap.js +0 -5866
  82. package/dist/widgets/webwriter-cloze.css +0 -444
  83. package/dist/widgets/webwriter-cloze.js +0 -3786
  84. package/dist/widgets/webwriter-mark.css +0 -444
  85. package/dist/widgets/webwriter-order.css +0 -444
  86. package/dist/widgets/webwriter-pairing-item.css +0 -444
  87. package/dist/widgets/webwriter-pairing.css +0 -444
  88. package/dist/widgets/webwriter-quiz.css +0 -444
  89. package/dist/widgets/webwriter-task-explainer.css +0 -444
  90. package/dist/widgets/webwriter-task-explainer.js +0 -2872
  91. package/dist/widgets/webwriter-task-hint.css +0 -444
  92. package/dist/widgets/webwriter-task-hint.js +0 -2811
  93. package/dist/widgets/webwriter-task.css +0 -444
  94. package/dist/widgets/webwriter-text.css +0 -444
  95. package/localization/bg.xlf +0 -69
  96. package/localization/cs.xlf +0 -69
  97. package/localization/da.xlf +0 -69
  98. package/localization/el.xlf +0 -69
  99. package/localization/et.xlf +0 -69
  100. package/localization/fi.xlf +0 -69
  101. package/localization/generated/bg.ts +0 -30
  102. package/localization/generated/cs.ts +0 -30
  103. package/localization/generated/da.ts +0 -30
  104. package/localization/generated/el.ts +0 -30
  105. package/localization/generated/et.ts +0 -30
  106. package/localization/generated/fi.ts +0 -30
  107. package/localization/generated/hu.ts +0 -30
  108. package/localization/generated/id.ts +0 -30
  109. package/localization/generated/ja.ts +0 -30
  110. package/localization/generated/ko.ts +0 -30
  111. package/localization/generated/lt.ts +0 -30
  112. package/localization/generated/lv.ts +0 -30
  113. package/localization/generated/nb.ts +0 -30
  114. package/localization/generated/pl.ts +0 -30
  115. package/localization/generated/pt-BR.ts +0 -30
  116. package/localization/generated/pt-PT.ts +0 -30
  117. package/localization/generated/ro.ts +0 -30
  118. package/localization/generated/ru.ts +0 -30
  119. package/localization/generated/sk.ts +0 -30
  120. package/localization/generated/sl.ts +0 -30
  121. package/localization/generated/sv.ts +0 -30
  122. package/localization/generated/tr.ts +0 -30
  123. package/localization/generated/uk.ts +0 -30
  124. package/localization/generated/zh-hans.ts +0 -30
  125. package/localization/hu.xlf +0 -69
  126. package/localization/id.xlf +0 -69
  127. package/localization/ja.xlf +0 -69
  128. package/localization/ko.xlf +0 -69
  129. package/localization/lt.xlf +0 -69
  130. package/localization/lv.xlf +0 -69
  131. package/localization/nb.xlf +0 -69
  132. package/localization/pl.xlf +0 -69
  133. package/localization/pt-BR.xlf +0 -69
  134. package/localization/pt-PT.xlf +0 -69
  135. package/localization/ro.xlf +0 -69
  136. package/localization/ru.xlf +0 -69
  137. package/localization/sk.xlf +0 -69
  138. package/localization/sl.xlf +0 -69
  139. package/localization/sv.xlf +0 -69
  140. package/localization/tr.xlf +0 -69
  141. package/localization/uk.xlf +0 -69
  142. package/localization/zh-hans.xlf +0 -69
  143. package/src/lib/combobox.ts +0 -456
  144. package/src/lib/highlighter-fill.svg +0 -6
  145. package/src/snippets/Beispiel-Choice.html +0 -57
  146. package/src/snippets/Beispiel-Mark.html +0 -14
  147. package/src/snippets/Beispiel-Order.html +0 -52
  148. package/src/snippets/Beispiel-Text.html +0 -20
  149. package/src/snippets/blank.html +0 -1
  150. package/src/snippets/choice.html +0 -8
  151. package/src/snippets/cloze.html +0 -4
  152. package/src/snippets/mark.html +0 -4
  153. package/src/snippets/pairing.html +0 -10
  154. package/src/snippets/speech.html +0 -4
  155. package/src/snippets/text.html +0 -4
  156. package/src/snippets/wordsearch.html +0 -4
  157. package/src/widgets/webwriter-choice-item.ts +0 -254
  158. package/src/widgets/webwriter-choice.ts +0 -353
  159. package/src/widgets/webwriter-cloze-gap.ts +0 -216
  160. package/src/widgets/webwriter-cloze.ts +0 -136
  161. package/src/widgets/webwriter-order-item.ts +0 -441
  162. package/src/widgets/webwriter-order.ts +0 -327
  163. package/src/widgets/webwriter-pairing-item.ts +0 -161
  164. package/src/widgets/webwriter-pairing.ts +0 -193
  165. package/src/widgets/webwriter-speech.ts +0 -273
  166. package/src/widgets/webwriter-task-explainer.ts +0 -43
  167. package/src/widgets/webwriter-task-hint.ts +0 -23
package/package.json CHANGED
@@ -1,259 +1,126 @@
1
1
  {
2
- "name": "@webwriter/quiz",
3
- "version": "1.1.3",
4
- "description": "Add interactive tasks (multiple choice, order, free text, highlighting, or speech input). Make a quiz out of multiple tasks.",
5
- "keywords": [
6
- "webwriter-widget",
7
- "widget-practical",
8
- "widget-lang-en",
9
- "widget-lang-de",
10
- "widget-lang-bg",
11
- "widget-lang-cs",
12
- "widget-lang-da",
13
- "widget-lang-el",
14
- "widget-lang-es",
15
- "widget-lang-et",
16
- "widget-lang-fi",
17
- "widget-lang-fr",
18
- "widget-lang-hu",
19
- "widget-lang-id",
20
- "widget-lang-it",
21
- "widget-lang-ja",
22
- "widget-lang-ko",
23
- "widget-lang-lt",
24
- "widget-lang-lv",
25
- "widget-lang-nb",
26
- "widget-lang-nl",
27
- "widget-lang-pl",
28
- "widget-lang-pt",
29
- "widget-lang-ro",
30
- "widget-lang-ru",
31
- "widget-lang-sk",
32
- "widget-lang-sl",
33
- "widget-lang-sv",
34
- "widget-lang-tr",
35
- "widget-lang-uk",
36
- "widget-lang-zh",
37
- "isced2011-1",
38
- "isced2011-2",
39
- "isced2011-3",
40
- "isced2011-4",
41
- "isced2011-5",
42
- "isced2011-6",
43
- "isced2011-7",
44
- "isced2011-8",
45
- "quiz",
46
- "choice",
47
- "test"
48
- ],
49
- "license": "MIT",
50
- "author": "Frederic Salmen <frederic@fsalmen.de>",
51
- "scripts": {
52
- "prepublishOnly": "npx @webwriter/build",
53
- "dev": "npx @webwriter/build dev",
54
- "localize": "lit-localize extract && npx @webwriter/build localize && lit-localize build"
2
+ "name": "@webwriter/quiz",
3
+ "version": "2.0.0",
4
+ "description": "Add interactive tasks (multiple choice, order, free text, highlighting, or speech input). Make a quiz out of multiple tasks.",
5
+ "keywords": [
6
+ "webwriter-widget",
7
+ "widget-practical",
8
+ "widget-lang-en",
9
+ "widget-lang-de",
10
+ "widget-lang-fr",
11
+ "widget-lang-es",
12
+ "widget-lang-it",
13
+ "widget-lang-nl",
14
+ "isced2011-1",
15
+ "isced2011-2",
16
+ "isced2011-3",
17
+ "isced2011-4",
18
+ "isced2011-5",
19
+ "isced2011-6",
20
+ "isced2011-7",
21
+ "isced2011-8",
22
+ "quiz",
23
+ "choice",
24
+ "test"
25
+ ],
26
+ "license": "MIT",
27
+ "author": "Frederic Salmen <frederic@fsalmen.de>",
28
+ "repository": "https://github.com/webwriter-app/quiz",
29
+ "scripts": {
30
+ "prepublishOnly": "npx @webwriter/build && npm run build:api",
31
+ "build:api": "tsc -p tsconfig.api.json",
32
+ "dev": "npx @webwriter/build dev",
33
+ "localize": "npx @lit/localize-tools extract && npx @lit/localize-tools build",
34
+ "localize:apply": "node scripts/apply-translations.mjs"
35
+ },
36
+ "dependencies": {
37
+ "@shoelace-style/shoelace": "^2.20.1",
38
+ "@webwriter/lit": "^2",
39
+ "bootstrap-icons": "^1.13.1",
40
+ "lit": "^3"
41
+ },
42
+ "devDependencies": {
43
+ "@lit/localize-tools": "^0.8",
44
+ "typescript": "~5.9.0"
45
+ },
46
+ "exports": {
47
+ "./api": {
48
+ "types": "./dist/api/index.d.ts",
49
+ "source": "./src/api/index.ts",
50
+ "import": "./dist/api/index.js",
51
+ "default": "./dist/api/index.js"
55
52
  },
56
- "dependencies": {
57
- "@shoelace-style/shoelace": "^2.13.1",
58
- "@shopify/draggable": "^1.1.3",
59
- "@webwriter/lit": "^2.1.2",
60
- "bootstrap-icons": "^1.11.3",
61
- "interactjs": "^1.10.26",
62
- "lit": "^3.2.0",
63
- "minimasonry": "^1.3.2",
64
- "sortablejs": "^1.15.2"
53
+ "./widgets/webwriter-quiz.*": {
54
+ "source": "./src/widgets/webwriter-quiz.ts",
55
+ "default": "./dist/widgets/webwriter-quiz.*"
65
56
  },
66
- "devDependencies": {
67
- "@lit/localize": "^0.12.2",
68
- "@lit/localize-tools": "^0.8.0",
69
- "@types/minimasonry": "^1.3.5",
70
- "@types/sortablejs": "^1.15.7"
57
+ "./widgets/webwriter-task.*": {
58
+ "source": "./src/widgets/webwriter-task.ts",
59
+ "default": "./dist/widgets/webwriter-task.*"
71
60
  },
72
- "peerDependencies": {
73
- "@webwriter/phet-simulation": "^1.0.0"
61
+ "./widgets/webwriter-task-prompt.*": {
62
+ "source": "./src/widgets/webwriter-task-prompt.ts",
63
+ "default": "./dist/widgets/webwriter-task-prompt.*"
74
64
  },
75
- "exports": {
76
- "./widgets/webwriter-quiz.*": {
77
- "source": "./src/widgets/webwriter-quiz.ts",
78
- "default": "./dist/widgets/webwriter-quiz.*"
79
- },
80
- "./snippets/blank.html": "./src/snippets/blank.html",
81
- "./snippets/choice.html": "./src/snippets/choice.html",
82
- "./snippets/order.html": "./src/snippets/order.html",
83
- "./snippets/text.html": "./src/snippets/text.html",
84
- "./snippets/mark.html": "./src/snippets/mark.html",
85
- "./snippets/speech.html": "./src/snippets/speech.html",
86
- "./snippets/beispiel-choice.html": "./src/snippets/Beispiel-Choice.html",
87
- "./snippets/beispiel-order.html": "./src/snippets/Beispiel-Order.html",
88
- "./snippets/beispiel-text.html": "./src/snippets/Beispiel-Text.html",
89
- "./snippets/beispiel-mark.html": "./src/snippets/Beispiel-Mark.html",
90
- "./widgets/webwriter-task.*": {
91
- "source": "./src/widgets/webwriter-task.ts",
92
- "default": "./dist/widgets/webwriter-task.*"
93
- },
94
- "./widgets/webwriter-task-prompt.*": {
95
- "source": "./src/widgets/webwriter-task-prompt.ts",
96
- "default": "./dist/widgets/webwriter-task-prompt.*"
97
- },
98
- "./widgets/webwriter-task-hint.*": {
99
- "source": "./src/widgets/webwriter-task-hint.ts",
100
- "default": "./dist/widgets/webwriter-task-hint.*"
101
- },
102
- "./widgets/webwriter-task-explainer.*": {
103
- "source": "./src/widgets/webwriter-task-explainer.ts",
104
- "default": "./dist/widgets/webwriter-task-explainer.*"
105
- },
106
- "./widgets/webwriter-choice.*": {
107
- "source": "./src/widgets/webwriter-choice.ts",
108
- "default": "./dist/widgets/webwriter-choice.*"
109
- },
110
- "./widgets/webwriter-choice-item.*": {
111
- "source": "./src/widgets/webwriter-choice-item.ts",
112
- "default": "./dist/widgets/webwriter-choice-item.*"
113
- },
114
- "./widgets/webwriter-order.*": {
115
- "source": "./src/widgets/webwriter-order.ts",
116
- "default": "./dist/widgets/webwriter-order.*"
117
- },
118
- "./widgets/webwriter-order-item.*": {
119
- "source": "./src/widgets/webwriter-order-item.ts",
120
- "default": "./dist/widgets/webwriter-order-item.*"
121
- },
122
- "./widgets/webwriter-pairing.*": {
123
- "source": "./src/widgets/webwriter-pairing.ts",
124
- "default": "./dist/widgets/webwriter-pairing.*"
125
- },
126
- "./widgets/webwriter-pairing-item.*": {
127
- "source": "./src/widgets/webwriter-pairing-item.ts",
128
- "default": "./dist/widgets/webwriter-pairing-item.*"
129
- },
130
- "./widgets/webwriter-text.*": {
131
- "source": "./src/widgets/webwriter-text.ts",
132
- "default": "./dist/widgets/webwriter-text.*"
133
- },
134
- "./widgets/webwriter-mark.*": {
135
- "source": "./src/widgets/webwriter-mark.ts",
136
- "default": "./dist/widgets/webwriter-mark.*"
137
- },
138
- "./widgets/webwriter-speech.*": {
139
- "source": "./src/widgets/webwriter-speech.ts",
140
- "default": "./dist/widgets/webwriter-speech.*"
141
- },
142
- "./widgets/webwriter-cloze.*": {
143
- "source": "./src/widgets/webwriter-cloze.ts",
144
- "default": "./dist/widgets/webwriter-cloze.*"
145
- },
146
- "./widgets/webwriter-cloze-gap.*": {
147
- "source": "./src/widgets/webwriter-cloze-gap.ts",
148
- "default": "./dist/widgets/webwriter-cloze-gap.*"
149
- },
150
- "./package.json": "./package.json",
151
- "./icon": "./icon.svg"
65
+ "./widgets/webwriter-quiz-hint.*": {
66
+ "source": "./src/widgets/webwriter-quiz-hint.ts",
67
+ "default": "./dist/widgets/webwriter-quiz-hint.*"
152
68
  },
153
- "editingConfig": {
154
- ".": {
155
- "label": {
156
- "_": "Quiz"
157
- },
158
- "description": {
159
- "de": "Fügen Sie interaktive Aufgaben hinzu (Multiple Choice, Reihenfolge, freier Text, Hervorhebung oder Spracheingabe). Erstellen Sie ein Quiz aus mehreren Aufgaben."
160
- }
161
- },
162
- "./widgets/webwriter-choice-item": {
163
- "group": "",
164
- "selectable": false,
165
- "draggable": false,
166
- "isolating": false,
167
- "content": "(p | flow)+",
168
- "uninsertable": true
169
- },
170
- "./widgets/webwriter-choice": {
171
- "group": "answer",
172
- "content": "webwriter-choice-item+",
173
- "uninsertable": true
174
- },
175
- "./widgets/webwriter-order-item": {
176
- "group": "",
177
- "selectable": false,
178
- "draggable": true,
179
- "isolating": false,
180
- "content": "(p | flow)+",
181
- "uninsertable": true
182
- },
183
- "./widgets/webwriter-order": {
184
- "group": "answer",
185
- "content": "webwriter-order-item+",
186
- "uninsertable": true
187
- },
188
- "./widgets/webwriter-pairing": {
189
- "group": "answer",
190
- "isolating": true,
191
- "content": "webwriter-pairing-item+",
192
- "uninsertable": true
193
- },
194
- "./widgets/webwriter-pairing-item": {
195
- "group": "",
196
- "draggable": true,
197
- "isolating": true,
198
- "content": "picture | flow",
199
- "uninsertable": true
200
- },
201
- "./widgets/webwriter-text": {
202
- "group": "answer",
203
- "uninsertable": true
204
- },
205
- "./widgets/webwriter-mark": {
206
- "group": "answer inlinecontainer",
207
- "defining": true,
208
- "isolating": true,
209
- "content": "(text | br | wbr)*",
210
- "marks": "",
211
- "uninsertable": true
212
- },
213
- "./widgets/webwriter-speech": {
214
- "group": "answer",
215
- "uninsertable": true
216
- },
217
- "./widgets/webwriter-cloze": {
218
- "group": "answer",
219
- "isolating": true,
220
- "content": "p",
221
- "uninsertable": true
222
- },
223
- "./widgets/webwriter-cloze-gap": {
224
- "group": "phrasing",
225
- "inline": true,
226
- "isolating": false,
227
- "uninsertable": true
228
- },
229
- "./widgets/webwriter-task": {
230
- "isolating": true,
231
- "content": "webwriter-task-prompt webwriter-task-hint? answer",
232
- "uninsertable": true
233
- },
234
- "./widgets/webwriter-task-prompt": {
235
- "group": "",
236
- "defining": true,
237
- "content": "(p | flow)+",
238
- "uninsertable": true
239
- },
240
- "./widgets/webwriter-task-hint": {
241
- "group": "containerinline",
242
- "defining": true,
243
- "isolating": true,
244
- "content": "p",
245
- "uninsertable": true
246
- },
247
- "./widgets/webwriter-task-explainer": {
248
- "group": "",
249
- "defining": true,
250
- "isolating": true,
251
- "content": "(p | flow)+",
252
- "uninsertable": true
253
- },
254
- "./widgets/webwriter-quiz": {
255
- "content": "webwriter-task*",
256
- "uninsertable": true
257
- }
258
- }
69
+ "./widgets/webwriter-true-false.*": {
70
+ "source": "./src/widgets/choice/webwriter-true-false.ts",
71
+ "default": "./dist/widgets/webwriter-true-false.*"
72
+ },
73
+ "./widgets/webwriter-choice.*": {
74
+ "source": "./src/widgets/choice/webwriter-choice.ts",
75
+ "default": "./dist/widgets/webwriter-choice.*"
76
+ },
77
+ "./widgets/webwriter-choice-item.*": {
78
+ "source": "./src/widgets/choice/webwriter-choice-item.ts",
79
+ "default": "./dist/widgets/webwriter-choice-item.*"
80
+ },
81
+ "./widgets/webwriter-order.*": {
82
+ "source": "./src/widgets/order/webwriter-order.ts",
83
+ "default": "./dist/widgets/webwriter-order.*"
84
+ },
85
+ "./widgets/webwriter-order-item.*": {
86
+ "source": "./src/widgets/order/webwriter-order-item.ts",
87
+ "default": "./dist/widgets/webwriter-order-item.*"
88
+ },
89
+ "./widgets/webwriter-speech.*": {
90
+ "source": "./src/widgets/speech/webwriter-speech.ts",
91
+ "default": "./dist/widgets/webwriter-speech.*"
92
+ },
93
+ "./widgets/webwriter-text.*": {
94
+ "source": "./src/widgets/webwriter-text.ts",
95
+ "default": "./dist/widgets/webwriter-text.*"
96
+ },
97
+ "./widgets/webwriter-mark.*": {
98
+ "source": "./src/widgets/webwriter-mark.ts",
99
+ "default": "./dist/widgets/webwriter-mark.*"
100
+ },
101
+ "./widgets/webwriter-gap.*": {
102
+ "source": "./src/widgets/gap/webwriter-gap.ts",
103
+ "default": "./dist/widgets/webwriter-gap.*"
104
+ },
105
+ "./widgets/webwriter-gap-item.*": {
106
+ "source": "./src/widgets/gap/webwriter-gap-item.ts",
107
+ "default": "./dist/widgets/webwriter-gap-item.*"
108
+ },
109
+ "./widgets/webwriter-pairing.*": {
110
+ "source": "./src/widgets/pairing/webwriter-pairing.ts",
111
+ "default": "./dist/widgets/webwriter-pairing.*"
112
+ },
113
+ "./widgets/webwriter-pairing-item.*": {
114
+ "source": "./src/widgets/pairing/webwriter-pairing-item.ts",
115
+ "default": "./dist/widgets/webwriter-pairing-item.*"
116
+ },
117
+ "./migrate.js": {
118
+ "source": "./src/migrate/index.ts",
119
+ "default": "./dist/migrate.js"
120
+ },
121
+ "./icon": "./icon.svg",
122
+ "./editing-config.json": "./editing-config.json",
123
+ "./custom-elements.json": "./custom-elements.json"
124
+ },
125
+ "customElements": "custom-elements.json"
259
126
  }
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Merges translations from JSON into the XLIFF interchange files.
4
+ *
5
+ * Usage: node scripts/apply-translations.mjs <dir>
6
+ *
7
+ * Expects <dir> to contain `translations-<locale>.json` files, each mapping
8
+ * trans-unit id -> translated string, for the locales in lit-localize.json.
9
+ * Only fills targets that are missing or empty; existing targets are left
10
+ * alone, so hand-corrected translations survive a re-run.
11
+ *
12
+ * Run `npx @lit/localize-tools extract` before this, and
13
+ * `npx @lit/localize-tools build` after.
14
+ */
15
+
16
+ import { promises as fs } from "fs";
17
+ import { join } from "path";
18
+
19
+ const XML_ESCAPES = { "&": "&amp;", "<": "&lt;", ">": "&gt;" };
20
+ const escapeXml = text => text.replace(/[&<>]/g, c => XML_ESCAPES[c]);
21
+
22
+ /** Placeholders are `<x id="0" equiv-text="${...}"/>` in the source; translators write them back as `${...}`. */
23
+ function encodeTarget(source, translation) {
24
+ const placeholders = [...source.matchAll(/<x id="(\d+)" equiv-text="([^"]*)"\/>/g)];
25
+ let target = escapeXml(translation);
26
+ for (const [, id, equivText] of placeholders) {
27
+ // Tolerate a placeholder wrapped in braces (`{${x}}`) — a common way for the
28
+ // interpolation to come back doubled — before checking it is present at all.
29
+ target = target.replaceAll(`{${equivText}}`, () => equivText);
30
+ // equiv-text is already XML-escaped in the source; compare against the escaped translation
31
+ if (!target.includes(equivText)) {
32
+ throw new Error(`missing placeholder ${equivText} in translation ${JSON.stringify(translation)}`);
33
+ }
34
+ // function replacement: `$` in the placeholder must not be read as a substitution pattern
35
+ target = target.replace(equivText, () => `<x id="${id}" equiv-text="${equivText}"/>`);
36
+ }
37
+ if (placeholders.length && /[{}]/.test(target.replace(/<x [^>]*\/>/g, ""))) {
38
+ throw new Error(`stray brace left around placeholder in ${JSON.stringify(translation)}`);
39
+ }
40
+ return target;
41
+ }
42
+
43
+ async function applyLocale(xliffDir, locale, translations) {
44
+ const path = join(xliffDir, `${locale}.xlf`);
45
+ const xliff = await fs.readFile(path, "utf8");
46
+ let filled = 0;
47
+ let skipped = 0;
48
+ const missing = [];
49
+
50
+ const updated = xliff.replace(/<trans-unit id="([^"]+)">([\s\S]*?)<\/trans-unit>/g, (unit, id, body) => {
51
+ const translation = translations[id];
52
+ if (translation === undefined) {
53
+ missing.push(id);
54
+ return unit;
55
+ }
56
+ if (/<target[ >]/.test(body)) {
57
+ skipped++;
58
+ return unit;
59
+ }
60
+ const source = body.match(/<source>([\s\S]*?)<\/source>/)?.[1] ?? "";
61
+ const target = encodeTarget(source, translation);
62
+ filled++;
63
+ return unit.replace(/(<source>[\s\S]*?<\/source>)/, `$1\n <target>${target}</target>`);
64
+ });
65
+
66
+ await fs.writeFile(path, updated, "utf8");
67
+ return { filled, skipped, missing };
68
+ }
69
+
70
+ const dir = process.argv[2];
71
+ if (!dir) {
72
+ console.error("usage: node scripts/apply-translations.mjs <dir-with-translations-*.json>");
73
+ process.exit(1);
74
+ }
75
+
76
+ const { targetLocales, interchange } = JSON.parse(await fs.readFile("./lit-localize.json", "utf8"));
77
+ let failed = false;
78
+
79
+ for (const locale of targetLocales) {
80
+ const jsonPath = join(dir, `translations-${locale}.json`);
81
+ let translations;
82
+ try {
83
+ translations = JSON.parse(await fs.readFile(jsonPath, "utf8"));
84
+ } catch {
85
+ console.error(`${locale}: no ${jsonPath}, skipping`);
86
+ failed = true;
87
+ continue;
88
+ }
89
+ const { filled, skipped, missing } = await applyLocale(interchange.xliffDir, locale, translations);
90
+ console.log(`${locale}: filled ${filled}, kept ${skipped} existing, ${missing.length} untranslated`);
91
+ if (missing.length) {
92
+ console.error(` missing ids: ${missing.join(", ")}`);
93
+ failed = true;
94
+ }
95
+ }
96
+
97
+ process.exit(failed ? 1 : 0);
@@ -0,0 +1,72 @@
1
+ export type QuizType = {
2
+ packageId: string;
3
+ widgetPosition?: number;
4
+ id: string;
5
+ icon: string;
6
+ getName(): string;
7
+ createInstance(): HTMLElement & IWebWriterQuizType;
8
+ };
9
+
10
+ export interface IWebWriterQuizType {
11
+ checkValidity(): boolean;
12
+ reset(): void;
13
+ checkAnswer?(detailedFeedback: boolean): number;
14
+ }
15
+
16
+ type WebwriterQuizRegistry = {
17
+ version: number;
18
+ quizTypes: QuizType[];
19
+ listeners: ((types: readonly QuizType[]) => void)[];
20
+ };
21
+
22
+ declare global {
23
+ interface Window {
24
+ __wwQuizRegistry__: WebwriterQuizRegistry;
25
+ }
26
+ }
27
+
28
+ function getRegistry(): WebwriterQuizRegistry {
29
+ window.__wwQuizRegistry__ ??= { version: 1, quizTypes: [], listeners: [] };
30
+ return window.__wwQuizRegistry__;
31
+ }
32
+
33
+ export function getQuizTypes(): readonly QuizType[] {
34
+ return [...getRegistry().quizTypes];
35
+ }
36
+
37
+ export function registerQuizType(type: QuizType): void {
38
+ const registry = getRegistry();
39
+
40
+ let i = 0;
41
+
42
+ // First, order by package id
43
+ // The @webwriter/quiz package is always first, regardless of its package id
44
+ if (type.packageId !== "@webwriter/quiz") {
45
+ while (i < registry.quizTypes.length) {
46
+ if (registry.quizTypes[i].packageId.localeCompare(type.packageId) >= 0) break;
47
+ i++;
48
+ }
49
+ }
50
+
51
+ // Then, order by widget position (ascending), and then by widget id (ascending)
52
+ const newPosition = type.widgetPosition ?? Infinity;
53
+ while (i < registry.quizTypes.length && registry.quizTypes[i].packageId === type.packageId) {
54
+ const currentPosition = registry.quizTypes[i].widgetPosition ?? Infinity;
55
+ if (currentPosition > newPosition) break;
56
+ // Use id as a tiebreaker (alphabetical order)
57
+ if (currentPosition === newPosition && registry.quizTypes[i].id.localeCompare(type.id) >= 0) break;
58
+ i++;
59
+ }
60
+
61
+ registry.quizTypes.splice(i, 0, type);
62
+
63
+ const quizTypes = getQuizTypes();
64
+ registry.listeners.forEach(cb => cb(quizTypes));
65
+ }
66
+
67
+ export function onQuizTypesChanged(cb: (types: readonly QuizType[]) => void): () => void {
68
+ const registry = getRegistry();
69
+ cb(getQuizTypes());
70
+ registry.listeners.push(cb);
71
+ return () => registry.listeners = registry.listeners.filter(l => l !== cb);
72
+ }