@erudit-js/prose 4.0.0 → 4.0.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.
Files changed (86) hide show
  1. package/dist/app/default/Inliners.vue +11 -11
  2. package/dist/app/default/Mix.vue +11 -11
  3. package/dist/app/default/Text.vue +25 -25
  4. package/dist/app/shared/Prose.vue +40 -40
  5. package/dist/app/shared/Render.vue +51 -51
  6. package/dist/app/shared/assets/block.svg +2 -2
  7. package/dist/app/shared/assets/check.svg +2 -2
  8. package/dist/app/shared/assets/inliner.svg +2 -2
  9. package/dist/app/shared/assets/plus.svg +2 -2
  10. package/dist/app/shared/block/AsideMenu.vue +44 -44
  11. package/dist/app/shared/block/AsideMenuButton.vue +51 -51
  12. package/dist/app/shared/block/AsideMenuCopyLink.vue +40 -40
  13. package/dist/app/shared/block/AsideMenuSeparator.vue +3 -3
  14. package/dist/app/shared/block/Block.vue +270 -270
  15. package/dist/app/shared/inliner/Inliner.vue +11 -11
  16. package/dist/app/shared/photoswipe/style.css +22 -22
  17. package/dist/elements/accent/Accent.vue +88 -88
  18. package/dist/elements/accent/AccentColumnSection.vue +61 -61
  19. package/dist/elements/accent/AccentRowSections.vue +64 -64
  20. package/dist/elements/callout/Callout.vue +81 -81
  21. package/dist/elements/callout/_global.d.ts +15 -15
  22. package/dist/elements/caption/Caption.vue +44 -44
  23. package/dist/elements/caption/_global.d.ts +26 -26
  24. package/dist/elements/details/Details.vue +49 -49
  25. package/dist/elements/details/_global.d.ts +27 -27
  26. package/dist/elements/details/icon.svg +2 -2
  27. package/dist/elements/diagram/Diagram.vue +360 -360
  28. package/dist/elements/diagram/_global.d.ts +19 -19
  29. package/dist/elements/emphasis/Emphasis.vue +25 -25
  30. package/dist/elements/emphasis/_global.d.ts +18 -18
  31. package/dist/elements/flex/Flex.vue +36 -36
  32. package/dist/elements/flex/_global.d.ts +23 -23
  33. package/dist/elements/gallery/Gallery.vue +56 -56
  34. package/dist/elements/gallery/_global.d.ts +18 -18
  35. package/dist/elements/heading/Heading.vue +44 -44
  36. package/dist/elements/heading/_global.d.ts +42 -42
  37. package/dist/elements/heading/icon.svg +2 -2
  38. package/dist/elements/horizontalLine/HorizontalLine.vue +6 -6
  39. package/dist/elements/horizontalLine/_global.d.ts +17 -17
  40. package/dist/elements/image/Image.vue +15 -15
  41. package/dist/elements/image/ImageElement.vue +80 -80
  42. package/dist/elements/image/_global.d.ts +18 -18
  43. package/dist/elements/lineBreak/LineBreak.vue +3 -3
  44. package/dist/elements/lineBreak/_global.d.ts +18 -18
  45. package/dist/elements/link/BlockLink.vue +108 -108
  46. package/dist/elements/link/Link.vue +92 -92
  47. package/dist/elements/link/dependency/_global.d.ts +47 -47
  48. package/dist/elements/link/reference/_global.d.ts +49 -49
  49. package/dist/elements/list/List.vue +58 -58
  50. package/dist/elements/list/_global.d.ts +50 -50
  51. package/dist/elements/math/_global.d.ts +72 -72
  52. package/dist/elements/math/_global.ts +3 -3
  53. package/dist/elements/math/components/BlockMath.vue +30 -30
  54. package/dist/elements/math/components/InlinerMath.vue +65 -65
  55. package/dist/elements/math/components/Katex.vue +88 -88
  56. package/dist/elements/math/components/MathGroup.vue +41 -41
  57. package/dist/elements/paragraph/Paragraph.vue +25 -25
  58. package/dist/elements/paragraph/_global.d.ts +27 -27
  59. package/dist/elements/paragraph/icon.svg +3 -3
  60. package/dist/elements/problem/_global.d.ts +112 -112
  61. package/dist/elements/problem/assets/actions/answer.svg +2 -2
  62. package/dist/elements/problem/assets/actions/check.svg +2 -2
  63. package/dist/elements/problem/assets/actions/generate.svg +2 -2
  64. package/dist/elements/problem/assets/actions/hint.svg +2 -2
  65. package/dist/elements/problem/assets/actions/note.svg +2 -2
  66. package/dist/elements/problem/assets/actions/solution.svg +2 -2
  67. package/dist/elements/problem/assets/icon.svg +2 -2
  68. package/dist/elements/problem/components/Problem.vue +22 -22
  69. package/dist/elements/problem/components/ProblemButton.vue +20 -20
  70. package/dist/elements/problem/components/ProblemContainer.vue +8 -8
  71. package/dist/elements/problem/components/ProblemContent.vue +356 -356
  72. package/dist/elements/problem/components/ProblemExpander.vue +7 -7
  73. package/dist/elements/problem/components/ProblemExpanderSection.vue +57 -57
  74. package/dist/elements/problem/components/ProblemHeader.vue +100 -100
  75. package/dist/elements/problem/components/Problems.vue +83 -83
  76. package/dist/elements/problem/components/SubProblem.vue +14 -14
  77. package/dist/elements/problem/components/expanders/Check.vue +153 -153
  78. package/dist/elements/problem/components/expanders/Checks.vue +146 -146
  79. package/dist/elements/problem/components/expanders/DefaultPlusSections.vue +38 -38
  80. package/dist/elements/problem/components/expanders/Hint.vue +26 -26
  81. package/dist/elements/table/Table.vue +100 -100
  82. package/dist/elements/table/_global.d.ts +36 -36
  83. package/dist/elements/video/Video.vue +110 -110
  84. package/dist/elements/video/_global.d.ts +18 -18
  85. package/package.json +4 -4
  86. package/types.d.ts +4 -4
@@ -1,356 +1,356 @@
1
- <script lang="ts" setup>
2
- import {
3
- computed,
4
- onMounted,
5
- ref,
6
- shallowRef,
7
- useTemplateRef,
8
- watchEffect,
9
- type Component,
10
- } from 'vue';
11
- import { autoUpdate, offset, useFloating } from '@floating-ui/vue';
12
- import {
13
- isProseElement,
14
- resolveRawElement,
15
- type ProseElement,
16
- } from '@jsprose/core';
17
-
18
- import {
19
- problemAnswer,
20
- problemCheckSchema,
21
- problemDescriptionSchema,
22
- problemHintSchema,
23
- problemNote,
24
- problemSolution,
25
- type CheckFunction,
26
- type ProblemContentChild,
27
- } from '../problemContent.js';
28
- import { useProseContext } from '../../../app/composables/context.js';
29
- import { useProblemPhrase } from '../composables/phrase.js';
30
- import type { ProblemAction } from '../shared.js';
31
- import { useArrayContainsAnchor } from '../../../app/composables/anchor.js';
32
- import type { ProblemScriptInstance } from '../problemScript.js';
33
- import type { problemSchema } from '../problem.js';
34
- import type { subProblemSchema } from '../problems.js';
35
- import { useElementStorage } from '../../../app/composables/storage.js';
36
- import type { ProblemScriptStorage } from '../storage.js';
37
- import { createProblemScriptInstance } from '../composables/problemScript.js';
38
- import { DEFAULT_SEED, type ProblemSeed } from '../rng.js';
39
- import plusIcon from '../../../app/shared/assets/plus.svg?raw';
40
- import Render from '../../../app/shared/Render.vue';
41
- import Hint from './expanders/Hint.vue';
42
- import DefaultPlusSections from './expanders/DefaultPlusSections.vue';
43
- import ProblemButton from './ProblemButton.vue';
44
- import Checks from './expanders/Checks.vue';
45
-
46
- const { element, initialElements } = defineProps<{
47
- element:
48
- | ProseElement<typeof problemSchema>
49
- | ProseElement<typeof subProblemSchema>;
50
- initialElements: ProseElement<ProblemContentChild>[];
51
- }>();
52
-
53
- const { EruditIcon, EruditTransition } = useProseContext();
54
- const phrase = await useProblemPhrase();
55
-
56
- const actionIcons: Record<ProblemAction, string> = Object.fromEntries(
57
- Object.entries(
58
- // @ts-ignore
59
- import.meta.glob('../assets/actions/*.svg', {
60
- query: 'raw',
61
- eager: true,
62
- import: 'default',
63
- }),
64
- ).map(([key, value]) => {
65
- const name = key.split('/').pop()?.replace('.svg', '') ?? key;
66
- return [name as any, value as string];
67
- }),
68
- );
69
-
70
- const key = ref(0);
71
- const elements =
72
- shallowRef<ProseElement<ProblemContentChild>[]>(initialElements);
73
-
74
- const description = computed(() => {
75
- return elements.value.find((element) =>
76
- isProseElement(element, problemDescriptionSchema),
77
- )!;
78
- });
79
-
80
- const expanderComponents: Record<
81
- Exclude<ProblemAction, 'generate'>,
82
- Component
83
- > = {
84
- check: Checks,
85
- hint: Hint,
86
- answer: DefaultPlusSections,
87
- solution: DefaultPlusSections,
88
- note: DefaultPlusSections,
89
- };
90
-
91
- const expandableActions = computed(() => {
92
- type ActionMap<T> =
93
- T extends Partial<Exclude<Record<ProblemAction, any>, 'generate'>>
94
- ? T
95
- : never;
96
-
97
- const actionMap: ActionMap<{
98
- hint?: ProseElement<typeof problemHintSchema>[];
99
- answer?: ProseElement<typeof problemAnswer.schema>;
100
- solution?: ProseElement<typeof problemSolution.schema>;
101
- note?: ProseElement<typeof problemNote.schema>;
102
- check?: {
103
- checkElements: ProseElement<typeof problemCheckSchema>[];
104
- checkFunction?: CheckFunction;
105
- };
106
- }> = {};
107
-
108
- const checks = elements.value.filter((element) =>
109
- isProseElement(element, problemCheckSchema),
110
- );
111
- if (checks.length > 0) {
112
- actionMap.check = {
113
- checkElements: checks,
114
- checkFunction: scriptCheck.value,
115
- };
116
- }
117
-
118
- const hints = elements.value.filter((element) =>
119
- isProseElement(element, problemHintSchema),
120
- );
121
- if (hints.length > 0) {
122
- actionMap.hint = hints;
123
- }
124
-
125
- (['answer', 'solution', 'note'] as const).forEach((action) => {
126
- const actionElements = elements.value.filter((element) => {
127
- switch (action) {
128
- case 'answer':
129
- return isProseElement(element, problemAnswer.schema);
130
- case 'solution':
131
- return isProseElement(element, problemSolution.schema);
132
- case 'note':
133
- return isProseElement(element, problemNote.schema);
134
- }
135
- });
136
-
137
- if (actionElements.length > 0) {
138
- actionMap[action] = actionElements[0]! as any;
139
- }
140
- });
141
-
142
- return actionMap;
143
- });
144
-
145
- const currentAction = ref<Exclude<ProblemAction, 'generate'>>();
146
-
147
- const containsAnchorArray = useArrayContainsAnchor(elements.value);
148
-
149
- watchEffect(() => {
150
- const anchorIndex = containsAnchorArray.value;
151
-
152
- if (anchorIndex === undefined) {
153
- return;
154
- }
155
-
156
- const anchorElement = elements.value.at(anchorIndex);
157
-
158
- switch (true) {
159
- case isProseElement(anchorElement, problemHintSchema):
160
- currentAction.value = 'hint';
161
- break;
162
- case isProseElement(anchorElement, problemAnswer.schema):
163
- currentAction.value = 'answer';
164
- break;
165
- case isProseElement(anchorElement, problemSolution.schema):
166
- currentAction.value = 'solution';
167
- break;
168
- case isProseElement(anchorElement, problemNote.schema):
169
- currentAction.value = 'note';
170
- break;
171
- }
172
- });
173
-
174
- //
175
- // Problem Script
176
- //
177
-
178
- const scriptInstance = shallowRef<ProblemScriptInstance>();
179
- const scriptStorage = (await useElementStorage(
180
- element as any,
181
- )) as ProblemScriptStorage;
182
- const isGenerator = computed(() => Boolean(scriptInstance.value?.isGenerator));
183
- const scriptCheck = shallowRef<CheckFunction>();
184
-
185
- onMounted(async () => {
186
- scriptInstance.value = await createProblemScriptInstance(
187
- scriptStorage?.resolvedScriptSrc,
188
- element.data.scriptUniques,
189
- );
190
-
191
- if (scriptInstance.value) {
192
- const initialGenerateResult = scriptInstance.value!.generate(DEFAULT_SEED);
193
- scriptCheck.value = initialGenerateResult.check;
194
- }
195
- });
196
-
197
- const generateRotation = ref(0);
198
- const generating = ref(false);
199
- const seed = ref<ProblemSeed>(DEFAULT_SEED);
200
- const usingCustomSeed = ref(false);
201
-
202
- async function doGenerate() {
203
- if (!scriptInstance.value) {
204
- return;
205
- }
206
-
207
- generateRotation.value += 180;
208
-
209
- if (generating.value) {
210
- return;
211
- }
212
-
213
- generating.value = true;
214
-
215
- if (usingCustomSeed.value) {
216
- usingCustomSeed.value = false;
217
- } else {
218
- seed.value = Math.floor(Math.random() * 1000000000) + 1;
219
- }
220
-
221
- const generateResult = scriptInstance.value.generate(seed.value);
222
-
223
- if (generateResult.check) {
224
- scriptCheck.value = generateResult.check;
225
- }
226
-
227
- const rawElements = generateResult.problemContent;
228
- const proseElements: ProseElement<ProblemContentChild>[] = [];
229
- for (const rawElement of rawElements) {
230
- const resolveResult = await resolveRawElement({
231
- rawElement,
232
- linkable: false,
233
- });
234
- proseElements.push(resolveResult.proseElement as any);
235
- }
236
-
237
- elements.value = proseElements;
238
-
239
- key.value++;
240
- generating.value = false;
241
- }
242
-
243
- //
244
- // Seed Popup
245
- //
246
-
247
- const seedPopupVisible = ref(false);
248
- const seedReferenceElement = useTemplateRef('seedReference');
249
- const seedPopupElement = useTemplateRef('seedPopup');
250
-
251
- const { floatingStyles: seedFloatingStyles } = useFloating(
252
- seedReferenceElement,
253
- seedPopupElement,
254
- {
255
- whileElementsMounted: autoUpdate,
256
- placement: 'top',
257
- },
258
- );
259
- </script>
260
-
261
- <template>
262
- <div>
263
- <Suspense suspensible>
264
- <div class="py-(--proseAsideWidth)" :key>
265
- <Render v-for="child of description.children" :element="child" />
266
- </div>
267
- </Suspense>
268
-
269
- <div
270
- v-if="Object.values(expandableActions).some(Boolean) || isGenerator"
271
- class="gap-small micro:gap-normal flex flex-wrap p-(--proseAsideWidth)
272
- pt-0"
273
- >
274
- <ProblemButton
275
- v-for="(_, actionKey) in expandableActions"
276
- :key="actionKey"
277
- @click="
278
- currentAction = actionKey === currentAction ? undefined : actionKey
279
- "
280
- :active="actionKey === currentAction"
281
- class="flex items-center gap-[7px]"
282
- >
283
- <EruditIcon :name="actionIcons[actionKey]" class="text-[1.3em]" />
284
- <span>{{ phrase[`action_${actionKey}`] }}</span>
285
- </ProblemButton>
286
- <div
287
- v-if="isGenerator"
288
- ref="seedReference"
289
- @mouseenter="seedPopupVisible = true"
290
- @mouseleave="seedPopupVisible = false"
291
- >
292
- <ProblemButton
293
- @touchstart="seedPopupVisible = seedPopupVisible ? false : true"
294
- @click="doGenerate"
295
- class="flex items-center gap-[7px]"
296
- >
297
- <EruditIcon
298
- :name="actionIcons.generate"
299
- :style="{ transform: `rotate(${generateRotation}deg)` }"
300
- class="text-[1.3em] transition-[transform] backface-hidden"
301
- />
302
- <span>{{ phrase.action_generate }}</span>
303
- </ProblemButton>
304
- <EruditTransition>
305
- <div
306
- v-if="seedPopupVisible"
307
- ref="seedPopup"
308
- :style="seedFloatingStyles"
309
- class="pb-2.5"
310
- >
311
- <form
312
- class="shadow-border text-main-xs flex rounded bg-neutral-900
313
- text-white shadow-lg dark:bg-neutral-200 dark:text-black"
314
- @submit.prevent="doGenerate"
315
- >
316
- <input
317
- type="text"
318
- v-model="seed"
319
- @input="usingCustomSeed = true"
320
- :title="phrase.seed_explain"
321
- @focus="($event as any).target.select()"
322
- class="max-w-[100px] flex-1 p-[5px] text-center outline-none"
323
- />
324
- <button
325
- v-if="seed !== DEFAULT_SEED"
326
- type="button"
327
- @click="
328
- seed = DEFAULT_SEED;
329
- usingCustomSeed = true;
330
- doGenerate();
331
- "
332
- class="cursor-pointer pr-[3px]"
333
- >
334
- <EruditIcon
335
- :name="plusIcon"
336
- class="hocus:text-white dark:hocus:text-black rotate-45
337
- text-[1.3em] text-neutral-400 transition-[color]
338
- dark:text-neutral-600"
339
- />
340
- </button>
341
- </form>
342
- </div>
343
- </EruditTransition>
344
- </div>
345
- </div>
346
-
347
- <Suspense suspensible>
348
- <component
349
- v-if="currentAction"
350
- :key="`${key}-${currentAction}`"
351
- :is="expanderComponents[currentAction]"
352
- :value="expandableActions[currentAction]"
353
- />
354
- </Suspense>
355
- </div>
356
- </template>
1
+ <script lang="ts" setup>
2
+ import {
3
+ computed,
4
+ onMounted,
5
+ ref,
6
+ shallowRef,
7
+ useTemplateRef,
8
+ watchEffect,
9
+ type Component,
10
+ } from 'vue';
11
+ import { autoUpdate, offset, useFloating } from '@floating-ui/vue';
12
+ import {
13
+ isProseElement,
14
+ resolveRawElement,
15
+ type ProseElement,
16
+ } from '@jsprose/core';
17
+
18
+ import {
19
+ problemAnswer,
20
+ problemCheckSchema,
21
+ problemDescriptionSchema,
22
+ problemHintSchema,
23
+ problemNote,
24
+ problemSolution,
25
+ type CheckFunction,
26
+ type ProblemContentChild,
27
+ } from '../problemContent.js';
28
+ import { useProseContext } from '../../../app/composables/context.js';
29
+ import { useProblemPhrase } from '../composables/phrase.js';
30
+ import type { ProblemAction } from '../shared.js';
31
+ import { useArrayContainsAnchor } from '../../../app/composables/anchor.js';
32
+ import type { ProblemScriptInstance } from '../problemScript.js';
33
+ import type { problemSchema } from '../problem.js';
34
+ import type { subProblemSchema } from '../problems.js';
35
+ import { useElementStorage } from '../../../app/composables/storage.js';
36
+ import type { ProblemScriptStorage } from '../storage.js';
37
+ import { createProblemScriptInstance } from '../composables/problemScript.js';
38
+ import { DEFAULT_SEED, type ProblemSeed } from '../rng.js';
39
+ import plusIcon from '../../../app/shared/assets/plus.svg?raw';
40
+ import Render from '../../../app/shared/Render.vue';
41
+ import Hint from './expanders/Hint.vue';
42
+ import DefaultPlusSections from './expanders/DefaultPlusSections.vue';
43
+ import ProblemButton from './ProblemButton.vue';
44
+ import Checks from './expanders/Checks.vue';
45
+
46
+ const { element, initialElements } = defineProps<{
47
+ element:
48
+ | ProseElement<typeof problemSchema>
49
+ | ProseElement<typeof subProblemSchema>;
50
+ initialElements: ProseElement<ProblemContentChild>[];
51
+ }>();
52
+
53
+ const { EruditIcon, EruditTransition } = useProseContext();
54
+ const phrase = await useProblemPhrase();
55
+
56
+ const actionIcons: Record<ProblemAction, string> = Object.fromEntries(
57
+ Object.entries(
58
+ // @ts-ignore
59
+ import.meta.glob('../assets/actions/*.svg', {
60
+ query: 'raw',
61
+ eager: true,
62
+ import: 'default',
63
+ }),
64
+ ).map(([key, value]) => {
65
+ const name = key.split('/').pop()?.replace('.svg', '') ?? key;
66
+ return [name as any, value as string];
67
+ }),
68
+ );
69
+
70
+ const key = ref(0);
71
+ const elements =
72
+ shallowRef<ProseElement<ProblemContentChild>[]>(initialElements);
73
+
74
+ const description = computed(() => {
75
+ return elements.value.find((element) =>
76
+ isProseElement(element, problemDescriptionSchema),
77
+ )!;
78
+ });
79
+
80
+ const expanderComponents: Record<
81
+ Exclude<ProblemAction, 'generate'>,
82
+ Component
83
+ > = {
84
+ check: Checks,
85
+ hint: Hint,
86
+ answer: DefaultPlusSections,
87
+ solution: DefaultPlusSections,
88
+ note: DefaultPlusSections,
89
+ };
90
+
91
+ const expandableActions = computed(() => {
92
+ type ActionMap<T> =
93
+ T extends Partial<Exclude<Record<ProblemAction, any>, 'generate'>>
94
+ ? T
95
+ : never;
96
+
97
+ const actionMap: ActionMap<{
98
+ hint?: ProseElement<typeof problemHintSchema>[];
99
+ answer?: ProseElement<typeof problemAnswer.schema>;
100
+ solution?: ProseElement<typeof problemSolution.schema>;
101
+ note?: ProseElement<typeof problemNote.schema>;
102
+ check?: {
103
+ checkElements: ProseElement<typeof problemCheckSchema>[];
104
+ checkFunction?: CheckFunction;
105
+ };
106
+ }> = {};
107
+
108
+ const checks = elements.value.filter((element) =>
109
+ isProseElement(element, problemCheckSchema),
110
+ );
111
+ if (checks.length > 0) {
112
+ actionMap.check = {
113
+ checkElements: checks,
114
+ checkFunction: scriptCheck.value,
115
+ };
116
+ }
117
+
118
+ const hints = elements.value.filter((element) =>
119
+ isProseElement(element, problemHintSchema),
120
+ );
121
+ if (hints.length > 0) {
122
+ actionMap.hint = hints;
123
+ }
124
+
125
+ (['answer', 'solution', 'note'] as const).forEach((action) => {
126
+ const actionElements = elements.value.filter((element) => {
127
+ switch (action) {
128
+ case 'answer':
129
+ return isProseElement(element, problemAnswer.schema);
130
+ case 'solution':
131
+ return isProseElement(element, problemSolution.schema);
132
+ case 'note':
133
+ return isProseElement(element, problemNote.schema);
134
+ }
135
+ });
136
+
137
+ if (actionElements.length > 0) {
138
+ actionMap[action] = actionElements[0]! as any;
139
+ }
140
+ });
141
+
142
+ return actionMap;
143
+ });
144
+
145
+ const currentAction = ref<Exclude<ProblemAction, 'generate'>>();
146
+
147
+ const containsAnchorArray = useArrayContainsAnchor(elements.value);
148
+
149
+ watchEffect(() => {
150
+ const anchorIndex = containsAnchorArray.value;
151
+
152
+ if (anchorIndex === undefined) {
153
+ return;
154
+ }
155
+
156
+ const anchorElement = elements.value.at(anchorIndex);
157
+
158
+ switch (true) {
159
+ case isProseElement(anchorElement, problemHintSchema):
160
+ currentAction.value = 'hint';
161
+ break;
162
+ case isProseElement(anchorElement, problemAnswer.schema):
163
+ currentAction.value = 'answer';
164
+ break;
165
+ case isProseElement(anchorElement, problemSolution.schema):
166
+ currentAction.value = 'solution';
167
+ break;
168
+ case isProseElement(anchorElement, problemNote.schema):
169
+ currentAction.value = 'note';
170
+ break;
171
+ }
172
+ });
173
+
174
+ //
175
+ // Problem Script
176
+ //
177
+
178
+ const scriptInstance = shallowRef<ProblemScriptInstance>();
179
+ const scriptStorage = (await useElementStorage(
180
+ element as any,
181
+ )) as ProblemScriptStorage;
182
+ const isGenerator = computed(() => Boolean(scriptInstance.value?.isGenerator));
183
+ const scriptCheck = shallowRef<CheckFunction>();
184
+
185
+ onMounted(async () => {
186
+ scriptInstance.value = await createProblemScriptInstance(
187
+ scriptStorage?.resolvedScriptSrc,
188
+ element.data.scriptUniques,
189
+ );
190
+
191
+ if (scriptInstance.value) {
192
+ const initialGenerateResult = scriptInstance.value!.generate(DEFAULT_SEED);
193
+ scriptCheck.value = initialGenerateResult.check;
194
+ }
195
+ });
196
+
197
+ const generateRotation = ref(0);
198
+ const generating = ref(false);
199
+ const seed = ref<ProblemSeed>(DEFAULT_SEED);
200
+ const usingCustomSeed = ref(false);
201
+
202
+ async function doGenerate() {
203
+ if (!scriptInstance.value) {
204
+ return;
205
+ }
206
+
207
+ generateRotation.value += 180;
208
+
209
+ if (generating.value) {
210
+ return;
211
+ }
212
+
213
+ generating.value = true;
214
+
215
+ if (usingCustomSeed.value) {
216
+ usingCustomSeed.value = false;
217
+ } else {
218
+ seed.value = Math.floor(Math.random() * 1000000000) + 1;
219
+ }
220
+
221
+ const generateResult = scriptInstance.value.generate(seed.value);
222
+
223
+ if (generateResult.check) {
224
+ scriptCheck.value = generateResult.check;
225
+ }
226
+
227
+ const rawElements = generateResult.problemContent;
228
+ const proseElements: ProseElement<ProblemContentChild>[] = [];
229
+ for (const rawElement of rawElements) {
230
+ const resolveResult = await resolveRawElement({
231
+ rawElement,
232
+ linkable: false,
233
+ });
234
+ proseElements.push(resolveResult.proseElement as any);
235
+ }
236
+
237
+ elements.value = proseElements;
238
+
239
+ key.value++;
240
+ generating.value = false;
241
+ }
242
+
243
+ //
244
+ // Seed Popup
245
+ //
246
+
247
+ const seedPopupVisible = ref(false);
248
+ const seedReferenceElement = useTemplateRef('seedReference');
249
+ const seedPopupElement = useTemplateRef('seedPopup');
250
+
251
+ const { floatingStyles: seedFloatingStyles } = useFloating(
252
+ seedReferenceElement,
253
+ seedPopupElement,
254
+ {
255
+ whileElementsMounted: autoUpdate,
256
+ placement: 'top',
257
+ },
258
+ );
259
+ </script>
260
+
261
+ <template>
262
+ <div>
263
+ <Suspense suspensible>
264
+ <div class="py-(--proseAsideWidth)" :key>
265
+ <Render v-for="child of description.children" :element="child" />
266
+ </div>
267
+ </Suspense>
268
+
269
+ <div
270
+ v-if="Object.values(expandableActions).some(Boolean) || isGenerator"
271
+ class="gap-small micro:gap-normal flex flex-wrap p-(--proseAsideWidth)
272
+ pt-0"
273
+ >
274
+ <ProblemButton
275
+ v-for="(_, actionKey) in expandableActions"
276
+ :key="actionKey"
277
+ @click="
278
+ currentAction = actionKey === currentAction ? undefined : actionKey
279
+ "
280
+ :active="actionKey === currentAction"
281
+ class="flex items-center gap-[7px]"
282
+ >
283
+ <EruditIcon :name="actionIcons[actionKey]" class="text-[1.3em]" />
284
+ <span>{{ phrase[`action_${actionKey}`] }}</span>
285
+ </ProblemButton>
286
+ <div
287
+ v-if="isGenerator"
288
+ ref="seedReference"
289
+ @mouseenter="seedPopupVisible = true"
290
+ @mouseleave="seedPopupVisible = false"
291
+ >
292
+ <ProblemButton
293
+ @touchstart="seedPopupVisible = seedPopupVisible ? false : true"
294
+ @click="doGenerate"
295
+ class="flex items-center gap-[7px]"
296
+ >
297
+ <EruditIcon
298
+ :name="actionIcons.generate"
299
+ :style="{ transform: `rotate(${generateRotation}deg)` }"
300
+ class="text-[1.3em] transition-[transform] backface-hidden"
301
+ />
302
+ <span>{{ phrase.action_generate }}</span>
303
+ </ProblemButton>
304
+ <EruditTransition>
305
+ <div
306
+ v-if="seedPopupVisible"
307
+ ref="seedPopup"
308
+ :style="seedFloatingStyles"
309
+ class="pb-2.5"
310
+ >
311
+ <form
312
+ class="shadow-border text-main-xs flex rounded bg-neutral-900
313
+ text-white shadow-lg dark:bg-neutral-200 dark:text-black"
314
+ @submit.prevent="doGenerate"
315
+ >
316
+ <input
317
+ type="text"
318
+ v-model="seed"
319
+ @input="usingCustomSeed = true"
320
+ :title="phrase.seed_explain"
321
+ @focus="($event as any).target.select()"
322
+ class="max-w-[100px] flex-1 p-[5px] text-center outline-none"
323
+ />
324
+ <button
325
+ v-if="seed !== DEFAULT_SEED"
326
+ type="button"
327
+ @click="
328
+ seed = DEFAULT_SEED;
329
+ usingCustomSeed = true;
330
+ doGenerate();
331
+ "
332
+ class="cursor-pointer pr-[3px]"
333
+ >
334
+ <EruditIcon
335
+ :name="plusIcon"
336
+ class="hocus:text-white dark:hocus:text-black rotate-45
337
+ text-[1.3em] text-neutral-400 transition-[color]
338
+ dark:text-neutral-600"
339
+ />
340
+ </button>
341
+ </form>
342
+ </div>
343
+ </EruditTransition>
344
+ </div>
345
+ </div>
346
+
347
+ <Suspense suspensible>
348
+ <component
349
+ v-if="currentAction"
350
+ :key="`${key}-${currentAction}`"
351
+ :is="expanderComponents[currentAction]"
352
+ :value="expandableActions[currentAction]"
353
+ />
354
+ </Suspense>
355
+ </div>
356
+ </template>