@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.
- package/components/contest/ContestAdvancementPanel.vue +138 -0
- package/components/contest/ContestBannerAdjust.vue +121 -0
- package/components/contest/ContestBodyCanvas.vue +23 -14
- package/components/contest/ContestEditor.vue +94 -132
- package/components/contest/ContestHero.vue +5 -1
- package/components/contest/ContestProposalForm.vue +3 -0
- package/components/contest/ContestStageCard.vue +207 -0
- package/components/contest/ContestStageSubmission.vue +3 -0
- package/components/contest/ContestStageTemplateEditor.vue +374 -0
- package/components/contest/ContestStagesEditor.vue +25 -325
- package/components/contest/blocks/JudgesShowcaseBlock.vue +113 -6
- package/composables/useContestEditor.ts +40 -4
- package/package.json +9 -9
- package/pages/contests/[slug]/index.vue +4 -1
- package/pages/contests/index.vue +1 -0
- package/utils/contestBlocks.ts +10 -0
- package/utils/contestBody.ts +3 -3
- package/utils/contestImage.ts +35 -0
- package/utils/contestStages.ts +80 -51
- package/utils/contestSubmissionTemplates.ts +165 -0
- package/utils/contestTemplates.ts +119 -0
|
@@ -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
|
+
}
|