@frigear-nu/forms 0.0.9 → 0.1.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.
- package/dist/module.json +1 -1
- package/dist/module.mjs +7 -5
- package/dist/runtime/app/components/FormBuilderAccess.vue +6 -13
- package/dist/runtime/app/components/FormBuilderFieldRow.d.vue.ts +2 -2
- package/dist/runtime/app/components/FormBuilderFieldRow.vue.d.ts +2 -2
- package/dist/runtime/app/components/FormBuilderStepHeader.d.vue.ts +2 -2
- package/dist/runtime/app/components/FormBuilderStepHeader.vue.d.ts +2 -2
- package/dist/runtime/app/components/FormCreateSlideover.d.vue.ts +8 -0
- package/dist/runtime/app/components/FormCreateSlideover.vue +73 -0
- package/dist/runtime/app/components/FormCreateSlideover.vue.d.ts +8 -0
- package/dist/runtime/app/components/FormPreview.vue +2 -8
- package/dist/runtime/app/composables/index.d.ts +1 -0
- package/dist/runtime/app/composables/index.js +1 -0
- package/dist/runtime/app/composables/useFormActions.d.ts +26 -0
- package/dist/runtime/app/composables/useFormActions.js +103 -0
- package/dist/runtime/app/composables/useForms.d.ts +1 -1
- package/dist/runtime/app/composables/useForms.js +2 -9
- package/dist/runtime/app/pages/admin-form.vue +84 -82
- package/dist/runtime/app/pages/admin-submission.vue +60 -29
- package/dist/runtime/app/pages/admin-submissions.vue +81 -47
- package/dist/runtime/app/pages/admin.vue +152 -233
- package/dist/runtime/app/pages/form.vue +47 -45
- package/dist/runtime/i18n/lang/da.json +31 -10
- package/dist/runtime/i18n/lang/en.json +31 -10
- package/dist/runtime/menus.js +0 -11
- package/package.json +17 -17
|
@@ -1,278 +1,197 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { defineForm } from "../../utils";
|
|
3
2
|
const t = useModuleT("forms");
|
|
4
3
|
const paths = useFormsPaths();
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const { data: forms, error,
|
|
8
|
-
const starter = defineForm({
|
|
9
|
-
mode: "single",
|
|
10
|
-
storage: "db",
|
|
11
|
-
steps: [{
|
|
12
|
-
id: "main",
|
|
13
|
-
fields: [
|
|
14
|
-
{ key: "name", type: "text", label: "Name", required: true },
|
|
15
|
-
{ key: "message", type: "textarea", label: "Message", required: true }
|
|
16
|
-
]
|
|
17
|
-
}]
|
|
18
|
-
});
|
|
19
|
-
const creating = ref(false);
|
|
20
|
-
const draft = reactive({ name: "", slug: "" });
|
|
21
|
-
const importing = ref(false);
|
|
22
|
-
const importJson = ref("");
|
|
23
|
-
const slugify = (value) => value.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
24
|
-
watch(() => draft.name, (name, previous) => {
|
|
25
|
-
if (draft.slug === slugify(previous)) draft.slug = slugify(name);
|
|
26
|
-
});
|
|
27
|
-
async function create() {
|
|
28
|
-
try {
|
|
29
|
-
const created = await $fetch("/api/forms/admin/forms", { method: "POST", body: { ...draft, definition: starter } });
|
|
30
|
-
await navigateTo(paths("adminForm", { id: created.id }));
|
|
31
|
-
} catch (cause) {
|
|
32
|
-
toast.add({ title: cause.statusMessage ?? t("error.generic", "Something went wrong. Please try again."), color: "error" });
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
async function clone(form) {
|
|
36
|
-
try {
|
|
37
|
-
const copy = await $fetch(`/api/forms/admin/forms/${form.id}/clone`, { method: "POST" });
|
|
38
|
-
await navigateTo(paths("adminForm", { id: copy.id }));
|
|
39
|
-
} catch (cause) {
|
|
40
|
-
toast.add({ title: cause.statusMessage ?? t("error.generic", "Something went wrong. Please try again."), color: "error" });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
async function runImport() {
|
|
44
|
-
try {
|
|
45
|
-
const parsed = JSON.parse(importJson.value);
|
|
46
|
-
const result = await $fetch("/api/forms/admin/import", {
|
|
47
|
-
method: "POST",
|
|
48
|
-
body: { forms: Array.isArray(parsed) ? parsed : [parsed] }
|
|
49
|
-
});
|
|
50
|
-
toast.add({ title: t("admin.imported", "Imported {inserted}, skipped {skipped} existing.", { inserted: result.inserted.length, skipped: result.skipped.length }), color: "success" });
|
|
51
|
-
importing.value = false;
|
|
52
|
-
importJson.value = "";
|
|
53
|
-
await refresh();
|
|
54
|
-
} catch (cause) {
|
|
55
|
-
toast.add({ title: cause.statusMessage ?? t("admin.invalidJson", "The JSON is not valid."), color: "error" });
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
const columns = [
|
|
59
|
-
{ accessorKey: "name", header: t("admin.name", "Name") },
|
|
60
|
-
{ accessorKey: "status", header: t("admin.status", "Status") },
|
|
61
|
-
{ id: "actions", header: "" }
|
|
62
|
-
];
|
|
63
|
-
const statusColor = { draft: "neutral", published: "success", archived: "warning" };
|
|
64
|
-
const statuses = ["published", "draft", "archived"];
|
|
65
|
-
const tab = ref("published");
|
|
4
|
+
const format = useFormat();
|
|
5
|
+
const actions = useFormActions();
|
|
6
|
+
const { data: forms, error, status } = await useFetch("/api/forms/admin/forms", { key: FORMS_LIST_KEY });
|
|
66
7
|
const search = ref("");
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
label: t(
|
|
70
|
-
|
|
71
|
-
|
|
8
|
+
const statusFilter = ref("all");
|
|
9
|
+
const statusItems = computed(() => [
|
|
10
|
+
{ value: "all", label: t("admin.allStatuses", "All statuses") },
|
|
11
|
+
...["published", "draft", "archived"].map((value) => ({ value, label: t(`status.${value}`, value) }))
|
|
12
|
+
]);
|
|
72
13
|
const visibleForms = computed(() => {
|
|
73
14
|
const query = search.value.trim().toLowerCase();
|
|
74
15
|
return (forms.value ?? []).filter(
|
|
75
|
-
(form) => form.status ===
|
|
16
|
+
(form) => (statusFilter.value === "all" || form.status === statusFilter.value) && (!query || form.name.toLowerCase().includes(query) || form.slug.toLowerCase().includes(query))
|
|
76
17
|
);
|
|
77
18
|
});
|
|
19
|
+
const filtered = computed(() => !!search.value.trim() || statusFilter.value !== "all");
|
|
20
|
+
function clearFilters() {
|
|
21
|
+
search.value = "";
|
|
22
|
+
statusFilter.value = "all";
|
|
23
|
+
}
|
|
24
|
+
const { sorting, header } = useTableSort({ id: "updatedAt", desc: true });
|
|
25
|
+
const columns = computed(() => [
|
|
26
|
+
{ accessorKey: "name", header: header("name", t("admin.name", "Name")) },
|
|
27
|
+
{ accessorKey: "status", header: header("status", t("admin.status", "Status")) },
|
|
28
|
+
{ accessorKey: "submissionCount", header: header("submissionCount", t("admin.submissionsTitle", "Submissions")), meta: numericColumn },
|
|
29
|
+
{ accessorKey: "updatedAt", header: header("updatedAt", t("admin.updated", "Updated")) },
|
|
30
|
+
{ id: "actions", header: "", meta: { class: { th: "w-0", td: "w-0" } } }
|
|
31
|
+
]);
|
|
32
|
+
const formDescription = (form) => [
|
|
33
|
+
`/${form.slug}`,
|
|
34
|
+
form.mode === "stepped" ? t("admin.stepped", "Stepped") : t("admin.single", "Single page"),
|
|
35
|
+
`v${form.version}`,
|
|
36
|
+
...form.storage === "none" ? [t("admin.notStored", "Not stored")] : []
|
|
37
|
+
].join(" \xB7 ");
|
|
38
|
+
const editPath = (form) => paths("adminForm", { id: form.id });
|
|
39
|
+
const menu = (form) => actions.menu(form, {
|
|
40
|
+
extra: [{ label: t("admin.open", "Open"), icon: "i-lucide-arrow-right", to: editPath(form) }]
|
|
41
|
+
});
|
|
42
|
+
const importing = ref(false);
|
|
43
|
+
const importText = ref("");
|
|
44
|
+
const importBusy = ref(false);
|
|
45
|
+
async function runImport() {
|
|
46
|
+
importBusy.value = true;
|
|
47
|
+
try {
|
|
48
|
+
if (await actions.importJson(importText.value)) {
|
|
49
|
+
importing.value = false;
|
|
50
|
+
importText.value = "";
|
|
51
|
+
}
|
|
52
|
+
} finally {
|
|
53
|
+
importBusy.value = false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
78
56
|
</script>
|
|
79
57
|
|
|
80
58
|
<template>
|
|
81
|
-
<
|
|
82
|
-
<
|
|
59
|
+
<div class="flex flex-col gap-4">
|
|
60
|
+
<PageHeading :title="t('admin.title', 'Forms')">
|
|
83
61
|
<template #actions>
|
|
84
62
|
<UButton
|
|
85
63
|
color="neutral"
|
|
86
64
|
variant="outline"
|
|
87
65
|
icon="i-lucide-upload"
|
|
88
|
-
:label="t('admin.import', 'Import')"
|
|
66
|
+
:label="t('admin.import', 'Import JSON')"
|
|
89
67
|
@click="importing = true"
|
|
90
68
|
/>
|
|
91
69
|
<UButton
|
|
92
70
|
icon="i-lucide-plus"
|
|
93
71
|
:label="t('admin.new', 'New form')"
|
|
94
|
-
@click="
|
|
72
|
+
@click="actions.create()"
|
|
95
73
|
/>
|
|
96
74
|
</template>
|
|
97
|
-
</
|
|
75
|
+
</PageHeading>
|
|
98
76
|
|
|
99
|
-
<
|
|
77
|
+
<UEmpty
|
|
100
78
|
v-if="error"
|
|
101
|
-
|
|
102
|
-
variant="subtle"
|
|
79
|
+
icon="i-lucide-lock"
|
|
103
80
|
:title="t('admin.forbidden', 'You are not allowed to manage forms.')"
|
|
104
81
|
/>
|
|
105
82
|
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
class="w-auto"
|
|
115
|
-
/>
|
|
116
|
-
<UInput
|
|
117
|
-
v-model="search"
|
|
118
|
-
class="w-full sm:w-72"
|
|
119
|
-
icon="i-lucide-search"
|
|
120
|
-
:placeholder="t('admin.search', 'Search forms')"
|
|
121
|
-
:aria-label="t('admin.search', 'Search forms')"
|
|
122
|
-
/>
|
|
123
|
-
</div>
|
|
124
|
-
|
|
125
|
-
<AdminTable
|
|
126
|
-
v-if="!error"
|
|
127
|
-
:data="visibleForms"
|
|
128
|
-
:columns="columns"
|
|
129
|
-
@select="(_, row2) => navigateTo(paths('adminForm', { id: row2.original.id }))"
|
|
130
|
-
>
|
|
131
|
-
<template #name-cell="{ row }">
|
|
132
|
-
<div class="space-y-0.5">
|
|
133
|
-
<p class="font-medium">
|
|
134
|
-
{{ row.original.name }}
|
|
135
|
-
</p>
|
|
136
|
-
<p class="text-sm text-muted">
|
|
137
|
-
/{{ row.original.slug }} · {{ row.original.mode === "stepped" ? t("admin.stepped", "Stepped") : t("admin.single", "Single page") }}
|
|
138
|
-
· v{{ row.original.version }}
|
|
139
|
-
<template v-if="row.original.storage === 'none'">
|
|
140
|
-
· {{ t("admin.notStored", "Not stored") }}
|
|
141
|
-
</template>
|
|
142
|
-
</p>
|
|
143
|
-
</div>
|
|
144
|
-
</template>
|
|
145
|
-
<template #status-cell="{ row }">
|
|
146
|
-
<UBadge
|
|
147
|
-
:color="statusColor[row.original.status]"
|
|
148
|
-
variant="subtle"
|
|
149
|
-
:label="t(`status.${row.original.status}`, row.original.status)"
|
|
83
|
+
<template v-else>
|
|
84
|
+
<PageToolbar>
|
|
85
|
+
<UInput
|
|
86
|
+
v-model="search"
|
|
87
|
+
class="w-full sm:w-72"
|
|
88
|
+
icon="i-lucide-search"
|
|
89
|
+
:placeholder="t('admin.search', 'Search forms')"
|
|
90
|
+
:aria-label="t('admin.search', 'Search forms')"
|
|
150
91
|
/>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
color="neutral"
|
|
157
|
-
variant="outline"
|
|
158
|
-
size="sm"
|
|
159
|
-
:to="paths('adminSubmissions', { id: row.original.id })"
|
|
160
|
-
:label="t('admin.submissions', 'Submissions ({count})', { count: row.original.submissionCount })"
|
|
161
|
-
@click.stop
|
|
162
|
-
/>
|
|
163
|
-
<UButton
|
|
164
|
-
v-if="row.original.status === 'published' && link.enabled.value"
|
|
165
|
-
color="neutral"
|
|
166
|
-
variant="ghost"
|
|
167
|
-
size="sm"
|
|
168
|
-
icon="i-lucide-link"
|
|
169
|
-
:aria-label="t('admin.copyLink', 'Copy link')"
|
|
170
|
-
@click.stop="link.copy(row.original.slug)"
|
|
171
|
-
/>
|
|
172
|
-
<UButton
|
|
173
|
-
color="neutral"
|
|
174
|
-
variant="ghost"
|
|
175
|
-
size="sm"
|
|
176
|
-
icon="i-lucide-copy"
|
|
177
|
-
:aria-label="t('admin.clone', 'Duplicate')"
|
|
178
|
-
@click="clone(form)"
|
|
179
|
-
/>
|
|
180
|
-
<UButton
|
|
181
|
-
color="neutral"
|
|
182
|
-
variant="ghost"
|
|
183
|
-
size="sm"
|
|
184
|
-
icon="i-lucide-pencil"
|
|
185
|
-
:to="paths('adminForm', { id: row.original.id })"
|
|
186
|
-
:aria-label="t('admin.edit', 'Edit')"
|
|
187
|
-
@click.stop
|
|
188
|
-
/>
|
|
189
|
-
</div>
|
|
190
|
-
</template>
|
|
191
|
-
<template #empty>
|
|
192
|
-
<AdminEmpty
|
|
193
|
-
icon="i-lucide-clipboard-list"
|
|
194
|
-
:title="forms?.length ? t('admin.noMatches', 'No forms match.') : t('admin.empty', 'No forms yet.')"
|
|
92
|
+
<USelect
|
|
93
|
+
v-model="statusFilter"
|
|
94
|
+
class="w-full sm:w-44"
|
|
95
|
+
:items="statusItems"
|
|
96
|
+
:aria-label="t('admin.statusFilter', 'Filter by status')"
|
|
195
97
|
/>
|
|
196
|
-
</
|
|
197
|
-
</AdminTable>
|
|
98
|
+
</PageToolbar>
|
|
198
99
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
100
|
+
<TableCard
|
|
101
|
+
:loading="status === 'pending' && !forms"
|
|
102
|
+
:refreshing="status === 'pending' && !!forms"
|
|
103
|
+
>
|
|
104
|
+
<UTable
|
|
105
|
+
v-model:sorting="sorting"
|
|
106
|
+
:data="visibleForms"
|
|
107
|
+
:columns="columns"
|
|
108
|
+
:ui="tableUi()"
|
|
109
|
+
@select="(_, row2) => navigateTo(editPath(row2.original))"
|
|
208
110
|
>
|
|
209
|
-
<
|
|
210
|
-
<
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
autofocus
|
|
111
|
+
<template #name-cell="{ row }">
|
|
112
|
+
<TableIdentity
|
|
113
|
+
:title="row.original.name"
|
|
114
|
+
:description="formDescription(row.original)"
|
|
214
115
|
/>
|
|
215
|
-
</
|
|
216
|
-
<
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
v-model="draft.slug"
|
|
222
|
-
class="w-full"
|
|
116
|
+
</template>
|
|
117
|
+
<template #status-cell="{ row }">
|
|
118
|
+
<UBadge
|
|
119
|
+
:color="statusColor(row.original.status, { archived: 'warning' })"
|
|
120
|
+
variant="subtle"
|
|
121
|
+
:label="t(`status.${row.original.status}`, row.original.status)"
|
|
223
122
|
/>
|
|
224
|
-
</
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
123
|
+
</template>
|
|
124
|
+
<template #updatedAt-cell="{ row }">
|
|
125
|
+
<span class="tabular-nums">{{ format.date(row.original.updatedAt) }}</span>
|
|
126
|
+
</template>
|
|
127
|
+
<template #actions-cell="{ row }">
|
|
128
|
+
<RowMenu
|
|
129
|
+
:items="menu(row.original)"
|
|
130
|
+
:subject="row.original.name"
|
|
131
|
+
/>
|
|
132
|
+
</template>
|
|
133
|
+
<template #empty>
|
|
134
|
+
<UEmpty
|
|
135
|
+
variant="naked"
|
|
136
|
+
icon="i-lucide-clipboard-list"
|
|
137
|
+
:title="filtered ? t('admin.noMatches', 'No forms match.') : t('admin.empty', 'No forms yet')"
|
|
138
|
+
:description="filtered ? void 0 : t('admin.emptyDescription', 'Create a form to start collecting answers.')"
|
|
139
|
+
>
|
|
140
|
+
<template #actions>
|
|
141
|
+
<UButton
|
|
142
|
+
v-if="filtered"
|
|
143
|
+
color="neutral"
|
|
144
|
+
variant="outline"
|
|
145
|
+
:label="t('admin.clearFilters', 'Clear filters')"
|
|
146
|
+
@click="clearFilters"
|
|
147
|
+
/>
|
|
148
|
+
<UButton
|
|
149
|
+
v-else
|
|
150
|
+
icon="i-lucide-plus"
|
|
151
|
+
:label="t('admin.new', 'New form')"
|
|
152
|
+
@click="actions.create()"
|
|
153
|
+
/>
|
|
154
|
+
</template>
|
|
155
|
+
</UEmpty>
|
|
156
|
+
</template>
|
|
157
|
+
</UTable>
|
|
158
|
+
<template
|
|
159
|
+
v-if="forms?.length"
|
|
160
|
+
#footer
|
|
161
|
+
>
|
|
162
|
+
<span>{{ t("admin.showing", "{shown} of {total} forms", { shown: visibleForms.length, total: forms.length }) }}</span>
|
|
163
|
+
</template>
|
|
164
|
+
</TableCard>
|
|
165
|
+
</template>
|
|
244
166
|
|
|
245
167
|
<UModal
|
|
246
168
|
v-model:open="importing"
|
|
247
|
-
:title="t('admin.import', 'Import')"
|
|
169
|
+
:title="t('admin.import', 'Import JSON')"
|
|
170
|
+
:description="t('admin.importHint', 'Paste one form or a list of forms (each with slug, name and definition). Forms whose slug already exists are skipped.')"
|
|
171
|
+
:ui="{ footer: 'justify-end gap-2' }"
|
|
248
172
|
>
|
|
249
173
|
<template #body>
|
|
250
|
-
<
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
v-model="importJson"
|
|
256
|
-
:rows="12"
|
|
257
|
-
class="w-full font-mono text-xs"
|
|
258
|
-
/>
|
|
259
|
-
</div>
|
|
174
|
+
<UTextarea
|
|
175
|
+
v-model="importText"
|
|
176
|
+
:rows="12"
|
|
177
|
+
class="w-full font-mono text-xs"
|
|
178
|
+
/>
|
|
260
179
|
</template>
|
|
261
180
|
<template #footer>
|
|
262
|
-
<
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
181
|
+
<UButton
|
|
182
|
+
color="neutral"
|
|
183
|
+
variant="outline"
|
|
184
|
+
:label="t('admin.cancel', 'Cancel')"
|
|
185
|
+
:disabled="importBusy"
|
|
186
|
+
@click="importing = false"
|
|
187
|
+
/>
|
|
188
|
+
<UButton
|
|
189
|
+
:label="t('admin.importSubmit', 'Import forms')"
|
|
190
|
+
:loading="importBusy"
|
|
191
|
+
:disabled="!importText.trim()"
|
|
192
|
+
@click="runImport"
|
|
193
|
+
/>
|
|
275
194
|
</template>
|
|
276
195
|
</UModal>
|
|
277
|
-
</
|
|
196
|
+
</div>
|
|
278
197
|
</template>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
const route = useRoute();
|
|
3
3
|
const t = useModuleT("forms");
|
|
4
|
+
const format = useFormat();
|
|
4
5
|
const slug = computed(() => String(route.params.slug));
|
|
5
6
|
const { data: access } = await useFormAccess(slug);
|
|
6
7
|
if (access.value && (access.value.access.availability === "api" || access.value.access.auth === "token")) {
|
|
@@ -32,53 +33,54 @@ useHead({ title: computed(() => access.value?.name ?? "") });
|
|
|
32
33
|
</script>
|
|
33
34
|
|
|
34
35
|
<template>
|
|
35
|
-
<UContainer
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
<UContainer>
|
|
37
|
+
<UPage>
|
|
38
|
+
<UPageHeader
|
|
39
|
+
v-if="access"
|
|
40
|
+
:title="access.name"
|
|
41
|
+
/>
|
|
42
|
+
<UPageBody class="max-w-2xl">
|
|
43
|
+
<UAlert
|
|
44
|
+
v-if="access && !access.open"
|
|
45
|
+
icon="i-lucide-calendar-x"
|
|
46
|
+
color="neutral"
|
|
47
|
+
variant="subtle"
|
|
48
|
+
:title="t('schedule.closed', 'This form is closed.')"
|
|
49
|
+
:description="access.opensAt ? t('schedule.opensAt', 'It opens again {date}.', { date: format.dateTime(access.opensAt) }) : void 0"
|
|
50
|
+
/>
|
|
42
51
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
<SectionCard v-else-if="gated && !unlocked">
|
|
53
|
+
<form
|
|
54
|
+
class="space-y-4"
|
|
55
|
+
@submit.prevent="unlock"
|
|
56
|
+
>
|
|
57
|
+
<UFormField
|
|
58
|
+
:label="t('access.passwordLabel', 'Password')"
|
|
59
|
+
:description="t('access.passwordHint', 'This form is protected. Enter the password you were given.')"
|
|
60
|
+
:error="wrong ? t('access.passwordWrong', 'Wrong password.') : void 0"
|
|
61
|
+
>
|
|
62
|
+
<UInput
|
|
63
|
+
v-model="password"
|
|
64
|
+
type="password"
|
|
65
|
+
autocomplete="off"
|
|
66
|
+
class="w-full"
|
|
67
|
+
/>
|
|
68
|
+
</UFormField>
|
|
69
|
+
<UButton
|
|
70
|
+
type="submit"
|
|
71
|
+
:loading="checking"
|
|
72
|
+
:disabled="!password"
|
|
73
|
+
:label="t('access.unlock', 'Continue')"
|
|
74
|
+
/>
|
|
75
|
+
</form>
|
|
76
|
+
</SectionCard>
|
|
51
77
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
>
|
|
57
|
-
<UFormField
|
|
58
|
-
:label="t('access.passwordLabel', 'Password')"
|
|
59
|
-
:description="t('access.passwordHint', 'This form is protected. Enter the password you were given.')"
|
|
60
|
-
:error="wrong ? t('access.passwordWrong', 'Wrong password.') : void 0"
|
|
61
|
-
>
|
|
62
|
-
<UInput
|
|
63
|
-
v-model="password"
|
|
64
|
-
type="password"
|
|
65
|
-
autocomplete="off"
|
|
66
|
-
class="w-full"
|
|
67
|
-
/>
|
|
68
|
-
</UFormField>
|
|
69
|
-
<UButton
|
|
70
|
-
type="submit"
|
|
71
|
-
:loading="checking"
|
|
72
|
-
:disabled="!password"
|
|
73
|
-
:label="t('access.unlock', 'Continue')"
|
|
78
|
+
<FormRenderer
|
|
79
|
+
v-else-if="!needsSignIn"
|
|
80
|
+
:slug="slug"
|
|
81
|
+
:password="unlocked || void 0"
|
|
74
82
|
/>
|
|
75
|
-
</
|
|
76
|
-
</
|
|
77
|
-
|
|
78
|
-
<FormRenderer
|
|
79
|
-
v-else-if="!needsSignIn"
|
|
80
|
-
:slug="slug"
|
|
81
|
-
:password="unlocked || void 0"
|
|
82
|
-
/>
|
|
83
|
+
</UPageBody>
|
|
84
|
+
</UPage>
|
|
83
85
|
</UContainer>
|
|
84
86
|
</template>
|
|
@@ -48,12 +48,6 @@
|
|
|
48
48
|
"published": "Udgivet",
|
|
49
49
|
"archived": "Arkiveret"
|
|
50
50
|
},
|
|
51
|
-
"delivery": {
|
|
52
|
-
"none": "Ikke leveret",
|
|
53
|
-
"pending": "Afventer",
|
|
54
|
-
"delivered": "Leveret",
|
|
55
|
-
"failed": "Levering fejlede"
|
|
56
|
-
},
|
|
57
51
|
"validation": {
|
|
58
52
|
"required": "Feltet er påkrævet.",
|
|
59
53
|
"email": "Indtast en gyldig e-mailadresse.",
|
|
@@ -74,19 +68,19 @@
|
|
|
74
68
|
"admin": {
|
|
75
69
|
"title": "Formularer",
|
|
76
70
|
"new": "Ny formular",
|
|
77
|
-
"import": "Importér",
|
|
71
|
+
"import": "Importér JSON",
|
|
78
72
|
"edit": "Rediger",
|
|
79
73
|
"create": "Opret",
|
|
80
74
|
"copyLink": "Kopiér link",
|
|
81
75
|
"linkCopied": "Link kopieret.",
|
|
82
76
|
"settings": "Levering og lagring",
|
|
83
|
-
"save": "Gem",
|
|
77
|
+
"save": "Gem formular",
|
|
84
78
|
"saved": "Gemt.",
|
|
85
79
|
"name": "Navn",
|
|
86
80
|
"slug": "Slug",
|
|
87
81
|
"slugHint": "Bruges i den offentlige adresse.",
|
|
88
82
|
"status": "Status",
|
|
89
|
-
"empty": "Ingen formularer endnu
|
|
83
|
+
"empty": "Ingen formularer endnu",
|
|
90
84
|
"stepped": "Flere trin",
|
|
91
85
|
"single": "Én side",
|
|
92
86
|
"notStored": "Gemmes ikke",
|
|
@@ -109,7 +103,30 @@
|
|
|
109
103
|
"builder": "Opbygning",
|
|
110
104
|
"json": "JSON",
|
|
111
105
|
"access": "Adgang",
|
|
112
|
-
"clone": "Duplikér"
|
|
106
|
+
"clone": "Duplikér formular",
|
|
107
|
+
"createForm": "Opret formular",
|
|
108
|
+
"importSubmit": "Importér formularer",
|
|
109
|
+
"search": "Søg i formularer",
|
|
110
|
+
"statusFilter": "Filtrér efter status",
|
|
111
|
+
"allStatuses": "Alle statusser",
|
|
112
|
+
"clearFilters": "Ryd filtre",
|
|
113
|
+
"noMatches": "Ingen formularer matcher.",
|
|
114
|
+
"emptyDescription": "Opret en formular for at begynde at indsamle svar.",
|
|
115
|
+
"showing": "{shown} af {total} formularer",
|
|
116
|
+
"open": "Åbn",
|
|
117
|
+
"updated": "Opdateret",
|
|
118
|
+
"cloned": "Formular duplikeret",
|
|
119
|
+
"created": "Formular oprettet",
|
|
120
|
+
"archive": "Arkivér formular",
|
|
121
|
+
"archiveTitle": "Arkivér {name}?",
|
|
122
|
+
"archiveBody": "Den modtager ikke længere besvarelser og forsvinder fra det offentlige site. Besvarelserne beholdes.",
|
|
123
|
+
"archived": "Formular arkiveret",
|
|
124
|
+
"allForms": "Alle formularer",
|
|
125
|
+
"notFound": "Formularen findes ikke.",
|
|
126
|
+
"details": "Detaljer",
|
|
127
|
+
"submissionNotFound": "Besvarelsen findes ikke.",
|
|
128
|
+
"noSubmissionsHint": "Svar vises her, når nogen udfylder formularen.",
|
|
129
|
+
"redelivered": "Leveret igen"
|
|
113
130
|
},
|
|
114
131
|
"schedule": {
|
|
115
132
|
"closed": "Formularen er lukket.",
|
|
@@ -175,6 +192,10 @@
|
|
|
175
192
|
}
|
|
176
193
|
},
|
|
177
194
|
"delivery": {
|
|
195
|
+
"none": "Ikke leveret",
|
|
196
|
+
"pending": "Afventer",
|
|
197
|
+
"delivered": "Leveret",
|
|
198
|
+
"failed": "Levering fejlede",
|
|
178
199
|
"title": "Levering og lagring",
|
|
179
200
|
"storeTitle": "Gem besvarelser",
|
|
180
201
|
"storeHint": "Gem besvarelser i databasen, så de kan ses under Besvarelser.",
|