mktcms 0.3.18 → 0.3.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/dist/module.json +1 -1
- package/dist/runtime/app/components/content/editor/frontmatter/filePicker/modal.d.vue.ts +1 -1
- package/dist/runtime/app/components/content/editor/frontmatter/filePicker/modal.vue.d.ts +1 -1
- package/dist/runtime/app/components/content/editor/frontmatter/filePicker/node.d.vue.ts +1 -1
- package/dist/runtime/app/components/content/editor/frontmatter/filePicker/node.vue.d.ts +1 -1
- package/dist/runtime/app/components/content/editor/monacoEditor.vue +119 -11
- package/dist/runtime/server/api/admin/list.js +4 -1
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
type __VLS_Props = {
|
|
2
2
|
isOpen: boolean;
|
|
3
|
-
uiHint: 'image' | 'pdf' | 'file';
|
|
3
|
+
uiHint: 'image' | 'pdf' | 'file' | 'media';
|
|
4
4
|
};
|
|
5
5
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
6
6
|
select: (path: string) => any;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
type __VLS_Props = {
|
|
2
2
|
isOpen: boolean;
|
|
3
|
-
uiHint: 'image' | 'pdf' | 'file';
|
|
3
|
+
uiHint: 'image' | 'pdf' | 'file' | 'media';
|
|
4
4
|
};
|
|
5
5
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
6
6
|
select: (path: string) => any;
|
|
@@ -2,7 +2,7 @@ type __VLS_Props = {
|
|
|
2
2
|
path: string;
|
|
3
3
|
name: string;
|
|
4
4
|
level: number;
|
|
5
|
-
uiHint: 'image' | 'pdf' | 'file';
|
|
5
|
+
uiHint: 'image' | 'pdf' | 'file' | 'media';
|
|
6
6
|
};
|
|
7
7
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
8
8
|
select: (path: string) => any;
|
|
@@ -2,7 +2,7 @@ type __VLS_Props = {
|
|
|
2
2
|
path: string;
|
|
3
3
|
name: string;
|
|
4
4
|
level: number;
|
|
5
|
-
uiHint: 'image' | 'pdf' | 'file';
|
|
5
|
+
uiHint: 'image' | 'pdf' | 'file' | 'media';
|
|
6
6
|
};
|
|
7
7
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
8
8
|
select: (path: string) => any;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
3
3
|
import TurndownService from "turndown";
|
|
4
|
+
import FilePickerModal from "./frontmatter/filePicker/modal.vue";
|
|
5
|
+
import { isImagePath } from "../../../../shared/contentFiles";
|
|
4
6
|
import "monaco-editor/min/vs/editor/editor.main.css";
|
|
5
7
|
import "monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution.js";
|
|
6
8
|
import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js";
|
|
@@ -12,7 +14,11 @@ const props = defineProps({
|
|
|
12
14
|
});
|
|
13
15
|
const emit = defineEmits(["update:modelValue"]);
|
|
14
16
|
const rootEl = ref(null);
|
|
17
|
+
const isFilePickerOpen = ref(false);
|
|
18
|
+
const isContextMenuOpen = ref(false);
|
|
19
|
+
const contextMenuPosition = ref({ x: 0, y: 0 });
|
|
15
20
|
let editor;
|
|
21
|
+
let pendingFileInsertionSelection;
|
|
16
22
|
let resizeObserver;
|
|
17
23
|
let suppressModelEmit = false;
|
|
18
24
|
const allowedPasteElementNames = /* @__PURE__ */ new Set([
|
|
@@ -146,14 +152,11 @@ function sanitizePastedHtml(html) {
|
|
|
146
152
|
}
|
|
147
153
|
return clipboardDocument.body;
|
|
148
154
|
}
|
|
149
|
-
function insertMarkdown(markdown) {
|
|
150
|
-
if (!editor)
|
|
151
|
-
return;
|
|
152
|
-
const selections = editor.getSelections();
|
|
153
|
-
if (!selections?.length)
|
|
155
|
+
function insertMarkdown(markdown, selections = editor?.getSelections()) {
|
|
156
|
+
if (!editor || !selections?.length)
|
|
154
157
|
return;
|
|
155
158
|
editor.pushUndoStop();
|
|
156
|
-
editor.executeEdits("
|
|
159
|
+
editor.executeEdits("insert-markdown", selections.map((selection) => ({
|
|
157
160
|
range: selection,
|
|
158
161
|
text: markdown,
|
|
159
162
|
forceMoveMarkers: true
|
|
@@ -161,6 +164,75 @@ function insertMarkdown(markdown) {
|
|
|
161
164
|
editor.pushUndoStop();
|
|
162
165
|
editor.focus();
|
|
163
166
|
}
|
|
167
|
+
function escapeMarkdownLabel(label) {
|
|
168
|
+
return label.replaceAll("\\", "\\\\").replaceAll("[", "\\[").replaceAll("]", "\\]");
|
|
169
|
+
}
|
|
170
|
+
function filenameWithoutExtension(path) {
|
|
171
|
+
const filename = path.split(":").at(-1) ?? path;
|
|
172
|
+
return filename.replace(/\.[^/.]+$/, "");
|
|
173
|
+
}
|
|
174
|
+
function toContentUrl(path) {
|
|
175
|
+
return `/api/content/${encodeURIComponent(path)}`;
|
|
176
|
+
}
|
|
177
|
+
function toMarkdownFileReference(path) {
|
|
178
|
+
const label = escapeMarkdownLabel(filenameWithoutExtension(path));
|
|
179
|
+
const url = toContentUrl(path);
|
|
180
|
+
if (isImagePath(path))
|
|
181
|
+
return ``;
|
|
182
|
+
return `[${label}](${url})`;
|
|
183
|
+
}
|
|
184
|
+
function closeContextMenu() {
|
|
185
|
+
isContextMenuOpen.value = false;
|
|
186
|
+
}
|
|
187
|
+
function openFilePicker() {
|
|
188
|
+
closeContextMenu();
|
|
189
|
+
isFilePickerOpen.value = true;
|
|
190
|
+
}
|
|
191
|
+
function insertSelectedFile(path) {
|
|
192
|
+
insertMarkdown(toMarkdownFileReference(path), pendingFileInsertionSelection ? [pendingFileInsertionSelection] : void 0);
|
|
193
|
+
pendingFileInsertionSelection = void 0;
|
|
194
|
+
}
|
|
195
|
+
function getMouseTargetPosition(event) {
|
|
196
|
+
const target = editor?.getTargetAtClientPoint(event.clientX, event.clientY);
|
|
197
|
+
if (target && "position" in target && target.position)
|
|
198
|
+
return target.position;
|
|
199
|
+
return void 0;
|
|
200
|
+
}
|
|
201
|
+
function handleContextMenu(event) {
|
|
202
|
+
if (!editor || !rootEl.value?.contains(event.target))
|
|
203
|
+
return;
|
|
204
|
+
event.preventDefault();
|
|
205
|
+
event.stopPropagation();
|
|
206
|
+
const position = getMouseTargetPosition(event);
|
|
207
|
+
if (position) {
|
|
208
|
+
pendingFileInsertionSelection = new monaco.Selection(
|
|
209
|
+
position.lineNumber,
|
|
210
|
+
position.column,
|
|
211
|
+
position.lineNumber,
|
|
212
|
+
position.column
|
|
213
|
+
);
|
|
214
|
+
editor.setPosition(position);
|
|
215
|
+
} else {
|
|
216
|
+
pendingFileInsertionSelection = editor.getSelection() ?? void 0;
|
|
217
|
+
}
|
|
218
|
+
contextMenuPosition.value = {
|
|
219
|
+
x: event.clientX,
|
|
220
|
+
y: event.clientY
|
|
221
|
+
};
|
|
222
|
+
isContextMenuOpen.value = true;
|
|
223
|
+
}
|
|
224
|
+
function handleDocumentClick(event) {
|
|
225
|
+
if (!isContextMenuOpen.value)
|
|
226
|
+
return;
|
|
227
|
+
const target = event.target;
|
|
228
|
+
if (target.closest("[data-monaco-custom-context-menu]"))
|
|
229
|
+
return;
|
|
230
|
+
closeContextMenu();
|
|
231
|
+
}
|
|
232
|
+
function handleDocumentKeydown(event) {
|
|
233
|
+
if (event.key === "Escape")
|
|
234
|
+
closeContextMenu();
|
|
235
|
+
}
|
|
164
236
|
function handlePaste(event) {
|
|
165
237
|
if (!editor?.hasTextFocus())
|
|
166
238
|
return;
|
|
@@ -195,7 +267,8 @@ onMounted(() => {
|
|
|
195
267
|
minimap: { enabled: false },
|
|
196
268
|
scrollBeyondLastLine: false,
|
|
197
269
|
wordWrap: "on",
|
|
198
|
-
automaticLayout: true
|
|
270
|
+
automaticLayout: true,
|
|
271
|
+
contextmenu: false
|
|
199
272
|
});
|
|
200
273
|
editor.onDidChangeModelContent(() => {
|
|
201
274
|
if (!editor || suppressModelEmit)
|
|
@@ -203,6 +276,9 @@ onMounted(() => {
|
|
|
203
276
|
emit("update:modelValue", editor.getValue());
|
|
204
277
|
});
|
|
205
278
|
document.addEventListener("paste", handlePaste, true);
|
|
279
|
+
document.addEventListener("contextmenu", handleContextMenu, true);
|
|
280
|
+
document.addEventListener("click", handleDocumentClick, true);
|
|
281
|
+
document.addEventListener("keydown", handleDocumentKeydown, true);
|
|
206
282
|
resizeObserver = new ResizeObserver(() => {
|
|
207
283
|
editor?.layout();
|
|
208
284
|
});
|
|
@@ -225,14 +301,46 @@ onBeforeUnmount(() => {
|
|
|
225
301
|
resizeObserver?.disconnect();
|
|
226
302
|
resizeObserver = void 0;
|
|
227
303
|
document.removeEventListener("paste", handlePaste, true);
|
|
304
|
+
document.removeEventListener("contextmenu", handleContextMenu, true);
|
|
305
|
+
document.removeEventListener("click", handleDocumentClick, true);
|
|
306
|
+
document.removeEventListener("keydown", handleDocumentKeydown, true);
|
|
228
307
|
editor?.dispose();
|
|
229
308
|
editor = void 0;
|
|
230
309
|
});
|
|
231
310
|
</script>
|
|
232
311
|
|
|
233
312
|
<template>
|
|
234
|
-
<div
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
313
|
+
<div class="relative w-full h-full">
|
|
314
|
+
<div
|
|
315
|
+
ref="rootEl"
|
|
316
|
+
class="w-full h-full"
|
|
317
|
+
/>
|
|
318
|
+
|
|
319
|
+
<div
|
|
320
|
+
v-if="isContextMenuOpen"
|
|
321
|
+
data-monaco-custom-context-menu
|
|
322
|
+
class="fixed z-9999 min-w-48 rounded-md border border-black/10 bg-white p-1 shadow-[0_8px_24px_rgba(0,0,0,0.18)]"
|
|
323
|
+
:style="{
|
|
324
|
+
left: `${contextMenuPosition.x}px`,
|
|
325
|
+
top: `${contextMenuPosition.y}px`
|
|
326
|
+
}"
|
|
327
|
+
role="menu"
|
|
328
|
+
>
|
|
329
|
+
<button
|
|
330
|
+
type="button"
|
|
331
|
+
class="w-full rounded px-3 py-2 text-left text-sm hover:bg-gray-100"
|
|
332
|
+
role="menuitem"
|
|
333
|
+
@click="openFilePicker"
|
|
334
|
+
>
|
|
335
|
+
Datei auswählen
|
|
336
|
+
</button>
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
<FilePickerModal
|
|
340
|
+
:is-open="isFilePickerOpen"
|
|
341
|
+
ui-hint="media"
|
|
342
|
+
@close="isFilePickerOpen = false"
|
|
343
|
+
@select="insertSelectedFile"
|
|
344
|
+
/>
|
|
345
|
+
</div>
|
|
238
346
|
</template>
|
|
@@ -8,7 +8,7 @@ function alphaSort(a, b) {
|
|
|
8
8
|
}
|
|
9
9
|
const querySchema = z.object({
|
|
10
10
|
path: z.string().optional(),
|
|
11
|
-
type: z.enum(["image", "pdf", "file"]).optional()
|
|
11
|
+
type: z.enum(["image", "pdf", "file", "media"]).optional()
|
|
12
12
|
});
|
|
13
13
|
export default defineEventHandler(async (event) => {
|
|
14
14
|
const { path, type } = await getValidatedQuery(event, (query) => querySchema.parse(query));
|
|
@@ -23,6 +23,9 @@ export default defineEventHandler(async (event) => {
|
|
|
23
23
|
if (type === "pdf") {
|
|
24
24
|
return isPdfPath(key);
|
|
25
25
|
}
|
|
26
|
+
if (type === "media") {
|
|
27
|
+
return isImagePath(key) || isPdfPath(key);
|
|
28
|
+
}
|
|
26
29
|
return true;
|
|
27
30
|
});
|
|
28
31
|
const filteredFiles = matchingKeys.filter((key) => !key.includes(":"));
|