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.
Files changed (64) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +37 -12
  3. package/dist/runtime/app/components/content/delete.vue +1 -1
  4. package/dist/runtime/app/components/content/editor/csv.vue +79 -53
  5. package/dist/runtime/app/components/content/editor/frontmatter/form.d.vue.ts +10 -0
  6. package/dist/runtime/app/components/content/editor/frontmatter/form.vue +17 -0
  7. package/dist/runtime/app/components/content/editor/frontmatter/form.vue.d.ts +10 -0
  8. package/dist/runtime/app/components/content/editor/frontmatter/input.d.vue.ts +14 -0
  9. package/dist/runtime/app/components/content/editor/frontmatter/input.vue +53 -0
  10. package/dist/runtime/app/components/content/editor/frontmatter/input.vue.d.ts +14 -0
  11. package/dist/runtime/app/components/content/editor/image.vue +1 -1
  12. package/dist/runtime/app/components/content/editor/markdown.vue +30 -7
  13. package/dist/runtime/app/components/content/editor/pdf.vue +1 -1
  14. package/dist/runtime/app/components/content/editor/selectFile/breadcrumb.d.vue.ts +10 -0
  15. package/dist/runtime/app/components/content/editor/selectFile/breadcrumb.vue +51 -0
  16. package/dist/runtime/app/components/content/editor/selectFile/breadcrumb.vue.d.ts +10 -0
  17. package/dist/runtime/app/components/content/editor/selectFile/index.d.vue.ts +12 -0
  18. package/dist/runtime/app/components/content/editor/selectFile/index.vue +89 -0
  19. package/dist/runtime/app/components/content/editor/selectFile/index.vue.d.ts +12 -0
  20. package/dist/runtime/app/components/content/editor/txt.vue +19 -2
  21. package/dist/runtime/app/components/content/fileButtons.vue +22 -3
  22. package/dist/runtime/app/components/content/files.vue +16 -1
  23. package/dist/runtime/app/components/content/index.vue +6 -27
  24. package/dist/runtime/app/components/content/upload.vue +22 -16
  25. package/dist/runtime/app/composables/useAdminUpload.d.ts +0 -1
  26. package/dist/runtime/app/composables/useAdminUpload.js +4 -15
  27. package/dist/runtime/app/pages/admin/edit/[path].vue +3 -0
  28. package/dist/runtime/app/styles/admin.css +1 -1
  29. package/dist/runtime/app/styles/admin.min.css +1 -1
  30. package/dist/runtime/server/api/admin/blob.js +37 -0
  31. package/dist/runtime/server/api/admin/csv.d.ts +5 -0
  32. package/dist/runtime/server/api/admin/csv.js +28 -0
  33. package/dist/runtime/server/api/admin/csv.post.js +28 -0
  34. package/dist/runtime/server/api/admin/delete.d.ts +4 -0
  35. package/dist/runtime/server/api/admin/{content/[path].delete.js → delete.js} +5 -4
  36. package/dist/runtime/server/api/admin/{content/[path].post.d.ts → download.d.ts} +1 -1
  37. package/dist/runtime/server/api/admin/download.js +29 -0
  38. package/dist/runtime/server/api/admin/list.d.ts +5 -0
  39. package/dist/runtime/server/api/admin/list.js +24 -0
  40. package/dist/runtime/server/api/admin/md.d.ts +5 -0
  41. package/dist/runtime/server/api/admin/md.js +22 -0
  42. package/dist/runtime/server/api/admin/md.post.d.ts +4 -0
  43. package/dist/runtime/server/api/admin/md.post.js +31 -0
  44. package/dist/runtime/server/api/admin/{content/list.d.ts → txt.d.ts} +1 -1
  45. package/dist/runtime/server/api/admin/txt.js +21 -0
  46. package/dist/runtime/server/api/admin/txt.post.d.ts +4 -0
  47. package/dist/runtime/server/api/admin/txt.post.js +19 -0
  48. package/dist/runtime/server/api/admin/{content/upload.js → upload.js} +1 -1
  49. package/dist/runtime/server/api/content/[path].js +15 -9
  50. package/dist/runtime/server/api/content/list.d.ts +11 -1
  51. package/dist/runtime/server/api/content/list.js +36 -4
  52. package/dist/runtime/server/utils/parseFrontmatter.d.ts +4 -0
  53. package/dist/runtime/server/utils/parseFrontmatter.js +24 -0
  54. package/package.json +3 -3
  55. package/dist/runtime/app/composables/useSaveContent.d.ts +0 -7
  56. package/dist/runtime/app/composables/useSaveContent.js +0 -31
  57. package/dist/runtime/app/util/csv.d.ts +0 -13
  58. package/dist/runtime/app/util/csv.js +0 -38
  59. package/dist/runtime/server/api/admin/content/[path].js +0 -29
  60. package/dist/runtime/server/api/admin/content/[path].post.js +0 -18
  61. package/dist/runtime/server/api/admin/content/list.js +0 -13
  62. /package/dist/runtime/server/api/admin/{content/[path].d.ts → blob.d.ts} +0 -0
  63. /package/dist/runtime/server/api/admin/{content/[path].delete.d.ts → csv.post.d.ts} +0 -0
  64. /package/dist/runtime/server/api/admin/{content/upload.d.ts → upload.d.ts} +0 -0
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mktcms",
3
3
  "configKey": "mktcms",
4
- "version": "0.1.22",
4
+ "version": "0.1.23",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
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/content/list",
49
- handler: resolver.resolve("./runtime/server/api/admin/content/list")
48
+ route: "/api/admin/list",
49
+ handler: resolver.resolve("./runtime/server/api/admin/list")
50
50
  });
51
51
  addServerHandler({
52
- route: "/api/admin/content/:path",
53
- method: "get",
54
- handler: resolver.resolve("./runtime/server/api/admin/content/[path]")
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/content/:path",
61
+ route: "/api/admin/csv",
58
62
  method: "post",
59
- handler: resolver.resolve("./runtime/server/api/admin/content/[path].post")
63
+ handler: resolver.resolve("./runtime/server/api/admin/csv.post")
60
64
  });
61
65
  addServerHandler({
62
- route: "/api/admin/content/:path",
63
- method: "delete",
64
- handler: resolver.resolve("./runtime/server/api/admin/content/[path].delete")
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/content/upload",
68
- handler: resolver.resolve("./runtime/server/api/admin/content/upload")
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/content/${path}`, {
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 { computed, ref } from "vue";
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
- const { content, saveContent, isSaving, savingSuccessful } = await useSaveContent();
7
- const parsedCsv = parseSemicolonCsv(content.value);
8
- const headers = ref(parsedCsv.headers);
9
- const rows = ref(parsedCsv.rows);
10
- const hasUnsavedChanges = ref(false);
11
- const columnCount = computed(() => headers.value.length);
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
- const serializedCsv = serializeSemicolonCsv({ headers: headers.value, rows: rows.value });
14
- content.value = serializedCsv;
15
- await saveContent();
16
- hasUnsavedChanges.value = false;
17
- }
18
- function getHeaderLabel(colIndex) {
19
- return headers.value[colIndex] || `Spalte ${colIndex + 1}`;
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
- const newRow = Array(columnCount.value).fill("");
23
- rows.value.splice(atIndex, 0, newRow);
24
- hasUnsavedChanges.value = true;
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
- rows.value.splice(rowIndex, 1);
28
- hasUnsavedChanges.value = true;
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 = rows.value.splice(rowIndex, 1)[0];
52
+ if (!table.value || rowIndex <= 0) return;
53
+ const row = table.value.rows.splice(rowIndex, 1)[0];
33
54
  if (!row) return;
34
- rows.value.splice(rowIndex - 1, 0, row);
35
- hasUnsavedChanges.value = true;
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 >= rows.value.length - 1) return;
39
- const row = rows.value.splice(rowIndex, 1)[0];
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
- rows.value.splice(rowIndex + 1, 0, row);
42
- hasUnsavedChanges.value = true;
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 = rows.value[rowIndex][colIndex] || "";
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
- rows.value[rowIndex][colIndex] = editBuffer.value;
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 class="flex items-center gap-2 mb-2.5">
66
- <span
67
- v-if="headers.length === 0"
68
- class="opacity-70 text-sm"
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
- {{ getHeaderLabel(colIndex) }}
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 && !hasUnsavedChanges" />
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
- :aria-label="`CSV-Zelle bearbeiten: ${getHeaderLabel(editingCell.colIndex)}`"
276
+ aria-label="Zelle bearbeiten"
248
277
  >
249
278
  <div class="flex flex-col gap-0.5">
250
279
  <div class="font-bold">
251
- Zelle bearbeiten
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;
@@ -19,7 +19,7 @@ const { path } = usePathParam();
19
19
  </button>
20
20
 
21
21
  <img
22
- :src="`/api/admin/content/${path}`"
22
+ :src="`/api/admin/blob?path=${path}`"
23
23
  alt="Image Preview"
24
24
  class="w-full h-auto border border-gray-200 rounded-sm p-1"
25
25
  >
@@ -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
- const { content, saveContent, isSaving, savingSuccessful } = await useSaveContent();
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -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
- <div class="flex gap-2 mb-2">
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="content"
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="saveContent"
107
+ @click="saveMarkdown"
85
108
  >
86
109
  <span v-if="isSaving">Speichern...</span>
87
110
  <span v-else>Speichern</span>
@@ -18,7 +18,7 @@ const { path } = usePathParam();
18
18
  PDF austauschen
19
19
  </button>
20
20
  <embed
21
- :src="`/api/admin/content/${path}`"
21
+ :src="`/api/admin/blob?path=${path}`"
22
22
  type="application/pdf"
23
23
  class="w-full h-auto aspect-[5.5/8] border border-gray-200 rounded-sm"
24
24
  >
@@ -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;