@commonpub/layer 0.83.2 → 0.85.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.
@@ -0,0 +1,119 @@
1
+ import type { ContestStage, ContestSubmissionTemplateField } from '@commonpub/schema';
2
+ import { markdownToBlockTuples } from '@commonpub/editor';
3
+ import { newStageId } from './contestStages';
4
+
5
+ /**
6
+ * Starter templates for a NEW contest. A blank create page is intimidating and
7
+ * leaves organisers to discover the (substantial) submission-form / stage / rubric
8
+ * machinery by hand, so create.vue seeds the `standard` template by default
9
+ * (ContestEditor, create mode, onMounted). Pure + flag-adaptive so it unit-tests in
10
+ * isolation and degrades gracefully on instances where the proposal/PII builder
11
+ * features are OFF.
12
+ *
13
+ * A BlockTuple is `[type, attrs]`; bodies are seeded as structured heading/paragraph
14
+ * blocks via `markdownToBlockTuples` (the same path contestBody.ts uses for legacy
15
+ * markdown), so the seeded copy renders identically in the editor canvas AND the
16
+ * public view — a raw `markdown` block would not (its attr key is `source`, not the
17
+ * `content` an ad-hoc tuple would carry).
18
+ */
19
+ export type ContestTemplateBlock = [string, Record<string, unknown>];
20
+
21
+ export interface ContestTemplateSeed {
22
+ stages: ContestStage[];
23
+ currentStageId: string | null;
24
+ judgingCriteria: Array<{ label: string; weight?: number; description?: string }>;
25
+ descriptionBlocks: ContestTemplateBlock[];
26
+ rulesBlocks: ContestTemplateBlock[];
27
+ prizesBlocks: ContestTemplateBlock[];
28
+ }
29
+
30
+ export interface StandardTemplateOptions {
31
+ /** `features.contestProposals` — if on, the entry stage collects a proposal form
32
+ * (creates a draft project); else it falls back to attaching a published project. */
33
+ proposals: boolean;
34
+ /** `features.contestPii` — if on, seed a rules-acceptance `agreement` field (the
35
+ * agreement field type is only offered/edited in the builder when this is on). */
36
+ pii: boolean;
37
+ }
38
+
39
+ const RULES_AGREEMENT_TERMS =
40
+ 'By entering, I confirm this submission is my own original work and I agree to the contest rules and code of conduct.';
41
+
42
+ /** The proposal/entry stage's starter submission form (the approved general shape). */
43
+ function standardSubmissionTemplate(opts: StandardTemplateOptions): ContestSubmissionTemplateField[] {
44
+ const fields: ContestSubmissionTemplateField[] = [
45
+ { key: 'project_name', label: 'Project name', type: 'text', required: true },
46
+ { key: 'summary', label: 'One-line summary', type: 'text', required: true, help: 'A single sentence describing your idea.' },
47
+ { key: 'description', label: 'Description', type: 'textarea', required: true, help: 'What you are building and the problem it solves.' },
48
+ { key: 'approach', label: 'Approach', type: 'textarea', required: false, help: 'How you plan to build it (optional).' },
49
+ ];
50
+ // The agreement field type is only surfaced in the builder when contestPii is on,
51
+ // so only seed it there — else it would be a hidden, un-editable required field.
52
+ if (opts.pii) {
53
+ fields.push({
54
+ key: 'rules_agreement',
55
+ label: 'Contest rules',
56
+ type: 'agreement',
57
+ required: true,
58
+ terms: RULES_AGREEMENT_TERMS,
59
+ mustAccept: true,
60
+ });
61
+ }
62
+ return fields;
63
+ }
64
+
65
+ /** The default rubric (contest-level; review stages fall back to it). */
66
+ function standardCriteria(): ContestTemplateSeed['judgingCriteria'] {
67
+ return [
68
+ { label: 'Innovation', weight: 40, description: 'Originality and creativity of the idea.' },
69
+ { label: 'Feasibility', weight: 30, description: 'How realistic and well-scoped the plan is.' },
70
+ { label: 'Impact', weight: 30, description: 'Potential value to the community.' },
71
+ ];
72
+ }
73
+
74
+ /**
75
+ * The standard new-contest template: a Proposals (submission) stage with a starter
76
+ * form + rules agreement, a Judging (review) stage, and a Results stage, plus a
77
+ * default rubric and starter Overview/Rules copy. Stage dates are intentionally
78
+ * unset — the organiser sets the schedule, then can set per-stage dates in the
79
+ * Stages tab.
80
+ */
81
+ export function standardContestTemplate(opts: StandardTemplateOptions): ContestTemplateSeed {
82
+ const stages: ContestStage[] = [
83
+ {
84
+ id: newStageId(),
85
+ name: 'Proposals',
86
+ kind: 'submission',
87
+ description: 'Entrants submit a proposal for review.',
88
+ submissionMode: opts.proposals ? 'proposal' : 'attach',
89
+ submissionTemplate: standardSubmissionTemplate(opts),
90
+ instructionsBlocks: markdownToBlockTuples(
91
+ 'Tell us about your idea. Be concrete about what you will build and why it matters. You can edit your proposal until the round closes.',
92
+ ) as ContestStage['instructionsBlocks'],
93
+ },
94
+ {
95
+ id: newStageId(),
96
+ name: 'Judging',
97
+ kind: 'review',
98
+ description: 'Judges score entries against the rubric.',
99
+ },
100
+ {
101
+ id: newStageId(),
102
+ name: 'Results',
103
+ kind: 'results',
104
+ description: 'Final standings are published.',
105
+ },
106
+ ];
107
+ return {
108
+ stages,
109
+ currentStageId: null,
110
+ judgingCriteria: standardCriteria(),
111
+ descriptionBlocks: markdownToBlockTuples(
112
+ '## About this contest\n\nDescribe who this contest is for, what to build, and why it matters. Replace this overview with your own.',
113
+ ) as ContestTemplateBlock[],
114
+ rulesBlocks: markdownToBlockTuples(
115
+ '## Rules\n\n- Who can enter\n- What counts as a valid entry\n- How judging works\n\nReplace these with your contest rules.',
116
+ ) as ContestTemplateBlock[],
117
+ prizesBlocks: [],
118
+ };
119
+ }