mktcms 0.1.22 → 0.1.23
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 +37 -12
- package/dist/runtime/app/components/content/delete.vue +1 -1
- package/dist/runtime/app/components/content/editor/csv.vue +79 -53
- package/dist/runtime/app/components/content/editor/frontmatter/form.d.vue.ts +10 -0
- package/dist/runtime/app/components/content/editor/frontmatter/form.vue +17 -0
- package/dist/runtime/app/components/content/editor/frontmatter/form.vue.d.ts +10 -0
- package/dist/runtime/app/components/content/editor/frontmatter/input.d.vue.ts +14 -0
- package/dist/runtime/app/components/content/editor/frontmatter/input.vue +53 -0
- package/dist/runtime/app/components/content/editor/frontmatter/input.vue.d.ts +14 -0
- package/dist/runtime/app/components/content/editor/image.vue +1 -1
- package/dist/runtime/app/components/content/editor/markdown.vue +30 -7
- package/dist/runtime/app/components/content/editor/pdf.vue +1 -1
- package/dist/runtime/app/components/content/editor/selectFile/breadcrumb.d.vue.ts +10 -0
- package/dist/runtime/app/components/content/editor/selectFile/breadcrumb.vue +51 -0
- package/dist/runtime/app/components/content/editor/selectFile/breadcrumb.vue.d.ts +10 -0
- package/dist/runtime/app/components/content/editor/selectFile/index.d.vue.ts +12 -0
- package/dist/runtime/app/components/content/editor/selectFile/index.vue +89 -0
- package/dist/runtime/app/components/content/editor/selectFile/index.vue.d.ts +12 -0
- package/dist/runtime/app/components/content/editor/txt.vue +19 -2
- package/dist/runtime/app/components/content/fileButtons.vue +22 -3
- package/dist/runtime/app/components/content/files.vue +16 -1
- package/dist/runtime/app/components/content/index.vue +6 -27
- package/dist/runtime/app/components/content/upload.vue +22 -16
- package/dist/runtime/app/composables/useAdminUpload.d.ts +0 -1
- package/dist/runtime/app/composables/useAdminUpload.js +4 -15
- package/dist/runtime/app/pages/admin/edit/[path].vue +3 -0
- package/dist/runtime/app/styles/admin.css +1 -1
- package/dist/runtime/app/styles/admin.min.css +1 -1
- package/dist/runtime/server/api/admin/blob.js +37 -0
- package/dist/runtime/server/api/admin/csv.d.ts +5 -0
- package/dist/runtime/server/api/admin/csv.js +28 -0
- package/dist/runtime/server/api/admin/csv.post.js +28 -0
- package/dist/runtime/server/api/admin/delete.d.ts +4 -0
- package/dist/runtime/server/api/admin/{content/[path].delete.js → delete.js} +5 -4
- package/dist/runtime/server/api/admin/{content/[path].post.d.ts → download.d.ts} +1 -1
- package/dist/runtime/server/api/admin/download.js +29 -0
- package/dist/runtime/server/api/admin/list.d.ts +5 -0
- package/dist/runtime/server/api/admin/list.js +24 -0
- package/dist/runtime/server/api/admin/md.d.ts +5 -0
- package/dist/runtime/server/api/admin/md.js +22 -0
- package/dist/runtime/server/api/admin/md.post.d.ts +4 -0
- package/dist/runtime/server/api/admin/md.post.js +31 -0
- package/dist/runtime/server/api/admin/{content/list.d.ts → txt.d.ts} +1 -1
- package/dist/runtime/server/api/admin/txt.js +21 -0
- package/dist/runtime/server/api/admin/txt.post.d.ts +4 -0
- package/dist/runtime/server/api/admin/txt.post.js +19 -0
- package/dist/runtime/server/api/admin/{content/upload.js → upload.js} +1 -1
- package/dist/runtime/server/api/content/[path].js +15 -9
- package/dist/runtime/server/api/content/list.d.ts +11 -1
- package/dist/runtime/server/api/content/list.js +36 -4
- package/dist/runtime/server/utils/parseFrontmatter.d.ts +4 -0
- package/dist/runtime/server/utils/parseFrontmatter.js +24 -0
- package/package.json +3 -3
- package/dist/runtime/app/composables/useSaveContent.d.ts +0 -7
- package/dist/runtime/app/composables/useSaveContent.js +0 -31
- package/dist/runtime/app/util/csv.d.ts +0 -13
- package/dist/runtime/app/util/csv.js +0 -38
- package/dist/runtime/server/api/admin/content/[path].js +0 -29
- package/dist/runtime/server/api/admin/content/[path].post.js +0 -18
- package/dist/runtime/server/api/admin/content/list.js +0 -13
- /package/dist/runtime/server/api/admin/{content/[path].d.ts → blob.d.ts} +0 -0
- /package/dist/runtime/server/api/admin/{content/[path].delete.d.ts → csv.post.d.ts} +0 -0
- /package/dist/runtime/server/api/admin/{content/upload.d.ts → upload.d.ts} +0 -0
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -45,27 +45,52 @@ const module$1 = defineNuxtModule({
|
|
|
45
45
|
handler: resolver.resolve("./runtime/server/api/admin/logout")
|
|
46
46
|
});
|
|
47
47
|
addServerHandler({
|
|
48
|
-
route: "/api/admin/
|
|
49
|
-
handler: resolver.resolve("./runtime/server/api/admin/
|
|
48
|
+
route: "/api/admin/list",
|
|
49
|
+
handler: resolver.resolve("./runtime/server/api/admin/list")
|
|
50
50
|
});
|
|
51
51
|
addServerHandler({
|
|
52
|
-
route: "/api/admin/
|
|
53
|
-
method: "
|
|
54
|
-
handler: resolver.resolve("./runtime/server/api/admin/
|
|
52
|
+
route: "/api/admin/delete",
|
|
53
|
+
method: "delete",
|
|
54
|
+
handler: resolver.resolve("./runtime/server/api/admin/delete")
|
|
55
|
+
});
|
|
56
|
+
addServerHandler({
|
|
57
|
+
route: "/api/admin/csv",
|
|
58
|
+
handler: resolver.resolve("./runtime/server/api/admin/csv")
|
|
55
59
|
});
|
|
56
60
|
addServerHandler({
|
|
57
|
-
route: "/api/admin/
|
|
61
|
+
route: "/api/admin/csv",
|
|
58
62
|
method: "post",
|
|
59
|
-
handler: resolver.resolve("./runtime/server/api/admin/
|
|
63
|
+
handler: resolver.resolve("./runtime/server/api/admin/csv.post")
|
|
60
64
|
});
|
|
61
65
|
addServerHandler({
|
|
62
|
-
route: "/api/admin/
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
route: "/api/admin/md",
|
|
67
|
+
handler: resolver.resolve("./runtime/server/api/admin/md")
|
|
68
|
+
});
|
|
69
|
+
addServerHandler({
|
|
70
|
+
route: "/api/admin/md",
|
|
71
|
+
method: "post",
|
|
72
|
+
handler: resolver.resolve("./runtime/server/api/admin/md.post")
|
|
73
|
+
});
|
|
74
|
+
addServerHandler({
|
|
75
|
+
route: "/api/admin/txt",
|
|
76
|
+
handler: resolver.resolve("./runtime/server/api/admin/txt")
|
|
77
|
+
});
|
|
78
|
+
addServerHandler({
|
|
79
|
+
route: "/api/admin/txt",
|
|
80
|
+
method: "post",
|
|
81
|
+
handler: resolver.resolve("./runtime/server/api/admin/txt.post")
|
|
82
|
+
});
|
|
83
|
+
addServerHandler({
|
|
84
|
+
route: "/api/admin/blob",
|
|
85
|
+
handler: resolver.resolve("./runtime/server/api/admin/blob")
|
|
86
|
+
});
|
|
87
|
+
addServerHandler({
|
|
88
|
+
route: "/api/admin/upload",
|
|
89
|
+
handler: resolver.resolve("./runtime/server/api/admin/upload")
|
|
65
90
|
});
|
|
66
91
|
addServerHandler({
|
|
67
|
-
route: "/api/admin/
|
|
68
|
-
handler: resolver.resolve("./runtime/server/api/admin/
|
|
92
|
+
route: "/api/admin/download",
|
|
93
|
+
handler: resolver.resolve("./runtime/server/api/admin/download")
|
|
69
94
|
});
|
|
70
95
|
addServerHandler({
|
|
71
96
|
route: "/api/content/list",
|
|
@@ -4,7 +4,7 @@ import usePathParam from "../../composables/usePathParam";
|
|
|
4
4
|
const { path, pathParts } = usePathParam();
|
|
5
5
|
const parentPath = pathParts.slice(0, -1).join(":");
|
|
6
6
|
async function deleteContent() {
|
|
7
|
-
await fetch(`/api/admin/
|
|
7
|
+
await fetch(`/api/admin/delete?path=${path}`, {
|
|
8
8
|
method: "DELETE"
|
|
9
9
|
});
|
|
10
10
|
await navigateTo(`/admin/${parentPath}`);
|
|
@@ -1,57 +1,91 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import {
|
|
3
|
-
import useSaveContent from "../../../composables/useSaveContent";
|
|
4
|
-
import { parseSemicolonCsv, serializeSemicolonCsv } from "../../../util/csv";
|
|
2
|
+
import { ref } from "vue";
|
|
5
3
|
import Saved from "../saved.vue";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
4
|
+
import usePathParam from "../../../composables/usePathParam";
|
|
5
|
+
import { useFetch } from "#imports";
|
|
6
|
+
const { path } = usePathParam();
|
|
7
|
+
const { data: table } = await useFetch(`/api/admin/csv?path=${path}`);
|
|
8
|
+
const isSaving = ref(false);
|
|
9
|
+
const savingSuccessful = ref(false);
|
|
12
10
|
async function saveCsv() {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
if (!table.value) return;
|
|
12
|
+
isSaving.value = true;
|
|
13
|
+
savingSuccessful.value = false;
|
|
14
|
+
try {
|
|
15
|
+
await useFetch(`/api/admin/csv?path=${path}`, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
body: {
|
|
18
|
+
table: table.value
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
savingSuccessful.value = true;
|
|
22
|
+
} catch (e) {
|
|
23
|
+
console.error("Fehler beim Speichern der CSV-Datei:", e);
|
|
24
|
+
} finally {
|
|
25
|
+
isSaving.value = false;
|
|
26
|
+
}
|
|
20
27
|
}
|
|
21
28
|
function insertRow(atIndex) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
if (!table.value) return;
|
|
30
|
+
const newRow = [];
|
|
31
|
+
const headerCount = table.value.headers.length;
|
|
32
|
+
for (let i = 0; i < headerCount; i++) {
|
|
33
|
+
newRow[i] = "-";
|
|
34
|
+
}
|
|
35
|
+
table.value = {
|
|
36
|
+
headers: table.value.headers,
|
|
37
|
+
rows: [
|
|
38
|
+
...table.value.rows.slice(0, atIndex),
|
|
39
|
+
newRow,
|
|
40
|
+
...table.value.rows.slice(atIndex)
|
|
41
|
+
]
|
|
42
|
+
};
|
|
25
43
|
}
|
|
26
44
|
function removeRow(rowIndex) {
|
|
27
|
-
|
|
28
|
-
|
|
45
|
+
if (!table.value) return;
|
|
46
|
+
table.value = {
|
|
47
|
+
headers: table.value.headers,
|
|
48
|
+
rows: table.value.rows.filter((_, index) => index !== rowIndex)
|
|
49
|
+
};
|
|
29
50
|
}
|
|
30
51
|
function moveRowUp(rowIndex) {
|
|
31
|
-
if (rowIndex <= 0) return;
|
|
32
|
-
const row =
|
|
52
|
+
if (!table.value || rowIndex <= 0) return;
|
|
53
|
+
const row = table.value.rows.splice(rowIndex, 1)[0];
|
|
33
54
|
if (!row) return;
|
|
34
|
-
|
|
35
|
-
|
|
55
|
+
table.value = {
|
|
56
|
+
headers: table.value.headers,
|
|
57
|
+
rows: [
|
|
58
|
+
...table.value.rows.slice(0, rowIndex - 1),
|
|
59
|
+
row,
|
|
60
|
+
...table.value.rows.slice(rowIndex - 1)
|
|
61
|
+
]
|
|
62
|
+
};
|
|
36
63
|
}
|
|
37
64
|
function moveRowDown(rowIndex) {
|
|
38
|
-
if (rowIndex >=
|
|
39
|
-
const row =
|
|
65
|
+
if (!table.value || rowIndex >= table.value.rows.length - 1) return;
|
|
66
|
+
const row = table.value.rows.splice(rowIndex, 1)[0];
|
|
40
67
|
if (!row) return;
|
|
41
|
-
|
|
42
|
-
|
|
68
|
+
table.value = {
|
|
69
|
+
headers: table.value.headers,
|
|
70
|
+
rows: [
|
|
71
|
+
...table.value.rows.slice(0, rowIndex + 1),
|
|
72
|
+
row,
|
|
73
|
+
...table.value.rows.slice(rowIndex + 1)
|
|
74
|
+
]
|
|
75
|
+
};
|
|
43
76
|
}
|
|
44
77
|
const editingCell = ref(null);
|
|
45
78
|
const editBuffer = ref("");
|
|
46
79
|
function startEdit(rowIndex, colIndex) {
|
|
80
|
+
if (!table.value) return;
|
|
47
81
|
editingCell.value = { rowIndex, colIndex };
|
|
48
|
-
editBuffer.value =
|
|
82
|
+
editBuffer.value = table.value.rows[rowIndex][colIndex] || "";
|
|
49
83
|
}
|
|
50
84
|
function saveEdit() {
|
|
85
|
+
if (!table.value) return;
|
|
51
86
|
if (!editingCell.value) return;
|
|
52
87
|
const { rowIndex, colIndex } = editingCell.value;
|
|
53
|
-
|
|
54
|
-
hasUnsavedChanges.value = true;
|
|
88
|
+
table.value.rows[rowIndex][colIndex] = editBuffer.value;
|
|
55
89
|
cancelEdit();
|
|
56
90
|
}
|
|
57
91
|
function cancelEdit() {
|
|
@@ -62,21 +96,14 @@ function cancelEdit() {
|
|
|
62
96
|
|
|
63
97
|
<template>
|
|
64
98
|
<div class="w-full">
|
|
65
|
-
<div
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
>
|
|
70
|
-
Keine Kopfzeile gefunden. Bitte eine CSV mit Kopfzeile bereitstellen, um Zeilen zu bearbeiten.
|
|
71
|
-
</span>
|
|
72
|
-
</div>
|
|
73
|
-
|
|
74
|
-
<div class="bg-white">
|
|
99
|
+
<div
|
|
100
|
+
v-if="table"
|
|
101
|
+
class="bg-white"
|
|
102
|
+
>
|
|
75
103
|
<div class="flex items-center h-0 justify-center border border-gray-200 rounded-sm mb-6">
|
|
76
104
|
<button
|
|
77
105
|
type="button"
|
|
78
106
|
class="button small soft"
|
|
79
|
-
:disabled="headers.length === 0"
|
|
80
107
|
@click="insertRow(0)"
|
|
81
108
|
>
|
|
82
109
|
<svg
|
|
@@ -98,7 +125,7 @@ function cancelEdit() {
|
|
|
98
125
|
|
|
99
126
|
<div>
|
|
100
127
|
<template
|
|
101
|
-
v-for="(r, rowIndex) in rows"
|
|
128
|
+
v-for="(r, rowIndex) in table.rows"
|
|
102
129
|
:key="rowIndex"
|
|
103
130
|
>
|
|
104
131
|
<div
|
|
@@ -112,7 +139,9 @@ function cancelEdit() {
|
|
|
112
139
|
class="p-1.5 border-r border-gray-200 box-border last:border-r-0"
|
|
113
140
|
>
|
|
114
141
|
<div class="text-xs mb-1.5 leading-tight wrap-break-word flex items-center justify-between gap-1">
|
|
115
|
-
|
|
142
|
+
<div class="font-bold">
|
|
143
|
+
{{ table.headers[colIndex] || "Spalte " + (colIndex + 1) }}
|
|
144
|
+
</div>
|
|
116
145
|
</div>
|
|
117
146
|
<div
|
|
118
147
|
class="text-xs whitespace-pre-line wrap-break-word border border-gray-300/70 rounded bg-gray-50 p-1.5 min-h-9.5 leading-[1.35] box-border overflow-hidden line-clamp-4 h-[calc(4*1.35*1em+12px)] max-[640px]:line-clamp-3 max-[640px]:h-[calc(3*1.35*1em+12px)] cursor-pointer"
|
|
@@ -152,7 +181,7 @@ function cancelEdit() {
|
|
|
152
181
|
<button
|
|
153
182
|
type="button"
|
|
154
183
|
class="button small"
|
|
155
|
-
:disabled="rowIndex === rows.length - 1"
|
|
184
|
+
:disabled="rowIndex === table.rows.length - 1"
|
|
156
185
|
aria-label="Zeile nach unten"
|
|
157
186
|
title="Nach unten"
|
|
158
187
|
@click="moveRowDown(rowIndex)"
|
|
@@ -201,7 +230,7 @@ function cancelEdit() {
|
|
|
201
230
|
<button
|
|
202
231
|
type="button"
|
|
203
232
|
class="button small"
|
|
204
|
-
:disabled="headers.length === 0"
|
|
233
|
+
:disabled="table.headers.length === 0"
|
|
205
234
|
@click="insertRow(rowIndex + 1)"
|
|
206
235
|
>
|
|
207
236
|
<svg
|
|
@@ -232,7 +261,7 @@ function cancelEdit() {
|
|
|
232
261
|
<span v-if="isSaving">Speichern...</span>
|
|
233
262
|
<span v-else>Speichern</span>
|
|
234
263
|
</button>
|
|
235
|
-
<Saved v-if="savingSuccessful
|
|
264
|
+
<Saved v-if="savingSuccessful" />
|
|
236
265
|
|
|
237
266
|
<div
|
|
238
267
|
v-if="editingCell"
|
|
@@ -244,14 +273,11 @@ function cancelEdit() {
|
|
|
244
273
|
class="w-full max-w-180 bg-white rounded-[10px] border border-black/10 shadow-[0_10px_40px_rgba(0,0,0,0.28)] p-3.5 flex flex-col gap-2.5"
|
|
245
274
|
role="dialog"
|
|
246
275
|
aria-modal="true"
|
|
247
|
-
|
|
276
|
+
aria-label="Zelle bearbeiten"
|
|
248
277
|
>
|
|
249
278
|
<div class="flex flex-col gap-0.5">
|
|
250
279
|
<div class="font-bold">
|
|
251
|
-
|
|
252
|
-
</div>
|
|
253
|
-
<div class="opacity-75 text-[13px]">
|
|
254
|
-
{{ getHeaderLabel(editingCell.colIndex) }} · Zeile {{ editingCell.rowIndex + 1 }}
|
|
280
|
+
{{ table?.headers[editingCell.colIndex] || "Spalte " + (editingCell.colIndex + 1) }}
|
|
255
281
|
</div>
|
|
256
282
|
</div>
|
|
257
283
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type __VLS_ModelProps = {
|
|
2
|
+
'frontmatter': Record<string, any>;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
5
|
+
"update:frontmatter": (value: Record<string, any>) => any;
|
|
6
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
7
|
+
"onUpdate:frontmatter"?: ((value: Record<string, any>) => any) | undefined;
|
|
8
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
9
|
+
declare const _default: typeof __VLS_export;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import FrontmatterInput from "./input.vue";
|
|
3
|
+
const frontmatter = defineModel("frontmatter", { type: Object, ...{
|
|
4
|
+
required: true
|
|
5
|
+
} });
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<div class="flex gap-2">
|
|
10
|
+
<FrontmatterInput
|
|
11
|
+
v-for="key in Object.keys(frontmatter)"
|
|
12
|
+
:key="key"
|
|
13
|
+
v-model:value="frontmatter[key]"
|
|
14
|
+
:label="key"
|
|
15
|
+
/>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type __VLS_ModelProps = {
|
|
2
|
+
'frontmatter': Record<string, any>;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
5
|
+
"update:frontmatter": (value: Record<string, any>) => any;
|
|
6
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
7
|
+
"onUpdate:frontmatter"?: ((value: Record<string, any>) => any) | undefined;
|
|
8
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
9
|
+
declare const _default: typeof __VLS_export;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
label: string;
|
|
3
|
+
};
|
|
4
|
+
type __VLS_ModelProps = {
|
|
5
|
+
'value': string;
|
|
6
|
+
};
|
|
7
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
8
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"update:value": (value: string) => any;
|
|
10
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
11
|
+
"onUpdate:value"?: ((value: string) => any) | undefined;
|
|
12
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
declare const _default: typeof __VLS_export;
|
|
14
|
+
export default _default;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref } from "vue";
|
|
3
|
+
import SelectFile from "../selectFile/index.vue";
|
|
4
|
+
defineProps({
|
|
5
|
+
label: { type: String, required: true }
|
|
6
|
+
});
|
|
7
|
+
const value = defineModel("value", { type: String, ...{
|
|
8
|
+
required: true
|
|
9
|
+
} });
|
|
10
|
+
const showFileSelect = ref(false);
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<div class="flex flex-col gap-2 w-full">
|
|
15
|
+
<label class="font-bold">
|
|
16
|
+
{{ label }}
|
|
17
|
+
</label>
|
|
18
|
+
<div class="relative">
|
|
19
|
+
<input
|
|
20
|
+
v-model="value"
|
|
21
|
+
type="text"
|
|
22
|
+
class="w-full"
|
|
23
|
+
>
|
|
24
|
+
<button
|
|
25
|
+
type="button"
|
|
26
|
+
class="absolute top-1/2 right-2 -translate-y-1/2 button secondary small"
|
|
27
|
+
@click="showFileSelect = true"
|
|
28
|
+
>
|
|
29
|
+
<svg
|
|
30
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
31
|
+
fill="none"
|
|
32
|
+
viewBox="0 0 24 24"
|
|
33
|
+
stroke-width="1.5"
|
|
34
|
+
stroke="currentColor"
|
|
35
|
+
class="size-5"
|
|
36
|
+
>
|
|
37
|
+
<path
|
|
38
|
+
stroke-linecap="round"
|
|
39
|
+
stroke-linejoin="round"
|
|
40
|
+
d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
|
|
41
|
+
/>
|
|
42
|
+
</svg>
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
<SelectFile
|
|
46
|
+
:is-open="showFileSelect"
|
|
47
|
+
@close="showFileSelect = false"
|
|
48
|
+
@select="(filePath) => {
|
|
49
|
+
value = filePath;
|
|
50
|
+
}"
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
label: string;
|
|
3
|
+
};
|
|
4
|
+
type __VLS_ModelProps = {
|
|
5
|
+
'value': string;
|
|
6
|
+
};
|
|
7
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
8
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"update:value": (value: string) => any;
|
|
10
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
11
|
+
"onUpdate:value"?: ((value: string) => any) | undefined;
|
|
12
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
declare const _default: typeof __VLS_export;
|
|
14
|
+
export default _default;
|
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref } from "vue";
|
|
3
3
|
import { marked } from "marked";
|
|
4
|
-
import useSaveContent from "../../../composables/useSaveContent";
|
|
5
4
|
import Saved from "../saved.vue";
|
|
6
|
-
|
|
5
|
+
import usePathParam from "../../../composables/usePathParam";
|
|
6
|
+
import { useFetch } from "#imports";
|
|
7
|
+
import FrontmatterForm from "./frontmatter/form.vue";
|
|
8
|
+
const { path } = usePathParam();
|
|
9
|
+
const { data: content } = await useFetch(`/api/admin/md?path=${path}`);
|
|
10
|
+
const frontmatter = ref(content.value?.frontmatter ?? {});
|
|
11
|
+
const markdown = ref(content.value?.markdown ?? "");
|
|
12
|
+
const isSaving = ref(false);
|
|
13
|
+
const savingSuccessful = ref(false);
|
|
14
|
+
async function saveMarkdown() {
|
|
15
|
+
if (!content.value) return;
|
|
16
|
+
isSaving.value = true;
|
|
17
|
+
savingSuccessful.value = false;
|
|
18
|
+
await useFetch(`/api/admin/md?path=${path}`, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
body: {
|
|
21
|
+
frontmatter: frontmatter.value,
|
|
22
|
+
markdown: markdown.value
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
isSaving.value = false;
|
|
26
|
+
savingSuccessful.value = true;
|
|
27
|
+
}
|
|
7
28
|
const mode = ref("edit");
|
|
8
29
|
function escapeHtml(value) {
|
|
9
30
|
return (value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -20,6 +41,7 @@ function isSafeHref(href) {
|
|
|
20
41
|
}
|
|
21
42
|
}
|
|
22
43
|
const renderedHtml = computed(() => {
|
|
44
|
+
if (!content.value) return "";
|
|
23
45
|
const renderer = new marked.Renderer();
|
|
24
46
|
renderer.html = (token) => escapeHtml(token.text);
|
|
25
47
|
renderer.link = function(token) {
|
|
@@ -37,7 +59,7 @@ const renderedHtml = computed(() => {
|
|
|
37
59
|
const t = token.title ? ` title="${escapeHtml(token.title)}"` : "";
|
|
38
60
|
return `<img src="${escapeHtml(safe)}" alt="${alt}"${t} />`;
|
|
39
61
|
};
|
|
40
|
-
return marked.parse(content.value ?? "", {
|
|
62
|
+
return marked.parse(content.value.markdown ?? "", {
|
|
41
63
|
renderer,
|
|
42
64
|
gfm: true,
|
|
43
65
|
breaks: true
|
|
@@ -46,8 +68,9 @@ const renderedHtml = computed(() => {
|
|
|
46
68
|
</script>
|
|
47
69
|
|
|
48
70
|
<template>
|
|
49
|
-
<div>
|
|
50
|
-
<
|
|
71
|
+
<div v-if="content">
|
|
72
|
+
<FrontmatterForm v-model:frontmatter="frontmatter" />
|
|
73
|
+
<div class="flex gap-2 my-2">
|
|
51
74
|
<button
|
|
52
75
|
type="button"
|
|
53
76
|
class="button secondary flex-1"
|
|
@@ -68,7 +91,7 @@ const renderedHtml = computed(() => {
|
|
|
68
91
|
|
|
69
92
|
<textarea
|
|
70
93
|
v-if="mode === 'edit'"
|
|
71
|
-
v-model="
|
|
94
|
+
v-model="markdown"
|
|
72
95
|
class="w-full min-h-72"
|
|
73
96
|
/>
|
|
74
97
|
|
|
@@ -81,7 +104,7 @@ const renderedHtml = computed(() => {
|
|
|
81
104
|
<button
|
|
82
105
|
type="button"
|
|
83
106
|
class="button w-full justify-center mt-3"
|
|
84
|
-
@click="
|
|
107
|
+
@click="saveMarkdown"
|
|
85
108
|
>
|
|
86
109
|
<span v-if="isSaving">Speichern...</span>
|
|
87
110
|
<span v-else>Speichern</span>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
path: string;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
5
|
+
"update-path": (newPathParts: string[]) => any;
|
|
6
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
7
|
+
"onUpdate-path"?: ((newPathParts: string[]) => any) | undefined;
|
|
8
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
9
|
+
declare const _default: typeof __VLS_export;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
const { path } = defineProps({
|
|
4
|
+
path: { type: String, required: true }
|
|
5
|
+
});
|
|
6
|
+
const parts = computed(() => {
|
|
7
|
+
const splitParts = path.split(":").filter((part) => part.length > 0);
|
|
8
|
+
splitParts.unshift("Hauptordner");
|
|
9
|
+
return splitParts;
|
|
10
|
+
});
|
|
11
|
+
defineEmits(["update-path"]);
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div class="text-gray-500 text-base flex items-center gap-1">
|
|
16
|
+
<div
|
|
17
|
+
v-for="(part, index) in parts"
|
|
18
|
+
:key="index"
|
|
19
|
+
class="flex items-center gap-1"
|
|
20
|
+
>
|
|
21
|
+
<svg
|
|
22
|
+
v-if="index > 0"
|
|
23
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
24
|
+
fill="none"
|
|
25
|
+
viewBox="0 0 24 24"
|
|
26
|
+
stroke-width="1.5"
|
|
27
|
+
stroke="currentColor"
|
|
28
|
+
class="size-6"
|
|
29
|
+
>
|
|
30
|
+
<path
|
|
31
|
+
stroke-linecap="round"
|
|
32
|
+
stroke-linejoin="round"
|
|
33
|
+
d="m8.25 4.5 7.5 7.5-7.5 7.5"
|
|
34
|
+
/>
|
|
35
|
+
</svg>
|
|
36
|
+
<button
|
|
37
|
+
v-if="index < parts.length - 1"
|
|
38
|
+
class="button secondary small"
|
|
39
|
+
@click="$emit('update-path', parts.slice(1, index + 1))"
|
|
40
|
+
>
|
|
41
|
+
{{ part }}
|
|
42
|
+
</button>
|
|
43
|
+
<span
|
|
44
|
+
v-else
|
|
45
|
+
class="font-bold"
|
|
46
|
+
>
|
|
47
|
+
{{ part }}
|
|
48
|
+
</span>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</template>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
path: string;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
5
|
+
"update-path": (newPathParts: string[]) => any;
|
|
6
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
7
|
+
"onUpdate-path"?: ((newPathParts: string[]) => any) | undefined;
|
|
8
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
9
|
+
declare const _default: typeof __VLS_export;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
isOpen: boolean;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
5
|
+
select: (filePath: string) => any;
|
|
6
|
+
close: () => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
8
|
+
onSelect?: ((filePath: string) => any) | undefined;
|
|
9
|
+
onClose?: (() => any) | undefined;
|
|
10
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
11
|
+
declare const _default: typeof __VLS_export;
|
|
12
|
+
export default _default;
|