mktcms 0.1.17 → 0.1.19
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/README.md +17 -14
- package/dist/module.json +1 -1
- package/dist/module.mjs +11 -3
- package/dist/runtime/app/components/admin.vue +1 -1
- package/dist/runtime/app/components/content/breadcrumb.d.vue.ts +6 -0
- package/dist/runtime/app/components/content/breadcrumb.vue +29 -0
- package/dist/runtime/app/components/content/breadcrumb.vue.d.ts +6 -0
- package/dist/runtime/app/components/content/dirs.d.vue.ts +7 -0
- package/dist/runtime/app/components/content/dirs.vue +36 -0
- package/dist/runtime/app/components/content/dirs.vue.d.ts +7 -0
- package/dist/runtime/app/components/content/editor/blob/image.d.vue.ts +10 -0
- package/dist/runtime/app/components/content/editor/blob/image.vue +12 -0
- package/dist/runtime/app/components/content/editor/blob/image.vue.d.ts +10 -0
- package/dist/runtime/app/components/content/editor/blob/index.vue +41 -0
- package/dist/runtime/app/components/content/editor/{index.vue → text/index.vue} +23 -13
- package/dist/runtime/app/components/content/files.d.vue.ts +7 -0
- package/dist/runtime/app/components/content/files.vue +109 -0
- package/dist/runtime/app/components/content/files.vue.d.ts +7 -0
- package/dist/runtime/app/components/content/index.vue +12 -50
- package/dist/runtime/app/components/content/upload.vue +90 -9
- package/dist/runtime/app/components/header.vue +38 -11
- package/dist/runtime/app/composables/useAdminUpload.d.ts +2 -0
- package/dist/runtime/app/composables/useAdminUpload.js +4 -0
- package/dist/runtime/app/pages/admin/edit/blob/[path].d.vue.ts +3 -0
- package/dist/runtime/app/pages/admin/edit/blob/[path].vue +12 -0
- package/dist/runtime/app/pages/admin/edit/blob/[path].vue.d.ts +3 -0
- package/dist/runtime/app/pages/admin/edit/text/[path].d.vue.ts +3 -0
- package/dist/runtime/app/pages/admin/edit/text/[path].vue +12 -0
- package/dist/runtime/app/pages/admin/edit/text/[path].vue.d.ts +3 -0
- package/dist/runtime/server/api/admin/content/[path].d.ts +1 -1
- package/dist/runtime/server/api/admin/content/[path].js +8 -2
- package/dist/runtime/server/api/content/[path].js +6 -2
- package/package.json +1 -1
- package/dist/runtime/app/pages/admin/edit/[path].vue +0 -12
- /package/dist/runtime/app/components/content/editor/{index.d.vue.ts → blob/index.d.vue.ts} +0 -0
- /package/dist/runtime/app/components/content/editor/{index.vue.d.ts → blob/index.vue.d.ts} +0 -0
- /package/dist/runtime/app/components/content/editor/{csv.d.vue.ts → text/csv.d.vue.ts} +0 -0
- /package/dist/runtime/app/components/content/editor/{csv.vue → text/csv.vue} +0 -0
- /package/dist/runtime/app/components/content/editor/{csv.vue.d.ts → text/csv.vue.d.ts} +0 -0
- /package/dist/runtime/app/{pages/admin/edit/[path].d.vue.ts → components/content/editor/text/index.d.vue.ts} +0 -0
- /package/dist/runtime/app/{pages/admin/edit/[path].vue.d.ts → components/content/editor/text/index.vue.d.ts} +0 -0
- /package/dist/runtime/app/components/content/editor/{markdown.d.vue.ts → text/markdown.d.vue.ts} +0 -0
- /package/dist/runtime/app/components/content/editor/{markdown.vue → text/markdown.vue} +0 -0
- /package/dist/runtime/app/components/content/editor/{markdown.vue.d.ts → text/markdown.vue.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Simple CMS module for Nuxt (pre-alpha)
|
|
2
2
|
|
|
3
|
-
This module is my personal, minimalist, opinionated, independent alternative to @nuxt/content and to a large portion of the WordPress projects I’ve worked on.
|
|
3
|
+
This module is my personal, minimalist, opinionated, independent alternative to @nuxt/content and to a large portion of the WordPress projects I’ve worked on. I want to build projects with Nuxt and customers need the simplest possible admin interface to manage content without getting overwhelmed by buttons and features they don’t need.
|
|
4
|
+
|
|
5
|
+
I don't want to decide on a database schema or limit myself to json. All editable content is stored as a file of supported format (json, markdown, csv, jpg, webp, pdf, etc.) in an S3 bucket. The admin interface is a simple file explorer/editor. The module provides a `useContent` composable to read content files and a `sendMail` utility to send emails via SMTP.
|
|
4
6
|
|
|
5
7
|
[![npm version][npm-version-src]][npm-version-href]
|
|
6
8
|
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
@@ -26,19 +28,20 @@ npx nuxi module add mktcms
|
|
|
26
28
|
```
|
|
27
29
|
|
|
28
30
|
```bash
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
NUXT_PUBLIC_MKTCMS_SITE_URL="http://localhost:3000"
|
|
32
|
+
NUXT_MKTCMS_ADMIN_AUTH_KEY="your-admin-auth-key"
|
|
33
|
+
NUXT_MKTCMS_S3_ACCESS_KEY_ID=your-s3-access-key-id
|
|
34
|
+
NUXT_MKTCMS_S3_SECRET_ACCESS_KEY=your-s3-secret-access-key
|
|
35
|
+
NUXT_MKTCMS_S3_BUCKET=your-s3-bucket-name
|
|
36
|
+
NUXT_MKTCMS_S3_REGION=your-s3-bucket-region
|
|
37
|
+
NUXT_MKTCMS_S3_PREFIX="your-project"
|
|
38
|
+
NUXT_MKTCMS_SMTP_HOST="your-smtp-host"
|
|
39
|
+
NUXT_MKTCMS_SMTP_PORT=465
|
|
40
|
+
NUXT_MKTCMS_SMTP_SECURE=true
|
|
41
|
+
NUXT_MKTCMS_SMTP_USER="your-smtp-user"
|
|
42
|
+
NUXT_MKTCMS_SMTP_PASS="your-smtp-pass"
|
|
43
|
+
NUXT_MKTCMS_MAILER_FROM="your-mailer-from-address"
|
|
44
|
+
NUXT_MKTCMS_MAILER_TO="your-mailer-to-address"
|
|
42
45
|
```
|
|
43
46
|
|
|
44
47
|
Add local development storage folder in `.gitignore`:
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -24,6 +24,9 @@ const module$1 = defineNuxtModule({
|
|
|
24
24
|
mailerFrom: "",
|
|
25
25
|
mailerTo: ""
|
|
26
26
|
}));
|
|
27
|
+
_nuxt.options.runtimeConfig.public.mktcms = defu((_nuxt.options.runtimeConfig.public.mktcms, {
|
|
28
|
+
siteUrl: ""
|
|
29
|
+
}));
|
|
27
30
|
addServerImports({
|
|
28
31
|
name: "sendMail",
|
|
29
32
|
from: resolver.resolve("runtime/server/utils/sendMail")
|
|
@@ -74,9 +77,14 @@ const module$1 = defineNuxtModule({
|
|
|
74
77
|
file: resolver.resolve("./runtime/app/pages/admin/index.vue")
|
|
75
78
|
});
|
|
76
79
|
pages.push({
|
|
77
|
-
name: "Admin Editor",
|
|
78
|
-
path: "/admin/edit/:path",
|
|
79
|
-
file: resolver.resolve("./runtime/app/pages/admin/edit/[path].vue")
|
|
80
|
+
name: "Admin Text Editor",
|
|
81
|
+
path: "/admin/edit/text/:path",
|
|
82
|
+
file: resolver.resolve("./runtime/app/pages/admin/edit/text/[path].vue")
|
|
83
|
+
});
|
|
84
|
+
pages.push({
|
|
85
|
+
name: "Admin Blob Editor",
|
|
86
|
+
path: "/admin/edit/blob/:path",
|
|
87
|
+
file: resolver.resolve("./runtime/app/pages/admin/edit/blob/[path].vue")
|
|
80
88
|
});
|
|
81
89
|
pages.push({
|
|
82
90
|
name: "Admin New Content",
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
</template>
|
|
6
6
|
|
|
7
7
|
<style>
|
|
8
|
-
*{box-sizing:border-box}body{font-family:Arial,sans-serif;margin:0}#mktcms-admin{background-color:#f9f9f9;margin:0 auto;max-width:800px;padding:20px}#mktcms-admin h1{color:#333}#mktcms-admin button{background-color
|
|
8
|
+
*{box-sizing:border-box}body{font-family:Arial,sans-serif;margin:0}#mktcms-admin{--mktcms-primary-color:#3cb371;--mktcms-primary-color-hover:#45a049;background-color:#f9f9f9;margin:0 auto;max-width:800px;padding:20px}#mktcms-admin h1{color:#333}#mktcms-admin .button,#mktcms-admin button{background-color:var(--mktcms-primary-color);border:none;border-radius:5px;color:#fff;cursor:pointer;font-size:16px;padding:10px 20px;text-align:center;text-decoration:none;transition:background-color .3s}#mktcms-admin .button:hover,#mktcms-admin button:hover{background-color:var(--mktcms-primary-color-hover)}#mktcms-admin .button.soft,#mktcms-admin button.soft{background-color:#eee;color:#666}#mktcms-admin .button.soft:hover,#mktcms-admin button.soft:hover{background-color:#ddd}#mktcms-admin .button-icon{height:20px;vertical-align:middle;width:20px}#mktcms-admin input[type=email],#mktcms-admin input[type=password],#mktcms-admin input[type=text],#mktcms-admin select,#mktcms-admin textarea{background-color:#fff;border:1px solid #ccc;border-radius:5px;font-size:16px;padding:10px;width:100%}#mktcms-admin .breadcrumbs{color:#888;font-size:1rem;margin:20px 0}#mktcms-admin .breadcrumbs a{background-color:#eee;border-radius:4px;color:#888;padding:2px 6px;text-decoration:none}#mktcms-admin .breadcrumbs a:hover{text-decoration:underline}#mktcms-admin .dirs,#mktcms-admin .files{display:flex;flex-direction:column;gap:8px}#mktcms-admin .dirs a,#mktcms-admin .files a{border-radius:4px;display:block;padding:8px 12px;text-decoration:none}#mktcms-admin .dirs a:hover,#mktcms-admin .files a:hover{text-decoration:underline}#mktcms-admin .files a{background-color:#fff;color:#555}#mktcms-admin .dirs a{background-color:#555;color:#fff;display:flex;justify-content:space-between}
|
|
9
9
|
</style>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
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>;
|
|
5
|
+
declare const _default: typeof __VLS_export;
|
|
6
|
+
export default _default;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
parts: { type: Array, required: true }
|
|
4
|
+
});
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<div class="breadcrumbs">
|
|
9
|
+
<NuxtLink to="/admin">Hauptordner</NuxtLink>
|
|
10
|
+
<span
|
|
11
|
+
v-for="(part, index) in parts"
|
|
12
|
+
:key="index"
|
|
13
|
+
>
|
|
14
|
+
/
|
|
15
|
+
<NuxtLink
|
|
16
|
+
v-if="index < parts.length - 1"
|
|
17
|
+
:to="`/admin/${parts.slice(0, index + 1).join(':')}`"
|
|
18
|
+
>
|
|
19
|
+
{{ part }}
|
|
20
|
+
</NuxtLink>
|
|
21
|
+
<span
|
|
22
|
+
v-else
|
|
23
|
+
style="font-weight: bold;"
|
|
24
|
+
>
|
|
25
|
+
{{ part }}
|
|
26
|
+
</span>
|
|
27
|
+
</span>
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
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>;
|
|
5
|
+
declare const _default: typeof __VLS_export;
|
|
6
|
+
export default _default;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
path: string;
|
|
3
|
+
dirs: string[];
|
|
4
|
+
};
|
|
5
|
+
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>;
|
|
6
|
+
declare const _default: typeof __VLS_export;
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
path: { type: String, required: true },
|
|
4
|
+
dirs: { type: Array, required: true }
|
|
5
|
+
});
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<div class="dirs">
|
|
10
|
+
<NuxtLink
|
|
11
|
+
v-for="dir in dirs"
|
|
12
|
+
:key="dir"
|
|
13
|
+
:to="`/admin/${path ? path + ':' : ''}${dir}`"
|
|
14
|
+
>
|
|
15
|
+
<span>
|
|
16
|
+
{{ dir }}
|
|
17
|
+
</span>
|
|
18
|
+
<span>
|
|
19
|
+
<svg
|
|
20
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
21
|
+
fill="none"
|
|
22
|
+
viewBox="0 0 24 24"
|
|
23
|
+
stroke-width="1.5"
|
|
24
|
+
stroke="currentColor"
|
|
25
|
+
style="width: 16px; height: 16px; vertical-align: middle;"
|
|
26
|
+
>
|
|
27
|
+
<path
|
|
28
|
+
stroke-linecap="round"
|
|
29
|
+
stroke-linejoin="round"
|
|
30
|
+
d="m8.25 4.5 7.5 7.5-7.5 7.5"
|
|
31
|
+
/>
|
|
32
|
+
</svg>
|
|
33
|
+
</span>
|
|
34
|
+
</NuxtLink>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
path: string;
|
|
3
|
+
dirs: string[];
|
|
4
|
+
};
|
|
5
|
+
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>;
|
|
6
|
+
declare const _default: typeof __VLS_export;
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type __VLS_ModelProps = {
|
|
2
|
+
'path'?: string;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
5
|
+
"update:path": (value: string | undefined) => any;
|
|
6
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
7
|
+
"onUpdate:path"?: ((value: string | undefined) => any) | undefined;
|
|
8
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
9
|
+
declare const _default: typeof __VLS_export;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type __VLS_ModelProps = {
|
|
2
|
+
'path'?: string;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
5
|
+
"update:path": (value: string | undefined) => any;
|
|
6
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
7
|
+
"onUpdate:path"?: ((value: string | undefined) => any) | undefined;
|
|
8
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
9
|
+
declare const _default: typeof __VLS_export;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useRoute } from "#app";
|
|
3
|
+
import { ref } from "vue";
|
|
4
|
+
import Image from "./image.vue";
|
|
5
|
+
import Breadcrumb from "../../breadcrumb.vue";
|
|
6
|
+
const path = useRoute().params.path || "";
|
|
7
|
+
const pathParts = path.split(":");
|
|
8
|
+
const isSaving = ref(false);
|
|
9
|
+
const savingSuccessful = ref(false);
|
|
10
|
+
async function saveContent() {
|
|
11
|
+
isSaving.value = true;
|
|
12
|
+
savingSuccessful.value = false;
|
|
13
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
14
|
+
isSaving.value = false;
|
|
15
|
+
savingSuccessful.value = true;
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<div>
|
|
21
|
+
<Breadcrumb :parts="pathParts" />
|
|
22
|
+
|
|
23
|
+
<div>
|
|
24
|
+
<Image
|
|
25
|
+
v-if="path.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i)"
|
|
26
|
+
:path="path"
|
|
27
|
+
/>
|
|
28
|
+
<button
|
|
29
|
+
style="margin-top: 10px;"
|
|
30
|
+
@click="saveContent"
|
|
31
|
+
>
|
|
32
|
+
<span v-if="isSaving">Speichern...</span>
|
|
33
|
+
<span v-else>Speichern</span>
|
|
34
|
+
</button>
|
|
35
|
+
<span
|
|
36
|
+
v-if="savingSuccessful"
|
|
37
|
+
style="color: green; margin-left: 10px;"
|
|
38
|
+
>✔️ Gespeichert</span>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { useFetch, useRoute } from "#app";
|
|
3
|
+
import { ref, watch } from "vue";
|
|
3
4
|
import Csv from "./csv.vue";
|
|
4
5
|
import Markdown from "./markdown.vue";
|
|
6
|
+
import Breadcrumb from "../../breadcrumb.vue";
|
|
5
7
|
const path = useRoute().params.path || "";
|
|
6
8
|
const pathParts = path.split(":");
|
|
7
9
|
const { data: content } = await useFetch(`/api/admin/content/${path}`);
|
|
10
|
+
const isSaving = ref(false);
|
|
11
|
+
const savingSuccessful = ref(false);
|
|
12
|
+
watch(content, () => {
|
|
13
|
+
savingSuccessful.value = false;
|
|
14
|
+
});
|
|
8
15
|
async function saveContent() {
|
|
16
|
+
isSaving.value = true;
|
|
17
|
+
savingSuccessful.value = false;
|
|
9
18
|
await $fetch(`/api/admin/content/${path}`, {
|
|
10
19
|
method: "POST",
|
|
11
20
|
body: { content: content.value }
|
|
12
21
|
});
|
|
22
|
+
isSaving.value = false;
|
|
23
|
+
savingSuccessful.value = true;
|
|
13
24
|
}
|
|
14
25
|
</script>
|
|
15
26
|
|
|
16
27
|
<template>
|
|
17
28
|
<div>
|
|
18
|
-
<
|
|
19
|
-
<a href="/admin">Hauptverzeichnis</a>
|
|
20
|
-
<span
|
|
21
|
-
v-for="(part, index) in pathParts"
|
|
22
|
-
:key="index"
|
|
23
|
-
>
|
|
24
|
-
/
|
|
25
|
-
<a :href="`/admin/${pathParts.slice(0, index + 1).join(':')}`">
|
|
26
|
-
{{ part }}
|
|
27
|
-
</a>
|
|
28
|
-
</span>
|
|
29
|
-
</div>
|
|
29
|
+
<Breadcrumb :parts="pathParts" />
|
|
30
30
|
|
|
31
31
|
<div v-if="content !== void 0">
|
|
32
32
|
<Markdown
|
|
@@ -37,12 +37,22 @@ async function saveContent() {
|
|
|
37
37
|
v-else-if="path.endsWith('.csv')"
|
|
38
38
|
v-model:content="content"
|
|
39
39
|
/>
|
|
40
|
+
<textarea
|
|
41
|
+
v-if="path.match(/\.(txt|json)$/i)"
|
|
42
|
+
v-model="content"
|
|
43
|
+
style="width: 100%; height: 400px; font-family: monospace; resize: vertical;"
|
|
44
|
+
/>
|
|
40
45
|
<button
|
|
41
46
|
style="margin-top: 10px;"
|
|
42
47
|
@click="saveContent"
|
|
43
48
|
>
|
|
44
|
-
Speichern
|
|
49
|
+
<span v-if="isSaving">Speichern...</span>
|
|
50
|
+
<span v-else>Speichern</span>
|
|
45
51
|
</button>
|
|
52
|
+
<span
|
|
53
|
+
v-if="savingSuccessful"
|
|
54
|
+
style="color: green; margin-left: 10px;"
|
|
55
|
+
>✔️ Gespeichert</span>
|
|
46
56
|
</div>
|
|
47
57
|
</div>
|
|
48
58
|
</template>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
path: string;
|
|
3
|
+
files: string[];
|
|
4
|
+
};
|
|
5
|
+
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>;
|
|
6
|
+
declare const _default: typeof __VLS_export;
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
path: { type: String, required: true },
|
|
4
|
+
files: { type: Array, required: true }
|
|
5
|
+
});
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<div class="files">
|
|
10
|
+
<div
|
|
11
|
+
v-for="file in files"
|
|
12
|
+
:key="file"
|
|
13
|
+
style="display: flex; align-items: center;"
|
|
14
|
+
>
|
|
15
|
+
<NuxtLink
|
|
16
|
+
:to="`/admin/edit/${file.match(/\.md$|\.csv$|\.txt$|\.json$/i) ? 'text' : 'blob'}/${path ? path + ':' : ''}${file}`"
|
|
17
|
+
style="flex-grow: 1;"
|
|
18
|
+
>
|
|
19
|
+
<img
|
|
20
|
+
v-if="file.match(/\.png$|\.jpg$|\.jpeg$|\.gif$|\.svg$|\.webp$/i)"
|
|
21
|
+
:src="`/api/content/${path ? path + ':' : ''}${file}`"
|
|
22
|
+
alt="Vorschaubild"
|
|
23
|
+
style="width: 64px; height: 64px; vertical-align: middle; margin-right: 4px; object-fit: cover;"
|
|
24
|
+
>
|
|
25
|
+
{{ file }}
|
|
26
|
+
</NuxtLink>
|
|
27
|
+
<NuxtLink
|
|
28
|
+
v-if="file.match(/\.png$|\.jpg$|\.jpeg$|\.gif$|\.svg$|\.webp$/i)"
|
|
29
|
+
style="margin-left: 8px;"
|
|
30
|
+
title="Link kopieren"
|
|
31
|
+
>
|
|
32
|
+
<svg
|
|
33
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
34
|
+
fill="none"
|
|
35
|
+
viewBox="0 0 24 24"
|
|
36
|
+
stroke-width="1.5"
|
|
37
|
+
stroke="currentColor"
|
|
38
|
+
class="button-icon"
|
|
39
|
+
>
|
|
40
|
+
<path
|
|
41
|
+
stroke-linecap="round"
|
|
42
|
+
stroke-linejoin="round"
|
|
43
|
+
d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244"
|
|
44
|
+
/>
|
|
45
|
+
</svg>
|
|
46
|
+
</NuxtLink>
|
|
47
|
+
<NuxtLink
|
|
48
|
+
:to="`/admin/move/${path ? path + ':' : ''}${file}`"
|
|
49
|
+
style="margin-left: 8px;"
|
|
50
|
+
title="verschieben / umbenennen"
|
|
51
|
+
>
|
|
52
|
+
<svg
|
|
53
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
54
|
+
fill="none"
|
|
55
|
+
viewBox="0 0 24 24"
|
|
56
|
+
stroke-width="1.5"
|
|
57
|
+
stroke="currentColor"
|
|
58
|
+
class="button-icon"
|
|
59
|
+
>
|
|
60
|
+
<path
|
|
61
|
+
stroke-linecap="round"
|
|
62
|
+
stroke-linejoin="round"
|
|
63
|
+
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"
|
|
64
|
+
/>
|
|
65
|
+
</svg>
|
|
66
|
+
</NuxtLink>
|
|
67
|
+
<NuxtLink
|
|
68
|
+
:to="`/admin/copy/${path ? path + ':' : ''}${file}`"
|
|
69
|
+
style="margin-left: 8px;"
|
|
70
|
+
title="kopieren"
|
|
71
|
+
>
|
|
72
|
+
<svg
|
|
73
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
74
|
+
fill="none"
|
|
75
|
+
viewBox="0 0 24 24"
|
|
76
|
+
stroke-width="1.5"
|
|
77
|
+
stroke="currentColor"
|
|
78
|
+
class="button-icon"
|
|
79
|
+
>
|
|
80
|
+
<path
|
|
81
|
+
stroke-linecap="round"
|
|
82
|
+
stroke-linejoin="round"
|
|
83
|
+
d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
|
|
84
|
+
/>
|
|
85
|
+
</svg>
|
|
86
|
+
</NuxtLink>
|
|
87
|
+
<NuxtLink
|
|
88
|
+
:to="`/admin/delete/${path ? path + ':' : ''}${file}`"
|
|
89
|
+
style="margin-left: 8px;"
|
|
90
|
+
title="löschen"
|
|
91
|
+
>
|
|
92
|
+
<svg
|
|
93
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
94
|
+
fill="none"
|
|
95
|
+
viewBox="0 0 24 24"
|
|
96
|
+
stroke-width="1.5"
|
|
97
|
+
stroke="currentColor"
|
|
98
|
+
class="button-icon"
|
|
99
|
+
>
|
|
100
|
+
<path
|
|
101
|
+
stroke-linecap="round"
|
|
102
|
+
stroke-linejoin="round"
|
|
103
|
+
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"
|
|
104
|
+
/>
|
|
105
|
+
</svg>
|
|
106
|
+
</NuxtLink>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</template>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
path: string;
|
|
3
|
+
files: string[];
|
|
4
|
+
};
|
|
5
|
+
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>;
|
|
6
|
+
declare const _default: typeof __VLS_export;
|
|
7
|
+
export default _default;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { useFetch, useRoute } from "#app";
|
|
3
3
|
import { computed } from "vue";
|
|
4
|
+
import Breadcrumb from "./breadcrumb.vue";
|
|
5
|
+
import Files from "./files.vue";
|
|
6
|
+
import Dirs from "./dirs.vue";
|
|
4
7
|
const path = useRoute().params.path || "";
|
|
5
8
|
const pathParts = path.split(":");
|
|
6
9
|
const { data: keys } = await useFetch("/api/admin/content/list", {
|
|
@@ -30,60 +33,19 @@ const dirs = computed(() => {
|
|
|
30
33
|
|
|
31
34
|
<template>
|
|
32
35
|
<div>
|
|
33
|
-
<
|
|
34
|
-
<a href="/admin">Hauptverzeichnis</a>
|
|
35
|
-
<span
|
|
36
|
-
v-for="(part, index) in pathParts"
|
|
37
|
-
:key="index"
|
|
38
|
-
>
|
|
39
|
-
/
|
|
40
|
-
<a :href="`/admin/${pathParts.slice(0, index + 1).join(':')}`">
|
|
41
|
-
{{ part }}
|
|
42
|
-
</a>
|
|
43
|
-
</span>
|
|
44
|
-
</div>
|
|
36
|
+
<Breadcrumb :parts="pathParts" />
|
|
45
37
|
|
|
46
|
-
<
|
|
38
|
+
<Files
|
|
47
39
|
v-if="files.length"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
v-for="file in files"
|
|
52
|
-
:key="file"
|
|
53
|
-
:href="`/admin/edit/${path ? path + ':' : ''}${file}`"
|
|
54
|
-
>{{ file }}</a>
|
|
55
|
-
</div>
|
|
40
|
+
:path="path"
|
|
41
|
+
:files="files"
|
|
42
|
+
/>
|
|
56
43
|
|
|
57
|
-
<
|
|
44
|
+
<Dirs
|
|
58
45
|
v-if="dirs.length"
|
|
59
|
-
|
|
46
|
+
:path="path"
|
|
47
|
+
:dirs="dirs"
|
|
60
48
|
style="margin-top: 8px;"
|
|
61
|
-
|
|
62
|
-
<a
|
|
63
|
-
v-for="dir in dirs"
|
|
64
|
-
:key="dir"
|
|
65
|
-
:href="`/admin/${path ? path + ':' : ''}${dir}`"
|
|
66
|
-
>
|
|
67
|
-
<span>
|
|
68
|
-
{{ dir }}
|
|
69
|
-
</span>
|
|
70
|
-
<span>
|
|
71
|
-
<svg
|
|
72
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
73
|
-
fill="none"
|
|
74
|
-
viewBox="0 0 24 24"
|
|
75
|
-
stroke-width="1.5"
|
|
76
|
-
stroke="currentColor"
|
|
77
|
-
style="width: 16px; height: 16px; vertical-align: middle;"
|
|
78
|
-
>
|
|
79
|
-
<path
|
|
80
|
-
stroke-linecap="round"
|
|
81
|
-
stroke-linejoin="round"
|
|
82
|
-
d="m8.25 4.5 7.5 7.5-7.5 7.5"
|
|
83
|
-
/>
|
|
84
|
-
</svg>
|
|
85
|
-
</span>
|
|
86
|
-
</a>
|
|
87
|
-
</div>
|
|
49
|
+
/>
|
|
88
50
|
</div>
|
|
89
51
|
</template>
|
|
@@ -1,23 +1,86 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { computed, onMounted, ref, useFetch, useRoute } from "#imports";
|
|
2
3
|
import useAdminUpload from "../../composables/useAdminUpload";
|
|
3
|
-
const {
|
|
4
|
+
const { data: keys } = await useFetch("/api/admin/content/list");
|
|
5
|
+
const dirs = computed(() => {
|
|
6
|
+
const dirSet = /* @__PURE__ */ new Set();
|
|
7
|
+
keys.value?.forEach((key) => {
|
|
8
|
+
const parts = key.split(":");
|
|
9
|
+
if (parts.length > 1) {
|
|
10
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
11
|
+
const dir2 = parts.slice(0, i + 1).join("/");
|
|
12
|
+
dirSet.add(dir2);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
return Array.from(dirSet).sort();
|
|
17
|
+
});
|
|
18
|
+
const route = useRoute();
|
|
19
|
+
const dir = ref(route.query.dir || "");
|
|
20
|
+
const newSubdir = ref("");
|
|
21
|
+
const { isUploading, fileInput, fileInputImg, fileInputPdf, path, uploadFiles } = useAdminUpload();
|
|
22
|
+
onMounted(() => {
|
|
23
|
+
path.value = dir.value;
|
|
24
|
+
});
|
|
4
25
|
</script>
|
|
5
26
|
|
|
6
27
|
<template>
|
|
7
|
-
<div style="display: flex; gap: 8px; margin: 16px 0;">
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
28
|
+
<div style="display: flex; flex-direction: column; gap: 8px; margin: 16px 0;">
|
|
29
|
+
<h1 style="margin-bottom: 0;">
|
|
30
|
+
Datei hochladen
|
|
31
|
+
</h1>
|
|
32
|
+
|
|
33
|
+
<div style="display: flex; gap: 16px; align-items: center;">
|
|
34
|
+
<div style="flex-grow: 1;">
|
|
35
|
+
<h3 style="margin-bottom: 4px;">
|
|
36
|
+
Ordner
|
|
37
|
+
</h3>
|
|
38
|
+
<select
|
|
39
|
+
v-model="dir"
|
|
40
|
+
@change="path = dir"
|
|
41
|
+
>
|
|
42
|
+
<option value="">
|
|
43
|
+
Hauptordner
|
|
44
|
+
</option>
|
|
45
|
+
<option
|
|
46
|
+
v-for="d in dirs"
|
|
47
|
+
:key="d"
|
|
48
|
+
:value="d"
|
|
49
|
+
>
|
|
50
|
+
{{ d }}
|
|
51
|
+
</option>
|
|
52
|
+
</select>
|
|
53
|
+
</div>
|
|
54
|
+
<div style="flex-grow: 1;">
|
|
55
|
+
<h3 style="margin-bottom: 4px;">
|
|
56
|
+
Neuen Unterordner erstellen
|
|
57
|
+
</h3>
|
|
58
|
+
<input
|
|
59
|
+
v-model="newSubdir"
|
|
60
|
+
type="text"
|
|
61
|
+
placeholder="Unterordner (z.B. 'Produkte')"
|
|
62
|
+
@change="path = dir ? dir.replace(/\//g, ':') + ':' + newSubdir : newSubdir"
|
|
63
|
+
>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<button
|
|
68
|
+
:disabled="isUploading"
|
|
69
|
+
@click="fileInputImg?.click()"
|
|
70
|
+
>
|
|
71
|
+
Bild hochladen
|
|
72
|
+
</button>
|
|
73
|
+
<button
|
|
74
|
+
:disabled="isUploading"
|
|
75
|
+
@click="fileInputPdf?.click()"
|
|
12
76
|
>
|
|
13
|
-
|
|
14
|
-
Neuer Inhalt
|
|
77
|
+
PDF hochladen
|
|
15
78
|
</button>
|
|
16
79
|
<button
|
|
17
80
|
:disabled="isUploading"
|
|
18
81
|
@click="fileInput?.click()"
|
|
19
82
|
>
|
|
20
|
-
|
|
83
|
+
Andere Datei hochladen
|
|
21
84
|
</button>
|
|
22
85
|
<input
|
|
23
86
|
ref="fileInput"
|
|
@@ -26,6 +89,24 @@ const { isUploading, fileInput, path, uploadFiles } = useAdminUpload();
|
|
|
26
89
|
accept=".pdf,.jpg,.jpeg,.png,.gif,.svg,.webp,.md,.docx,.txt"
|
|
27
90
|
@change="async (e) => {
|
|
28
91
|
await uploadFiles(e);
|
|
92
|
+
}"
|
|
93
|
+
>
|
|
94
|
+
<input
|
|
95
|
+
ref="fileInputImg"
|
|
96
|
+
style="display: none"
|
|
97
|
+
type="file"
|
|
98
|
+
accept=".jpg,.jpeg,.png,.gif,.svg,.webp"
|
|
99
|
+
@change="async (e) => {
|
|
100
|
+
await uploadFiles(e);
|
|
101
|
+
}"
|
|
102
|
+
>
|
|
103
|
+
<input
|
|
104
|
+
ref="fileInputPdf"
|
|
105
|
+
style="display: none"
|
|
106
|
+
type="file"
|
|
107
|
+
accept=".pdf"
|
|
108
|
+
@change="async (e) => {
|
|
109
|
+
await uploadFiles(e);
|
|
29
110
|
}"
|
|
30
111
|
>
|
|
31
112
|
</div>
|
|
@@ -1,23 +1,50 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useRoute, useRuntimeConfig } from "#imports";
|
|
3
|
+
const { public: { mktcms: { siteUrl } } } = useRuntimeConfig();
|
|
4
|
+
const route = useRoute();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
1
7
|
<template>
|
|
2
|
-
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
<div style="display: flex; justify-content: space-between; align-items: center; gap: 8px;">
|
|
9
|
+
<NuxtLink
|
|
10
|
+
v-if="route.fullPath != '/admin'"
|
|
11
|
+
to="/admin"
|
|
12
|
+
class="button soft"
|
|
13
|
+
>
|
|
14
|
+
<svg
|
|
15
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
16
|
+
fill="none"
|
|
17
|
+
viewBox="0 0 24 24"
|
|
18
|
+
stroke-width="1.5"
|
|
19
|
+
stroke="currentColor"
|
|
20
|
+
class="button-icon"
|
|
7
21
|
>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
22
|
+
<path
|
|
23
|
+
stroke-linecap="round"
|
|
24
|
+
stroke-linejoin="round"
|
|
25
|
+
d="M15.75 19.5 8.25 12l7.5-7.5"
|
|
26
|
+
/>
|
|
27
|
+
</svg>
|
|
28
|
+
</NuxtLink>
|
|
11
29
|
<NuxtLink
|
|
12
30
|
to="/admin/new"
|
|
13
|
-
|
|
31
|
+
class="button"
|
|
32
|
+
style="margin-left: auto;"
|
|
33
|
+
>
|
|
34
|
+
Datei hochladen
|
|
35
|
+
</NuxtLink>
|
|
36
|
+
<NuxtLink
|
|
37
|
+
:to="siteUrl"
|
|
38
|
+
external
|
|
39
|
+
target="_blank"
|
|
40
|
+
class="button soft"
|
|
14
41
|
>
|
|
15
|
-
|
|
42
|
+
zur Website
|
|
16
43
|
</NuxtLink>
|
|
17
44
|
<NuxtLink
|
|
18
45
|
external
|
|
19
46
|
to="/api/admin/logout"
|
|
20
|
-
|
|
47
|
+
class="button soft"
|
|
21
48
|
>
|
|
22
49
|
Abmelden
|
|
23
50
|
</NuxtLink>
|
|
@@ -4,6 +4,8 @@ export default function useAdminUpload(): {
|
|
|
4
4
|
path: import("vue").Ref<string | null, string | null>;
|
|
5
5
|
files: import("vue").Ref<string[], string[]>;
|
|
6
6
|
fileInput: import("vue").Ref<HTMLInputElement | null, HTMLInputElement | null>;
|
|
7
|
+
fileInputImg: import("vue").Ref<HTMLInputElement | null, HTMLInputElement | null>;
|
|
8
|
+
fileInputPdf: import("vue").Ref<HTMLInputElement | null, HTMLInputElement | null>;
|
|
7
9
|
uploadFiles: (event: Event) => Promise<void>;
|
|
8
10
|
deleteFile: (path: string) => Promise<void>;
|
|
9
11
|
};
|
|
@@ -3,6 +3,8 @@ export default function useAdminUpload() {
|
|
|
3
3
|
const uploadError = ref(null);
|
|
4
4
|
const files = ref([]);
|
|
5
5
|
const fileInput = ref(null);
|
|
6
|
+
const fileInputImg = ref(null);
|
|
7
|
+
const fileInputPdf = ref(null);
|
|
6
8
|
const isUploading = ref(false);
|
|
7
9
|
const path = ref(null);
|
|
8
10
|
const sanePath = computed(() => {
|
|
@@ -55,6 +57,8 @@ export default function useAdminUpload() {
|
|
|
55
57
|
path,
|
|
56
58
|
files,
|
|
57
59
|
fileInput,
|
|
60
|
+
fileInputImg,
|
|
61
|
+
fileInputPdf,
|
|
58
62
|
uploadFiles,
|
|
59
63
|
deleteFile
|
|
60
64
|
};
|
|
@@ -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,12 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import Admin from "../../../../components/admin.vue";
|
|
3
|
+
import Header from "../../../../components/header.vue";
|
|
4
|
+
import Editor from "../../../../components/content/editor/blob/index.vue";
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<Admin>
|
|
9
|
+
<Header />
|
|
10
|
+
<Editor />
|
|
11
|
+
</Admin>
|
|
12
|
+
</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,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,12 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import Admin from "../../../../components/admin.vue";
|
|
3
|
+
import Header from "../../../../components/header.vue";
|
|
4
|
+
import Editor from "../../../../components/content/editor/text/index.vue";
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<Admin>
|
|
9
|
+
<Header />
|
|
10
|
+
<Editor />
|
|
11
|
+
</Admin>
|
|
12
|
+
</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;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
2
2
|
export default _default;
|
|
@@ -8,11 +8,17 @@ export default defineEventHandler(async (event) => {
|
|
|
8
8
|
const { path } = await getValidatedRouterParams(event, (params) => paramsSchema.parse(params));
|
|
9
9
|
const { mktcms: { s3Prefix } } = useRuntimeConfig();
|
|
10
10
|
const fullPath = s3Prefix + ":" + path;
|
|
11
|
+
const isImage = path.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i);
|
|
12
|
+
if (isImage) {
|
|
13
|
+
event.node.res.setHeader("Content-Type", "image/" + path.split(".").pop()?.toLowerCase());
|
|
14
|
+
} else {
|
|
15
|
+
event.node.res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
16
|
+
}
|
|
11
17
|
const storage = useStorage("content");
|
|
12
|
-
const file = await storage.getItem(fullPath);
|
|
18
|
+
const file = isImage ? await storage.getItemRaw(fullPath) : await storage.getItem(fullPath);
|
|
13
19
|
if (!file) {
|
|
14
20
|
const fallbackStorage = useStorage("fallback");
|
|
15
|
-
const fallbackFile = await fallbackStorage.getItem(fullPath);
|
|
21
|
+
const fallbackFile = isImage ? await fallbackStorage.getItemRaw(fullPath) : await fallbackStorage.getItem(fullPath);
|
|
16
22
|
if (fallbackFile) {
|
|
17
23
|
return fallbackFile;
|
|
18
24
|
}
|
|
@@ -48,11 +48,15 @@ export default defineEventHandler(async (event) => {
|
|
|
48
48
|
const { path } = await getValidatedRouterParams(event, (params) => paramsSchema.parse(params));
|
|
49
49
|
const { mktcms: { s3Prefix } } = useRuntimeConfig();
|
|
50
50
|
const fullPath = s3Prefix + ":" + path;
|
|
51
|
+
const isImage = path.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i);
|
|
52
|
+
if (isImage) {
|
|
53
|
+
event.node.res.setHeader("Content-Type", "image/" + path.split(".").pop()?.toLowerCase());
|
|
54
|
+
}
|
|
51
55
|
const storage = useStorage("content");
|
|
52
|
-
const file = await storage.getItem(fullPath);
|
|
56
|
+
const file = isImage ? await storage.getItemRaw(fullPath) : await storage.getItem(fullPath);
|
|
53
57
|
if (!file) {
|
|
54
58
|
const fallbackStorage = useStorage("fallback");
|
|
55
|
-
const fallbackFile = await fallbackStorage.getItem(fullPath);
|
|
59
|
+
const fallbackFile = isImage ? await fallbackStorage.getItemRaw(fullPath) : await fallbackStorage.getItem(fullPath);
|
|
56
60
|
if (fallbackFile) {
|
|
57
61
|
return parsedFile(fullPath, fallbackFile);
|
|
58
62
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import Admin from "../../../components/admin.vue";
|
|
3
|
-
import Header from "../../../components/header.vue";
|
|
4
|
-
import Editor from "../../../components/content/editor/index.vue";
|
|
5
|
-
</script>
|
|
6
|
-
|
|
7
|
-
<template>
|
|
8
|
-
<Admin>
|
|
9
|
-
<Header />
|
|
10
|
-
<Editor />
|
|
11
|
-
</Admin>
|
|
12
|
-
</template>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/dist/runtime/app/components/content/editor/{markdown.d.vue.ts → text/markdown.d.vue.ts}
RENAMED
|
File without changes
|
|
File without changes
|
/package/dist/runtime/app/components/content/editor/{markdown.vue.d.ts → text/markdown.vue.d.ts}
RENAMED
|
File without changes
|