Skip to content

Commit 7e1438e

Browse files
committed
feat: project rules
1 parent aa7995e commit 7e1438e

File tree

6 files changed

+773
-2
lines changed

6 files changed

+773
-2
lines changed

frontend/app/api/generated.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1870,7 +1870,7 @@ export type AdminProjectEditPageQueryVariables = Exact<{
18701870
}>;
18711871

18721872

1873-
export type AdminProjectEditPageQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, name: string, description: string, startDate: any, endDate: any, archivedAt?: boolean | null, branding: { __typename?: 'Branding', logo?: string | null, rounding: number, colors: { __typename?: 'Colors', light: { __typename?: 'ColorSet', accent: string, accentContrast: string, onAccent: string, backgroundDefault: string, backgroundRaised: string, backgroundIndent: string, textDefault: string, textMuted: string, textHint: string, shadowDefault: string, shadowBlank: string, borderDefault: string }, dark: { __typename?: 'ColorSet', accent: string, accentContrast: string, onAccent: string, backgroundDefault: string, backgroundRaised: string, backgroundIndent: string, textDefault: string, textMuted: string, textHint: string, shadowDefault: string, shadowBlank: string, borderDefault: string } } } } };
1873+
export type AdminProjectEditPageQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, name: string, description: string, startDate: any, endDate: any, archivedAt?: boolean | null, branding: { __typename?: 'Branding', logo?: string | null, rounding: number, colors: { __typename?: 'Colors', light: { __typename?: 'ColorSet', accent: string, accentContrast: string, onAccent: string, backgroundDefault: string, backgroundRaised: string, backgroundIndent: string, textDefault: string, textMuted: string, textHint: string, shadowDefault: string, shadowBlank: string, borderDefault: string }, dark: { __typename?: 'ColorSet', accent: string, accentContrast: string, onAccent: string, backgroundDefault: string, backgroundRaised: string, backgroundIndent: string, textDefault: string, textMuted: string, textHint: string, shadowDefault: string, shadowBlank: string, borderDefault: string } } }, rules?: { __typename?: 'MarkdownText', markdown: string, html: string } | null } };
18741874

18751875
export type AdminProjectEventPageQueryVariables = Exact<{
18761876
eventId: Scalars['ID']['input'];
@@ -2644,6 +2644,10 @@ export const AdminProjectEditPageDocument = gql`
26442644
}
26452645
}
26462646
}
2647+
rules {
2648+
markdown
2649+
html
2650+
}
26472651
}
26482652
}
26492653
`;

frontend/app/assets/styles/main.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@import 'tailwindcss';
2+
@plugin '@tailwindcss/typography';
23
@import '@nuxt/ui';
34

45
@theme {
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<script setup lang="ts">
2+
import { Editor, EditorContent } from '@tiptap/vue-3'
3+
import StarterKit from '@tiptap/starter-kit'
4+
import { Markdown } from '@tiptap/markdown'
5+
6+
const modelValue = defineModel<string>({ default: '' })
7+
8+
const editor = ref<Editor>()
9+
10+
onMounted(() => {
11+
editor.value = new Editor({
12+
extensions: [StarterKit, Markdown],
13+
content: modelValue.value,
14+
onUpdate: ({ editor }) => {
15+
modelValue.value = editor.getMarkdown()
16+
},
17+
})
18+
})
19+
20+
watch(modelValue, (value) => {
21+
const currentMarkdown = editor.value?.getMarkdown()
22+
if (value === currentMarkdown) return
23+
editor.value?.commands.setContent(value, { contentType: 'markdown' })
24+
})
25+
26+
onBeforeUnmount(() => {
27+
editor.value?.destroy()
28+
})
29+
30+
const toolbarItems = [
31+
{
32+
icon: 'i-lucide-bold',
33+
action: () => editor.value?.chain().focus().toggleBold().run(),
34+
isActive: () => editor.value?.isActive('bold'),
35+
title: 'Bold',
36+
},
37+
{
38+
icon: 'i-lucide-italic',
39+
action: () => editor.value?.chain().focus().toggleItalic().run(),
40+
isActive: () => editor.value?.isActive('italic'),
41+
title: 'Italic',
42+
},
43+
{
44+
icon: 'i-lucide-strikethrough',
45+
action: () => editor.value?.chain().focus().toggleStrike().run(),
46+
isActive: () => editor.value?.isActive('strike'),
47+
title: 'Strikethrough',
48+
},
49+
{ type: 'divider' },
50+
{
51+
icon: 'i-lucide-heading-2',
52+
action: () =>
53+
editor.value?.chain().focus().toggleHeading({ level: 2 }).run(),
54+
isActive: () => editor.value?.isActive('heading', { level: 2 }),
55+
title: 'Heading 2',
56+
},
57+
{
58+
icon: 'i-lucide-heading-3',
59+
action: () =>
60+
editor.value?.chain().focus().toggleHeading({ level: 3 }).run(),
61+
isActive: () => editor.value?.isActive('heading', { level: 3 }),
62+
title: 'Heading 3',
63+
},
64+
{ type: 'divider' },
65+
{
66+
icon: 'i-lucide-list',
67+
action: () => editor.value?.chain().focus().toggleBulletList().run(),
68+
isActive: () => editor.value?.isActive('bulletList'),
69+
title: 'Bullet List',
70+
},
71+
{
72+
icon: 'i-lucide-list-ordered',
73+
action: () => editor.value?.chain().focus().toggleOrderedList().run(),
74+
isActive: () => editor.value?.isActive('orderedList'),
75+
title: 'Ordered List',
76+
},
77+
{ type: 'divider' },
78+
{
79+
icon: 'i-lucide-undo',
80+
action: () => editor.value?.chain().focus().undo().run(),
81+
isActive: () => false,
82+
disabled: () => !editor.value?.can().undo(),
83+
title: 'Undo',
84+
},
85+
{
86+
icon: 'i-lucide-redo',
87+
action: () => editor.value?.chain().focus().redo().run(),
88+
isActive: () => false,
89+
disabled: () => !editor.value?.can().redo(),
90+
title: 'Redo',
91+
},
92+
]
93+
</script>
94+
95+
<template>
96+
<div class="border-accented overflow-hidden rounded-md border">
97+
<div
98+
v-if="editor"
99+
class="bg-elevated border-accented flex flex-wrap gap-1 border-b p-1"
100+
>
101+
<template v-for="(item, index) in toolbarItems" :key="index">
102+
<div
103+
v-if="item.type === 'divider'"
104+
class="bg-accented mx-1 w-px self-stretch"
105+
/>
106+
<UButton
107+
v-else
108+
:icon="item.icon"
109+
variant="ghost"
110+
size="xs"
111+
:color="item.isActive?.() ? 'primary' : 'neutral'"
112+
:disabled="item.disabled?.()"
113+
:title="item.title"
114+
@click="
115+
() => {
116+
item.action?.()
117+
}
118+
"
119+
/>
120+
</template>
121+
</div>
122+
<EditorContent
123+
:editor
124+
class="prose prose-sm dark:prose-invert max-w-none p-3 focus:outline-none [&_.ProseMirror]:min-h-[150px] [&_.ProseMirror]:outline-none"
125+
/>
126+
</div>
127+
</template>

frontend/app/pages/admin/projects/[projectId]/edit.vue

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ gql(`
5050
}
5151
}
5252
}
53+
rules {
54+
markdown
55+
html
56+
}
5357
}
5458
}
5559
`)
@@ -103,6 +107,7 @@ const schema = z.object({
103107
}),
104108
rounding: z.number(),
105109
}),
110+
rules: z.string().optional(),
106111
})
107112
type Schema = z.infer<typeof schema>
108113
const state = reactive<Schema>({
@@ -144,6 +149,7 @@ const state = reactive<Schema>({
144149
},
145150
},
146151
},
152+
rules: '',
147153
})
148154
149155
watch(
@@ -157,6 +163,7 @@ watch(
157163
state.branding.logo = d.project.branding.logo ?? undefined
158164
state.branding.rounding = d.project.branding.rounding
159165
state.branding.colors = d.project.branding.colors
166+
state.rules = d.project.rules?.markdown
160167
}
161168
},
162169
{ once: true },
@@ -221,7 +228,7 @@ async function updateProject(event: FormSubmitEvent<Schema>) {
221228
<UForm
222229
:state
223230
:schema="schema"
224-
class="flex max-w-md flex-col gap-6"
231+
class="flex max-w-md flex-col gap-8"
225232
@submit.prevent="updateProject"
226233
>
227234
<UFormField name="name" label="Name">
@@ -245,6 +252,14 @@ async function updateProject(event: FormSubmitEvent<Schema>) {
245252
:project-name="data?.project.name"
246253
/>
247254
</UFormField>
255+
<UFormField
256+
name="rules"
257+
label="Project Rules"
258+
hint="(optional)"
259+
help="Explain how users collect points"
260+
>
261+
<MarkdownEditor v-model="state.rules" />
262+
</UFormField>
248263
<UButton type="submit" size="lg" block>Save changes</UButton>
249264
</UForm>
250265
</UContainer>

frontend/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
"@nuxt/image": "^2.0.0",
2121
"@nuxt/ui": "^4.2.1",
2222
"@nuxtjs/i18n": "^10.2.1",
23+
"@tiptap/core": "^3.11.1",
24+
"@tiptap/markdown": "^3.11.1",
25+
"@tiptap/pm": "^3.11.1",
26+
"@tiptap/starter-kit": "^3.11.1",
27+
"@tiptap/vue-3": "^3.11.1",
2328
"@urql/exchange-auth": "^3.0.0",
2429
"@urql/vue": "^2.0.0",
2530
"@vueuse/core": "^14.1.0",
@@ -40,6 +45,7 @@
4045
"@iconify-json/lucide": "^1.2.76",
4146
"@nuxt/eslint": "^1.11.0",
4247
"@nuxt/test-utils": "^3.20.1",
48+
"@tailwindcss/typography": "^0.5.19",
4349
"@vue/test-utils": "^2.4.6",
4450
"eslint": "^9.39.1",
4551
"happy-dom": "^20.0.11",

0 commit comments

Comments
 (0)