mktcms 0.1.21 → 0.1.22
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 +11 -6
- package/dist/runtime/app/components/admin.vue +1 -1
- package/dist/runtime/app/components/content/breadcrumb.d.vue.ts +1 -4
- package/dist/runtime/app/components/content/breadcrumb.vue +25 -9
- package/dist/runtime/app/components/content/breadcrumb.vue.d.ts +1 -4
- package/dist/runtime/app/components/content/delete.vue +69 -0
- package/dist/runtime/app/components/content/dirs.d.vue.ts +0 -1
- package/dist/runtime/app/components/content/dirs.vue +19 -3
- package/dist/runtime/app/components/content/dirs.vue.d.ts +0 -1
- package/dist/runtime/app/components/content/editor/csv.vue +284 -0
- package/dist/runtime/app/components/content/editor/image.vue +27 -0
- package/dist/runtime/app/components/content/editor/{text/markdown.vue → markdown.vue} +30 -8
- package/dist/runtime/app/components/content/editor/pdf.d.vue.ts +3 -0
- package/dist/runtime/app/components/content/editor/pdf.vue +26 -0
- package/dist/runtime/app/components/content/editor/pdf.vue.d.ts +3 -0
- package/dist/runtime/app/components/content/editor/txt.d.vue.ts +3 -0
- package/dist/runtime/app/components/content/editor/txt.vue +24 -0
- package/dist/runtime/app/components/content/editor/txt.vue.d.ts +3 -0
- package/dist/runtime/app/components/content/fileButtons.d.vue.ts +6 -0
- package/dist/runtime/app/components/content/fileButtons.vue +73 -0
- package/dist/runtime/app/components/content/fileButtons.vue.d.ts +6 -0
- package/dist/runtime/app/components/content/fileIcon.d.vue.ts +6 -0
- package/dist/runtime/app/components/content/fileIcon.vue +80 -0
- package/dist/runtime/app/components/content/fileIcon.vue.d.ts +6 -0
- package/dist/runtime/app/components/content/files.d.vue.ts +0 -1
- package/dist/runtime/app/components/content/files.vue +10 -91
- package/dist/runtime/app/components/content/files.vue.d.ts +0 -1
- package/dist/runtime/app/components/content/index.vue +3 -8
- package/dist/runtime/app/components/content/saved.d.vue.ts +3 -0
- package/dist/runtime/app/components/content/saved.vue +19 -0
- package/dist/runtime/app/components/content/saved.vue.d.ts +3 -0
- package/dist/runtime/app/components/content/upload.vue +18 -12
- package/dist/runtime/app/components/header.vue +55 -25
- package/dist/runtime/app/composables/useFileType.d.ts +7 -0
- package/dist/runtime/app/composables/useFileType.js +14 -0
- package/dist/runtime/app/composables/usePathParam.d.ts +9 -0
- package/dist/runtime/app/composables/usePathParam.js +16 -0
- package/dist/runtime/app/composables/useSaveContent.d.ts +7 -0
- package/dist/runtime/app/composables/useSaveContent.js +31 -0
- package/dist/runtime/app/pages/admin/delete/[path].d.vue.ts +3 -0
- package/dist/runtime/app/pages/admin/delete/[path].vue +14 -0
- package/dist/runtime/app/pages/admin/delete/[path].vue.d.ts +3 -0
- package/dist/runtime/app/pages/admin/edit/[path].d.vue.ts +3 -0
- package/dist/runtime/app/pages/admin/edit/[path].vue +24 -0
- package/dist/runtime/app/pages/admin/edit/[path].vue.d.ts +3 -0
- package/dist/runtime/app/pages/admin/index.vue +2 -0
- package/dist/runtime/app/pages/admin/login.vue +68 -21
- package/dist/runtime/app/styles/admin.css +1 -0
- package/dist/runtime/app/styles/admin.min.css +1 -0
- package/dist/runtime/app/util/csv.d.ts +13 -0
- package/dist/runtime/app/util/csv.js +38 -0
- package/dist/runtime/server/api/admin/content/[path].delete.d.ts +4 -0
- package/dist/runtime/server/api/admin/content/[path].delete.js +14 -0
- package/dist/runtime/server/api/admin/content/[path].js +0 -5
- package/dist/runtime/server/api/admin/content/list.js +0 -7
- package/dist/runtime/server/api/content/[path].js +0 -5
- package/dist/runtime/server/api/content/list.js +0 -7
- package/dist/runtime/server/plugins/storage.js +0 -3
- package/package.json +10 -2
- package/dist/runtime/app/components/content/editor/blob/image.d.vue.ts +0 -10
- package/dist/runtime/app/components/content/editor/blob/image.vue +0 -12
- package/dist/runtime/app/components/content/editor/blob/image.vue.d.ts +0 -10
- package/dist/runtime/app/components/content/editor/blob/index.vue +0 -41
- package/dist/runtime/app/components/content/editor/text/csv.d.vue.ts +0 -10
- package/dist/runtime/app/components/content/editor/text/csv.vue +0 -235
- package/dist/runtime/app/components/content/editor/text/csv.vue.d.ts +0 -10
- package/dist/runtime/app/components/content/editor/text/index.vue +0 -58
- package/dist/runtime/app/components/content/editor/text/markdown.d.vue.ts +0 -10
- package/dist/runtime/app/components/content/editor/text/markdown.vue.d.ts +0 -10
- package/dist/runtime/app/pages/admin/edit/blob/[path].vue +0 -12
- package/dist/runtime/app/pages/admin/edit/text/[path].vue +0 -12
- /package/dist/runtime/app/components/content/{editor/blob/index.d.vue.ts → delete.d.vue.ts} +0 -0
- /package/dist/runtime/app/components/content/{editor/blob/index.vue.d.ts → delete.vue.d.ts} +0 -0
- /package/dist/runtime/app/components/content/editor/{text/index.d.vue.ts → csv.d.vue.ts} +0 -0
- /package/dist/runtime/app/components/content/editor/{text/index.vue.d.ts → csv.vue.d.ts} +0 -0
- /package/dist/runtime/app/{pages/admin/edit/blob/[path].d.vue.ts → components/content/editor/image.d.vue.ts} +0 -0
- /package/dist/runtime/app/{pages/admin/edit/blob/[path].vue.d.ts → components/content/editor/image.vue.d.ts} +0 -0
- /package/dist/runtime/app/{pages/admin/edit/text/[path].d.vue.ts → components/content/editor/markdown.d.vue.ts} +0 -0
- /package/dist/runtime/app/{pages/admin/edit/text/[path].vue.d.ts → components/content/editor/markdown.vue.d.ts} +0 -0
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -58,6 +58,11 @@ const module$1 = defineNuxtModule({
|
|
|
58
58
|
method: "post",
|
|
59
59
|
handler: resolver.resolve("./runtime/server/api/admin/content/[path].post")
|
|
60
60
|
});
|
|
61
|
+
addServerHandler({
|
|
62
|
+
route: "/api/admin/content/:path",
|
|
63
|
+
method: "delete",
|
|
64
|
+
handler: resolver.resolve("./runtime/server/api/admin/content/[path].delete")
|
|
65
|
+
});
|
|
61
66
|
addServerHandler({
|
|
62
67
|
route: "/api/admin/content/upload",
|
|
63
68
|
handler: resolver.resolve("./runtime/server/api/admin/content/upload")
|
|
@@ -77,14 +82,14 @@ const module$1 = defineNuxtModule({
|
|
|
77
82
|
file: resolver.resolve("./runtime/app/pages/admin/index.vue")
|
|
78
83
|
});
|
|
79
84
|
pages.push({
|
|
80
|
-
name: "Admin
|
|
81
|
-
path: "/admin/edit
|
|
82
|
-
file: resolver.resolve("./runtime/app/pages/admin/edit/
|
|
85
|
+
name: "Admin Editor",
|
|
86
|
+
path: "/admin/edit/:path",
|
|
87
|
+
file: resolver.resolve("./runtime/app/pages/admin/edit/[path].vue")
|
|
83
88
|
});
|
|
84
89
|
pages.push({
|
|
85
|
-
name: "Admin
|
|
86
|
-
path: "/admin/
|
|
87
|
-
file: resolver.resolve("./runtime/app/pages/admin/
|
|
90
|
+
name: "Admin Delete",
|
|
91
|
+
path: "/admin/delete/:path",
|
|
92
|
+
file: resolver.resolve("./runtime/app/pages/admin/delete/[path].vue")
|
|
88
93
|
});
|
|
89
94
|
pages.push({
|
|
90
95
|
name: "Admin New Content",
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
</template>
|
|
6
6
|
|
|
7
7
|
<style>
|
|
8
|
-
|
|
8
|
+
@import "../styles/admin.min.css";
|
|
9
9
|
</style>
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
parts: string[];
|
|
3
|
-
};
|
|
4
|
-
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
5
2
|
declare const _default: typeof __VLS_export;
|
|
6
3
|
export default _default;
|
|
@@ -1,20 +1,36 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { useRoute } from "#app";
|
|
3
|
+
const path = useRoute().params.path || "";
|
|
4
|
+
const parts = path.split(":").filter((part) => part.length > 0);
|
|
5
|
+
parts.unshift("Hauptordner");
|
|
5
6
|
</script>
|
|
6
7
|
|
|
7
8
|
<template>
|
|
8
|
-
<div class="
|
|
9
|
-
<
|
|
10
|
-
<span
|
|
9
|
+
<div class="text-gray-500 text-base mt-12 mb-6 flex items-center gap-1">
|
|
10
|
+
<div
|
|
11
11
|
v-for="(part, index) in parts"
|
|
12
12
|
:key="index"
|
|
13
|
+
class="flex items-center gap-1"
|
|
13
14
|
>
|
|
14
|
-
|
|
15
|
+
<svg
|
|
16
|
+
v-if="index > 0"
|
|
17
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
18
|
+
fill="none"
|
|
19
|
+
viewBox="0 0 24 24"
|
|
20
|
+
stroke-width="1.5"
|
|
21
|
+
stroke="currentColor"
|
|
22
|
+
class="size-6"
|
|
23
|
+
>
|
|
24
|
+
<path
|
|
25
|
+
stroke-linecap="round"
|
|
26
|
+
stroke-linejoin="round"
|
|
27
|
+
d="m8.25 4.5 7.5 7.5-7.5 7.5"
|
|
28
|
+
/>
|
|
29
|
+
</svg>
|
|
15
30
|
<NuxtLink
|
|
16
31
|
v-if="index < parts.length - 1"
|
|
17
|
-
:to="`/admin/${parts.slice(
|
|
32
|
+
:to="`/admin/${index > 0 ? parts.slice(1, index + 1).join(':') : ''}`"
|
|
33
|
+
class="button secondary small"
|
|
18
34
|
>
|
|
19
35
|
{{ part }}
|
|
20
36
|
</NuxtLink>
|
|
@@ -24,6 +40,6 @@ defineProps({
|
|
|
24
40
|
>
|
|
25
41
|
{{ part }}
|
|
26
42
|
</span>
|
|
27
|
-
</
|
|
43
|
+
</div>
|
|
28
44
|
</div>
|
|
29
45
|
</template>
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
parts: string[];
|
|
3
|
-
};
|
|
4
|
-
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
5
2
|
declare const _default: typeof __VLS_export;
|
|
6
3
|
export default _default;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { navigateTo } from "#app";
|
|
3
|
+
import usePathParam from "../../composables/usePathParam";
|
|
4
|
+
const { path, pathParts } = usePathParam();
|
|
5
|
+
const parentPath = pathParts.slice(0, -1).join(":");
|
|
6
|
+
async function deleteContent() {
|
|
7
|
+
await fetch(`/api/admin/content/${path}`, {
|
|
8
|
+
method: "DELETE"
|
|
9
|
+
});
|
|
10
|
+
await navigateTo(`/admin/${parentPath}`);
|
|
11
|
+
}
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div>
|
|
16
|
+
<h1 class="text-2xl font-bold mb-4">
|
|
17
|
+
Datei löschen
|
|
18
|
+
</h1>
|
|
19
|
+
<p class="mb-4">
|
|
20
|
+
Sind Sie sicher, dass Sie
|
|
21
|
+
<strong>{{ pathParts[pathParts.length - 1] }}</strong>
|
|
22
|
+
aus
|
|
23
|
+
<strong>{{ parentPath || "Hauptordner" }}</strong>
|
|
24
|
+
löschen möchten? Die Datei kann nicht wiederhergestellt werden!
|
|
25
|
+
</p>
|
|
26
|
+
<div class="flex gap-2">
|
|
27
|
+
<NuxtLink
|
|
28
|
+
:to="`/admin/${parentPath}`"
|
|
29
|
+
class="button secondary flex-1"
|
|
30
|
+
>
|
|
31
|
+
<svg
|
|
32
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
33
|
+
fill="none"
|
|
34
|
+
viewBox="0 0 24 24"
|
|
35
|
+
stroke-width="1.5"
|
|
36
|
+
stroke="currentColor"
|
|
37
|
+
class="size-5"
|
|
38
|
+
>
|
|
39
|
+
<path
|
|
40
|
+
stroke-linecap="round"
|
|
41
|
+
stroke-linejoin="round"
|
|
42
|
+
d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18"
|
|
43
|
+
/>
|
|
44
|
+
</svg>
|
|
45
|
+
Abbrechen
|
|
46
|
+
</NuxtLink>
|
|
47
|
+
<button
|
|
48
|
+
class="button danger"
|
|
49
|
+
@click="deleteContent"
|
|
50
|
+
>
|
|
51
|
+
<svg
|
|
52
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
53
|
+
fill="none"
|
|
54
|
+
viewBox="0 0 24 24"
|
|
55
|
+
stroke-width="1.5"
|
|
56
|
+
stroke="currentColor"
|
|
57
|
+
class="size-5"
|
|
58
|
+
>
|
|
59
|
+
<path
|
|
60
|
+
stroke-linecap="round"
|
|
61
|
+
stroke-linejoin="round"
|
|
62
|
+
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
|
|
63
|
+
/>
|
|
64
|
+
</svg>
|
|
65
|
+
Datei löschen
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</template>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
type __VLS_Props = {
|
|
2
|
-
path: string;
|
|
3
2
|
dirs: string[];
|
|
4
3
|
};
|
|
5
4
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import usePathParam from "../../composables/usePathParam";
|
|
2
3
|
defineProps({
|
|
3
|
-
path: { type: String, required: true },
|
|
4
4
|
dirs: { type: Array, required: true }
|
|
5
5
|
});
|
|
6
|
+
const { path } = usePathParam();
|
|
6
7
|
</script>
|
|
7
8
|
|
|
8
9
|
<template>
|
|
9
|
-
<div class="
|
|
10
|
+
<div class="flex flex-col gap-2">
|
|
10
11
|
<NuxtLink
|
|
11
12
|
v-for="dir in dirs"
|
|
12
13
|
:key="dir"
|
|
13
14
|
:to="`/admin/${path ? path + ':' : ''}${dir}`"
|
|
15
|
+
class="button secondary"
|
|
14
16
|
>
|
|
17
|
+
<svg
|
|
18
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
19
|
+
fill="none"
|
|
20
|
+
viewBox="0 0 24 24"
|
|
21
|
+
stroke-width="1.5"
|
|
22
|
+
stroke="currentColor"
|
|
23
|
+
class="size-6 opacity-20"
|
|
24
|
+
>
|
|
25
|
+
<path
|
|
26
|
+
stroke-linecap="round"
|
|
27
|
+
stroke-linejoin="round"
|
|
28
|
+
d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 0 0-1.883 2.542l.857 6a2.25 2.25 0 0 0 2.227 1.932H19.05a2.25 2.25 0 0 0 2.227-1.932l.857-6a2.25 2.25 0 0 0-1.883-2.542m-16.5 0V6A2.25 2.25 0 0 1 6 3.75h3.879a1.5 1.5 0 0 1 1.06.44l2.122 2.12a1.5 1.5 0 0 0 1.06.44H18A2.25 2.25 0 0 1 20.25 9v.776"
|
|
29
|
+
/>
|
|
30
|
+
</svg>
|
|
15
31
|
<span>
|
|
16
32
|
{{ dir }}
|
|
17
33
|
</span>
|
|
18
|
-
<span>
|
|
34
|
+
<span class="ml-auto">
|
|
19
35
|
<svg
|
|
20
36
|
xmlns="http://www.w3.org/2000/svg"
|
|
21
37
|
fill="none"
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
type __VLS_Props = {
|
|
2
|
-
path: string;
|
|
3
2
|
dirs: string[];
|
|
4
3
|
};
|
|
5
4
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref } from "vue";
|
|
3
|
+
import useSaveContent from "../../../composables/useSaveContent";
|
|
4
|
+
import { parseSemicolonCsv, serializeSemicolonCsv } from "../../../util/csv";
|
|
5
|
+
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);
|
|
12
|
+
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}`;
|
|
20
|
+
}
|
|
21
|
+
function insertRow(atIndex) {
|
|
22
|
+
const newRow = Array(columnCount.value).fill("");
|
|
23
|
+
rows.value.splice(atIndex, 0, newRow);
|
|
24
|
+
hasUnsavedChanges.value = true;
|
|
25
|
+
}
|
|
26
|
+
function removeRow(rowIndex) {
|
|
27
|
+
rows.value.splice(rowIndex, 1);
|
|
28
|
+
hasUnsavedChanges.value = true;
|
|
29
|
+
}
|
|
30
|
+
function moveRowUp(rowIndex) {
|
|
31
|
+
if (rowIndex <= 0) return;
|
|
32
|
+
const row = rows.value.splice(rowIndex, 1)[0];
|
|
33
|
+
if (!row) return;
|
|
34
|
+
rows.value.splice(rowIndex - 1, 0, row);
|
|
35
|
+
hasUnsavedChanges.value = true;
|
|
36
|
+
}
|
|
37
|
+
function moveRowDown(rowIndex) {
|
|
38
|
+
if (rowIndex >= rows.value.length - 1) return;
|
|
39
|
+
const row = rows.value.splice(rowIndex, 1)[0];
|
|
40
|
+
if (!row) return;
|
|
41
|
+
rows.value.splice(rowIndex + 1, 0, row);
|
|
42
|
+
hasUnsavedChanges.value = true;
|
|
43
|
+
}
|
|
44
|
+
const editingCell = ref(null);
|
|
45
|
+
const editBuffer = ref("");
|
|
46
|
+
function startEdit(rowIndex, colIndex) {
|
|
47
|
+
editingCell.value = { rowIndex, colIndex };
|
|
48
|
+
editBuffer.value = rows.value[rowIndex][colIndex] || "";
|
|
49
|
+
}
|
|
50
|
+
function saveEdit() {
|
|
51
|
+
if (!editingCell.value) return;
|
|
52
|
+
const { rowIndex, colIndex } = editingCell.value;
|
|
53
|
+
rows.value[rowIndex][colIndex] = editBuffer.value;
|
|
54
|
+
hasUnsavedChanges.value = true;
|
|
55
|
+
cancelEdit();
|
|
56
|
+
}
|
|
57
|
+
function cancelEdit() {
|
|
58
|
+
editingCell.value = null;
|
|
59
|
+
editBuffer.value = "";
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<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">
|
|
75
|
+
<div class="flex items-center h-0 justify-center border border-gray-200 rounded-sm mb-6">
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
class="button small soft"
|
|
79
|
+
:disabled="headers.length === 0"
|
|
80
|
+
@click="insertRow(0)"
|
|
81
|
+
>
|
|
82
|
+
<svg
|
|
83
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
84
|
+
fill="none"
|
|
85
|
+
viewBox="0 0 24 24"
|
|
86
|
+
stroke-width="1.5"
|
|
87
|
+
stroke="currentColor"
|
|
88
|
+
class="size-4"
|
|
89
|
+
>
|
|
90
|
+
<path
|
|
91
|
+
stroke-linecap="round"
|
|
92
|
+
stroke-linejoin="round"
|
|
93
|
+
d="M12 4.5v15m7.5-7.5h-15"
|
|
94
|
+
/>
|
|
95
|
+
</svg>
|
|
96
|
+
</button>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div>
|
|
100
|
+
<template
|
|
101
|
+
v-for="(r, rowIndex) in rows"
|
|
102
|
+
:key="rowIndex"
|
|
103
|
+
>
|
|
104
|
+
<div
|
|
105
|
+
class="flex items-stretch rounded-sm border border-gray-200"
|
|
106
|
+
>
|
|
107
|
+
<div class="flex-1 min-w-0 overflow-x-auto overflow-y-hidden">
|
|
108
|
+
<div class="grid grid-flow-col auto-cols-[minmax(160px,1fr)] max-[640px]:auto-cols-[minmax(220px,1fr)]">
|
|
109
|
+
<div
|
|
110
|
+
v-for="(cell, colIndex) in r"
|
|
111
|
+
:key="colIndex"
|
|
112
|
+
class="p-1.5 border-r border-gray-200 box-border last:border-r-0"
|
|
113
|
+
>
|
|
114
|
+
<div class="text-xs mb-1.5 leading-tight wrap-break-word flex items-center justify-between gap-1">
|
|
115
|
+
{{ getHeaderLabel(colIndex) }}
|
|
116
|
+
</div>
|
|
117
|
+
<div
|
|
118
|
+
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"
|
|
119
|
+
:title="cell"
|
|
120
|
+
@click="startEdit(rowIndex, colIndex)"
|
|
121
|
+
>
|
|
122
|
+
{{ cell || "\u2014" }}
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div class="flex-none flex flex-col items-start gap-1 p-1.5 border-l border-gray-200 bg-white max-[640px]:px-1 max-[640px]:gap-0.5">
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
class="button small"
|
|
132
|
+
:disabled="rowIndex === 0"
|
|
133
|
+
aria-label="Zeile nach oben"
|
|
134
|
+
title="Nach oben"
|
|
135
|
+
@click="moveRowUp(rowIndex)"
|
|
136
|
+
>
|
|
137
|
+
<svg
|
|
138
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
139
|
+
fill="none"
|
|
140
|
+
viewBox="0 0 24 24"
|
|
141
|
+
stroke-width="1.5"
|
|
142
|
+
stroke="currentColor"
|
|
143
|
+
class="size-4"
|
|
144
|
+
>
|
|
145
|
+
<path
|
|
146
|
+
stroke-linecap="round"
|
|
147
|
+
stroke-linejoin="round"
|
|
148
|
+
d="M4.5 10.5 12 3m0 0 7.5 7.5M12 3v18"
|
|
149
|
+
/>
|
|
150
|
+
</svg>
|
|
151
|
+
</button>
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
class="button small"
|
|
155
|
+
:disabled="rowIndex === rows.length - 1"
|
|
156
|
+
aria-label="Zeile nach unten"
|
|
157
|
+
title="Nach unten"
|
|
158
|
+
@click="moveRowDown(rowIndex)"
|
|
159
|
+
>
|
|
160
|
+
<svg
|
|
161
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
162
|
+
fill="none"
|
|
163
|
+
viewBox="0 0 24 24"
|
|
164
|
+
stroke-width="1.5"
|
|
165
|
+
stroke="currentColor"
|
|
166
|
+
class="size-4"
|
|
167
|
+
>
|
|
168
|
+
<path
|
|
169
|
+
stroke-linecap="round"
|
|
170
|
+
stroke-linejoin="round"
|
|
171
|
+
d="M19.5 13.5 12 21m0 0-7.5-7.5M12 21V3"
|
|
172
|
+
/>
|
|
173
|
+
</svg>
|
|
174
|
+
</button>
|
|
175
|
+
<button
|
|
176
|
+
type="button"
|
|
177
|
+
class="button small"
|
|
178
|
+
aria-label="Zeile löschen"
|
|
179
|
+
title="Löschen"
|
|
180
|
+
@click="removeRow(rowIndex)"
|
|
181
|
+
>
|
|
182
|
+
<svg
|
|
183
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
184
|
+
fill="none"
|
|
185
|
+
viewBox="0 0 24 24"
|
|
186
|
+
stroke-width="1.5"
|
|
187
|
+
stroke="currentColor"
|
|
188
|
+
class="size-4"
|
|
189
|
+
>
|
|
190
|
+
<path
|
|
191
|
+
stroke-linecap="round"
|
|
192
|
+
stroke-linejoin="round"
|
|
193
|
+
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
|
|
194
|
+
/>
|
|
195
|
+
</svg>
|
|
196
|
+
</button>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<div class="h-0 flex items-center justify-center border border-gray-200 rounded-sm my-6">
|
|
201
|
+
<button
|
|
202
|
+
type="button"
|
|
203
|
+
class="button small"
|
|
204
|
+
:disabled="headers.length === 0"
|
|
205
|
+
@click="insertRow(rowIndex + 1)"
|
|
206
|
+
>
|
|
207
|
+
<svg
|
|
208
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
209
|
+
fill="none"
|
|
210
|
+
viewBox="0 0 24 24"
|
|
211
|
+
stroke-width="1.5"
|
|
212
|
+
stroke="currentColor"
|
|
213
|
+
class="size-4"
|
|
214
|
+
>
|
|
215
|
+
<path
|
|
216
|
+
stroke-linecap="round"
|
|
217
|
+
stroke-linejoin="round"
|
|
218
|
+
d="M12 4.5v15m7.5-7.5h-15"
|
|
219
|
+
/>
|
|
220
|
+
</svg>
|
|
221
|
+
</button>
|
|
222
|
+
</div>
|
|
223
|
+
</template>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<button
|
|
228
|
+
type="button"
|
|
229
|
+
class="button w-full mt-3 justify-center"
|
|
230
|
+
@click="saveCsv"
|
|
231
|
+
>
|
|
232
|
+
<span v-if="isSaving">Speichern...</span>
|
|
233
|
+
<span v-else>Speichern</span>
|
|
234
|
+
</button>
|
|
235
|
+
<Saved v-if="savingSuccessful && !hasUnsavedChanges" />
|
|
236
|
+
|
|
237
|
+
<div
|
|
238
|
+
v-if="editingCell"
|
|
239
|
+
class="fixed inset-0 bg-black/45 flex items-center justify-center p-4 z-9999"
|
|
240
|
+
role="presentation"
|
|
241
|
+
@click.self="cancelEdit()"
|
|
242
|
+
>
|
|
243
|
+
<div
|
|
244
|
+
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
|
+
role="dialog"
|
|
246
|
+
aria-modal="true"
|
|
247
|
+
:aria-label="`CSV-Zelle bearbeiten: ${getHeaderLabel(editingCell.colIndex)}`"
|
|
248
|
+
>
|
|
249
|
+
<div class="flex flex-col gap-0.5">
|
|
250
|
+
<div class="font-bold">
|
|
251
|
+
Zelle bearbeiten
|
|
252
|
+
</div>
|
|
253
|
+
<div class="opacity-75 text-[13px]">
|
|
254
|
+
{{ getHeaderLabel(editingCell.colIndex) }} · Zeile {{ editingCell.rowIndex + 1 }}
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<textarea
|
|
259
|
+
id="csv-edit-textarea"
|
|
260
|
+
v-model="editBuffer"
|
|
261
|
+
class="w-full box-border p-2.5 border border-gray-300 rounded-lg resize-y min-h-40 font-[inherit] focus:outline-none focus:ring-2 focus:ring-emerald-500/30"
|
|
262
|
+
rows="8"
|
|
263
|
+
/>
|
|
264
|
+
|
|
265
|
+
<div class="flex justify-end gap-2">
|
|
266
|
+
<button
|
|
267
|
+
type="button"
|
|
268
|
+
class="button"
|
|
269
|
+
@click="saveEdit()"
|
|
270
|
+
>
|
|
271
|
+
schließen
|
|
272
|
+
</button>
|
|
273
|
+
<button
|
|
274
|
+
type="button"
|
|
275
|
+
class="button secondary"
|
|
276
|
+
@click="cancelEdit()"
|
|
277
|
+
>
|
|
278
|
+
zurücksetzen
|
|
279
|
+
</button>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
</template>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import usePathParam from "../../../composables/usePathParam";
|
|
3
|
+
const { path } = usePathParam();
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div>
|
|
8
|
+
<button
|
|
9
|
+
class="button secondary w-full justify-center mb-2"
|
|
10
|
+
type="button"
|
|
11
|
+
>
|
|
12
|
+
Link kopieren
|
|
13
|
+
</button>
|
|
14
|
+
<button
|
|
15
|
+
class="button w-full justify-center mb-4"
|
|
16
|
+
type="button"
|
|
17
|
+
>
|
|
18
|
+
Bild austauschen
|
|
19
|
+
</button>
|
|
20
|
+
|
|
21
|
+
<img
|
|
22
|
+
:src="`/api/admin/content/${path}`"
|
|
23
|
+
alt="Image Preview"
|
|
24
|
+
class="w-full h-auto border border-gray-200 rounded-sm p-1"
|
|
25
|
+
>
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref } from "vue";
|
|
3
3
|
import { marked } from "marked";
|
|
4
|
-
|
|
4
|
+
import useSaveContent from "../../../composables/useSaveContent";
|
|
5
|
+
import Saved from "../saved.vue";
|
|
6
|
+
const { content, saveContent, isSaving, savingSuccessful } = await useSaveContent();
|
|
5
7
|
const mode = ref("edit");
|
|
6
8
|
function escapeHtml(value) {
|
|
7
9
|
return (value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -25,7 +27,7 @@ const renderedHtml = computed(() => {
|
|
|
25
27
|
const text = this.parser.parseInline(token.tokens);
|
|
26
28
|
if (!safe) return text;
|
|
27
29
|
const t = token.title ? ` title="${escapeHtml(token.title)}"` : "";
|
|
28
|
-
const target =
|
|
30
|
+
const target = ' target="_blank" rel="noopener noreferrer"';
|
|
29
31
|
return `<a href="${escapeHtml(safe)}"${t}${target}>${text}</a>`;
|
|
30
32
|
};
|
|
31
33
|
renderer.image = function(token) {
|
|
@@ -44,26 +46,46 @@ const renderedHtml = computed(() => {
|
|
|
44
46
|
</script>
|
|
45
47
|
|
|
46
48
|
<template>
|
|
47
|
-
<div
|
|
48
|
-
<div
|
|
49
|
+
<div>
|
|
50
|
+
<div class="flex gap-2 mb-2">
|
|
49
51
|
<button
|
|
50
52
|
type="button"
|
|
51
|
-
|
|
53
|
+
class="button secondary flex-1"
|
|
54
|
+
:disabled="mode === 'edit'"
|
|
55
|
+
@click="mode = 'edit'"
|
|
52
56
|
>
|
|
53
|
-
|
|
57
|
+
Bearbeiten
|
|
58
|
+
</button>
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
class="button secondary flex-1"
|
|
62
|
+
:disabled="mode === 'preview'"
|
|
63
|
+
@click="mode = 'preview'"
|
|
64
|
+
>
|
|
65
|
+
Vorschau
|
|
54
66
|
</button>
|
|
55
67
|
</div>
|
|
56
68
|
|
|
57
69
|
<textarea
|
|
58
70
|
v-if="mode === 'edit'"
|
|
59
71
|
v-model="content"
|
|
60
|
-
|
|
72
|
+
class="w-full min-h-72"
|
|
61
73
|
/>
|
|
62
74
|
|
|
63
75
|
<div
|
|
64
76
|
v-else
|
|
65
|
-
|
|
77
|
+
class="prose max-w-full min-h-72 border border-gray-200 rounded-sm p-4"
|
|
66
78
|
v-html="renderedHtml"
|
|
67
79
|
/>
|
|
80
|
+
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
class="button w-full justify-center mt-3"
|
|
84
|
+
@click="saveContent"
|
|
85
|
+
>
|
|
86
|
+
<span v-if="isSaving">Speichern...</span>
|
|
87
|
+
<span v-else>Speichern</span>
|
|
88
|
+
</button>
|
|
89
|
+
<Saved v-if="savingSuccessful" />
|
|
68
90
|
</div>
|
|
69
91
|
</template>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import usePathParam from "../../../composables/usePathParam";
|
|
3
|
+
const { path } = usePathParam();
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div>
|
|
8
|
+
<button
|
|
9
|
+
class="button secondary w-full justify-center mb-2"
|
|
10
|
+
type="button"
|
|
11
|
+
>
|
|
12
|
+
Link kopieren
|
|
13
|
+
</button>
|
|
14
|
+
<button
|
|
15
|
+
class="button w-full justify-center mb-4"
|
|
16
|
+
type="button"
|
|
17
|
+
>
|
|
18
|
+
PDF austauschen
|
|
19
|
+
</button>
|
|
20
|
+
<embed
|
|
21
|
+
:src="`/api/admin/content/${path}`"
|
|
22
|
+
type="application/pdf"
|
|
23
|
+
class="w-full h-auto aspect-[5.5/8] border border-gray-200 rounded-sm"
|
|
24
|
+
>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|