Added Internal Links #84
This commit is contained in:
@@ -4,26 +4,26 @@
|
|||||||
<div v-if="editor" class="border-b border-gray-100 dark:border-gray-800 px-4 py-2 flex flex-wrap gap-1 items-center sticky top-0 bg-white dark:bg-gray-900 z-20 shadow-sm select-none">
|
<div v-if="editor" class="border-b border-gray-100 dark:border-gray-800 px-4 py-2 flex flex-wrap gap-1 items-center sticky top-0 bg-white dark:bg-gray-900 z-20 shadow-sm select-none">
|
||||||
|
|
||||||
<div class="flex gap-0.5 border-r border-gray-200 dark:border-gray-700 pr-2 mr-2">
|
<div class="flex gap-0.5 border-r border-gray-200 dark:border-gray-700 pr-2 mr-2">
|
||||||
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }" class="toolbar-btn font-bold">B</button>
|
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }" class="toolbar-btn font-bold" title="Fett">B</button>
|
||||||
<button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }" class="toolbar-btn italic">I</button>
|
<button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }" class="toolbar-btn italic" title="Kursiv">I</button>
|
||||||
<button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }" class="toolbar-btn line-through">S</button>
|
<button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }" class="toolbar-btn line-through" title="Durchgestrichen">S</button>
|
||||||
<button @click="editor.chain().focus().toggleHighlight().run()" :class="{ 'is-active': editor.isActive('highlight') }" class="toolbar-btn text-yellow-500 font-bold">M</button>
|
<button @click="editor.chain().focus().toggleHighlight().run()" :class="{ 'is-active': editor.isActive('highlight') }" class="toolbar-btn text-yellow-500 font-bold" title="Markieren">M</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-0.5 border-r border-gray-200 dark:border-gray-700 pr-2 mr-2">
|
<div class="flex gap-0.5 border-r border-gray-200 dark:border-gray-700 pr-2 mr-2">
|
||||||
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }" class="toolbar-btn">H1</button>
|
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }" class="toolbar-btn" title="Überschrift 1">H1</button>
|
||||||
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }" class="toolbar-btn">H2</button>
|
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }" class="toolbar-btn" title="Überschrift 2">H2</button>
|
||||||
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }" class="toolbar-btn font-mono text-xs"></></button>
|
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }" class="toolbar-btn font-mono text-xs" title="Code Block"></></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-0.5 border-r border-gray-200 dark:border-gray-700 pr-2 mr-2">
|
<div class="flex gap-0.5 border-r border-gray-200 dark:border-gray-700 pr-2 mr-2">
|
||||||
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }" class="toolbar-btn">•</button>
|
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }" class="toolbar-btn" title="Liste">•</button>
|
||||||
<button @click="editor.chain().focus().toggleTaskList().run()" :class="{ 'is-active': editor.isActive('taskList') }" class="toolbar-btn">☑</button>
|
<button @click="editor.chain().focus().toggleTaskList().run()" :class="{ 'is-active': editor.isActive('taskList') }" class="toolbar-btn" title="Aufgabenliste">☑</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-0.5 items-center">
|
<div class="flex gap-0.5 items-center">
|
||||||
<button @click="editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()" class="toolbar-btn">▦</button>
|
<button @click="editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()" class="toolbar-btn" title="Tabelle">▦</button>
|
||||||
<button @click="addVideo" class="toolbar-btn text-red-600">
|
<button @click="addVideo" class="toolbar-btn text-red-600" title="YouTube Video">
|
||||||
<UIcon name="i-heroicons-video-camera" class="w-4 h-4" />
|
<UIcon name="i-heroicons-video-camera" class="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,19 +71,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { watch } from 'vue'
|
import { watch } from 'vue'
|
||||||
|
import { useRouter } from '#app'
|
||||||
import { useEditor, EditorContent, VueNodeViewRenderer } from '@tiptap/vue-3'
|
import { useEditor, EditorContent, VueNodeViewRenderer } from '@tiptap/vue-3'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { BubbleMenu, FloatingMenu } from '@tiptap/vue-3/menus'
|
import { BubbleMenu, FloatingMenu } from '@tiptap/vue-3/menus'
|
||||||
|
|
||||||
|
// Tiptap Extensions
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
import Placeholder from '@tiptap/extension-placeholder'
|
import Placeholder from '@tiptap/extension-placeholder'
|
||||||
import CodeBlock from '@tiptap/extension-code-block'
|
|
||||||
import { Table } from '@tiptap/extension-table'
|
|
||||||
import { TableRow } from '@tiptap/extension-table-row'
|
|
||||||
import { TableCell } from '@tiptap/extension-table-cell'
|
|
||||||
import { TableHeader } from '@tiptap/extension-table-header'
|
|
||||||
import BubbleMenuExtension from '@tiptap/extension-bubble-menu'
|
|
||||||
import FloatingMenuExtension from '@tiptap/extension-floating-menu'
|
|
||||||
import Link from '@tiptap/extension-link'
|
import Link from '@tiptap/extension-link'
|
||||||
import TaskList from '@tiptap/extension-task-list'
|
import TaskList from '@tiptap/extension-task-list'
|
||||||
import TaskItem from '@tiptap/extension-task-item'
|
import TaskItem from '@tiptap/extension-task-item'
|
||||||
@@ -92,18 +87,39 @@ import Highlight from '@tiptap/extension-highlight'
|
|||||||
import Youtube from '@tiptap/extension-youtube'
|
import Youtube from '@tiptap/extension-youtube'
|
||||||
import Typography from '@tiptap/extension-typography'
|
import Typography from '@tiptap/extension-typography'
|
||||||
import CharacterCount from '@tiptap/extension-character-count'
|
import CharacterCount from '@tiptap/extension-character-count'
|
||||||
|
import CodeBlock from '@tiptap/extension-code-block'
|
||||||
|
|
||||||
|
// -----------------------------------------------------------
|
||||||
|
// WICHTIGE ÄNDERUNG: NUR Table, KEINE Row/Cell/Header Imports
|
||||||
|
// -----------------------------------------------------------
|
||||||
|
import { Table } from '@tiptap/extension-table'
|
||||||
|
import { TableRow } from '@tiptap/extension-table-row'
|
||||||
|
import { TableCell } from '@tiptap/extension-table-cell'
|
||||||
|
import { TableHeader } from '@tiptap/extension-table-header'
|
||||||
|
|
||||||
|
import BubbleMenuExtension from '@tiptap/extension-bubble-menu'
|
||||||
|
import FloatingMenuExtension from '@tiptap/extension-floating-menu'
|
||||||
|
import Mention from '@tiptap/extension-mention'
|
||||||
|
|
||||||
// IMPORT DER NEUEN KOMPONENTE
|
|
||||||
import TiptapTaskItem from './TiptapTaskItem.vue'
|
import TiptapTaskItem from './TiptapTaskItem.vue'
|
||||||
|
import wikiSuggestion from '~/composables/useWikiSuggestion'
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: any }>()
|
const props = defineProps<{ modelValue: any }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// Damit wir TaskItem nicht ständig neu initialisieren
|
||||||
|
const CustomTaskItem = TaskItem.extend({
|
||||||
|
addNodeView() {
|
||||||
|
return VueNodeViewRenderer(TiptapTaskItem)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
content: props.modelValue,
|
content: props.modelValue,
|
||||||
extensions: [
|
extensions: [
|
||||||
StarterKit.configure({ codeBlock: false }),
|
StarterKit.configure({ codeBlock: false }),
|
||||||
Placeholder.configure({ placeholder: 'Tippe "/" oder schreibe los...' }),
|
Placeholder.configure({ placeholder: 'Tippe "/" für Befehle oder "#" für Seiten...' }),
|
||||||
|
|
||||||
BubbleMenuExtension.configure({ shouldShow: ({ editor, from, to }) => !editor.state.selection.empty && (to - from > 0) }),
|
BubbleMenuExtension.configure({ shouldShow: ({ editor, from, to }) => !editor.state.selection.empty && (to - from > 0) }),
|
||||||
FloatingMenuExtension,
|
FloatingMenuExtension,
|
||||||
@@ -112,35 +128,63 @@ const editor = useEditor({
|
|||||||
Image, Highlight, Typography, CharacterCount, CodeBlock,
|
Image, Highlight, Typography, CharacterCount, CodeBlock,
|
||||||
Youtube.configure({ width: 640, height: 480 }),
|
Youtube.configure({ width: 640, height: 480 }),
|
||||||
|
|
||||||
Table.configure({ resizable: true }), TableRow, TableHeader, TableCell,
|
// -----------------------------------------------------------
|
||||||
|
// WICHTIGE ÄNDERUNG: Nur Table.configure()
|
||||||
|
// TableRow, TableHeader, TableCell wurden HIER ENTFERNT
|
||||||
|
// -----------------------------------------------------------
|
||||||
|
Table.configure({ resizable: true }),
|
||||||
|
TableRow,
|
||||||
|
TableHeader,
|
||||||
|
TableCell,
|
||||||
|
|
||||||
// TASK LIST KONFIGURATION
|
// TASK LIST
|
||||||
TaskList,
|
TaskList,
|
||||||
TaskItem.configure({
|
CustomTaskItem.configure({
|
||||||
nested: true,
|
nested: true,
|
||||||
}).extend({
|
}),
|
||||||
// HIER SAGEN WIR TIPTAP: BENUTZE UNSERE VUE KOMPONENTE
|
|
||||||
addNodeView() {
|
// INTERNAL LINKING
|
||||||
return VueNodeViewRenderer(TiptapTaskItem)
|
Mention.configure({
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: 'wiki-mention',
|
||||||
},
|
},
|
||||||
|
suggestion: {
|
||||||
|
...wikiSuggestion,
|
||||||
|
char: '#'
|
||||||
|
},
|
||||||
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
editorProps: {
|
editorProps: {
|
||||||
attributes: {
|
attributes: {
|
||||||
class: 'min-h-[500px] pb-40',
|
class: 'min-h-[500px] pb-40',
|
||||||
},
|
},
|
||||||
|
handleClick: (view, pos, event) => {
|
||||||
|
const target = event.target as HTMLElement
|
||||||
|
if (target.closest('.wiki-mention')) {
|
||||||
|
const id = target.getAttribute('data-id')
|
||||||
|
if (id) {
|
||||||
|
router.push(`/wiki/${id}`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onUpdate: ({ editor }) => {
|
onUpdate: ({ editor }) => {
|
||||||
emit('update:modelValue', editor.getJSON())
|
emit('update:modelValue', editor.getJSON())
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Sync Model -> Editor
|
||||||
watch(() => props.modelValue, (val) => {
|
watch(() => props.modelValue, (val) => {
|
||||||
if (editor.value && JSON.stringify(val) !== JSON.stringify(editor.value.getJSON())) {
|
if (editor.value && JSON.stringify(val) !== JSON.stringify(editor.value.getJSON())) {
|
||||||
|
//@ts-ignore
|
||||||
editor.value.commands.setContent(val, false)
|
editor.value.commands.setContent(val, false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ACTIONS
|
||||||
const setLink = () => {
|
const setLink = () => {
|
||||||
if (!editor.value) return
|
if (!editor.value) return
|
||||||
const previousUrl = editor.value.getAttributes('link').href
|
const previousUrl = editor.value.getAttributes('link').href
|
||||||
@@ -174,22 +218,38 @@ const addVideo = () => {
|
|||||||
@apply bg-gray-200 dark:bg-gray-600 text-black dark:text-white;
|
@apply bg-gray-200 dark:bg-gray-600 text-black dark:text-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* EDITOR STYLES */
|
/* GLOBAL EDITOR STYLES */
|
||||||
:deep(.prose) {
|
:deep(.prose) {
|
||||||
/* Cursor Fix */
|
|
||||||
.ProseMirror {
|
.ProseMirror {
|
||||||
outline: none;
|
outline: none;
|
||||||
caret-color: currentColor;
|
caret-color: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LIST RESET: Wir müssen nur die UL resetten, die LI wird von Vue gerendert */
|
/* LIST RESET */
|
||||||
ul[data-type="taskList"] {
|
ul[data-type="taskList"] {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Table Fixes */
|
/* MENTION */
|
||||||
|
.wiki-mention {
|
||||||
|
/* Pill-Shape, grau/neutral statt knallig blau */
|
||||||
|
@apply bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-200 px-1.5 py-0.5 rounded-md text-sm font-medium no-underline inline-block mx-0.5 align-middle border border-gray-200 dark:border-gray-700;
|
||||||
|
|
||||||
|
box-decoration-break: clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-mention::before {
|
||||||
|
@apply text-gray-400 dark:text-gray-500 mr-0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-mention:hover {
|
||||||
|
@apply bg-primary-50 dark:bg-primary-900/30 border-primary-200 dark:border-primary-800 text-primary-700 dark:text-primary-400;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TABLE */
|
||||||
table { width: 100% !important; border-collapse: collapse; table-layout: fixed; margin: 1rem 0; }
|
table { width: 100% !important; border-collapse: collapse; table-layout: fixed; margin: 1rem 0; }
|
||||||
th, td { border: 1px solid #d1d5db; padding: 0.5rem; vertical-align: top; box-sizing: border-box; min-width: 1em; }
|
th, td { border: 1px solid #d1d5db; padding: 0.5rem; vertical-align: top; box-sizing: border-box; min-width: 1em; }
|
||||||
.dark th, .dark td { border-color: #374151; }
|
.dark th, .dark td { border-color: #374151; }
|
||||||
@@ -197,16 +257,16 @@ const addVideo = () => {
|
|||||||
.dark th { background-color: #1f2937; }
|
.dark th { background-color: #1f2937; }
|
||||||
.column-resize-handle { background-color: #3b82f6; width: 4px; }
|
.column-resize-handle { background-color: #3b82f6; width: 4px; }
|
||||||
|
|
||||||
/* Code Block */
|
/* CODE */
|
||||||
pre { background: #0d1117; color: #c9d1d9; font-family: 'JetBrains Mono', monospace; padding: 0.75rem 1rem; border-radius: 0.5rem; margin: 1rem 0; overflow-x: auto; }
|
pre { background: #0d1117; color: #c9d1d9; font-family: 'JetBrains Mono', monospace; padding: 0.75rem 1rem; border-radius: 0.5rem; margin: 1rem 0; overflow-x: auto; }
|
||||||
code { color: inherit; padding: 0; background: none; font-size: 0.9rem; }
|
code { color: inherit; padding: 0; background: none; font-size: 0.9rem; }
|
||||||
|
|
||||||
/* Images */
|
/* IMG */
|
||||||
img { max-width: 100%; height: auto; border-radius: 6px; display: block; margin: 1rem 0; }
|
img { max-width: 100%; height: auto; border-radius: 6px; display: block; margin: 1rem 0; }
|
||||||
|
|
||||||
/* Misc */
|
/* MISC */
|
||||||
|
p.is-editor-empty:first-child::before { color: #9ca3af; content: attr(data-placeholder); float: left; height: 0; pointer-events: none; }
|
||||||
mark { background-color: #fef08a; padding: 0.1rem 0.2rem; border-radius: 0.2rem; }
|
mark { background-color: #fef08a; padding: 0.1rem 0.2rem; border-radius: 0.2rem; }
|
||||||
.ProseMirror-selectednode { outline: 2px solid #3b82f6; }
|
.ProseMirror-selectednode { outline: 2px solid #3b82f6; }
|
||||||
p.is-editor-empty:first-child::before { color: #9ca3af; content: attr(data-placeholder); float: left; height: 0; pointer-events: none; }
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
57
frontend/components/wiki/WikiPageList.vue
Normal file
57
frontend/components/wiki/WikiPageList.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-xl overflow-hidden min-w-[12rem] flex flex-col p-1 gap-0.5">
|
||||||
|
<template v-if="items.length">
|
||||||
|
<button
|
||||||
|
v-for="(item, index) in items"
|
||||||
|
:key="item.id"
|
||||||
|
class="flex items-center gap-2 px-2 py-1.5 text-sm rounded text-left transition-colors w-full"
|
||||||
|
:class="{ 'bg-primary-50 dark:bg-primary-900/20 text-primary-700 dark:text-primary-400': index === selectedIndex, 'hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200': index !== selectedIndex }"
|
||||||
|
@click="selectItem(index)"
|
||||||
|
>
|
||||||
|
<UIcon :name="item.isFolder ? 'i-heroicons-folder' : 'i-heroicons-document-text'" class="w-4 h-4 text-gray-400" />
|
||||||
|
<span class="truncate">{{ item.title }}</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<div v-else class="px-3 py-2 text-xs text-gray-400 text-center">
|
||||||
|
Keine Seite gefunden
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
items: { type: Array, required: true },
|
||||||
|
command: { type: Function, required: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedIndex = ref(0)
|
||||||
|
|
||||||
|
// Wenn sich die Liste ändert, Reset Selection
|
||||||
|
watch(() => props.items, () => { selectedIndex.value = 0 })
|
||||||
|
|
||||||
|
function selectItem(index: number) {
|
||||||
|
const item = props.items[index]
|
||||||
|
if (item) props.command({ id: item.id, label: item.title })
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyDown({ event }: { event: KeyboardEvent }) {
|
||||||
|
if (event.key === 'ArrowUp') {
|
||||||
|
selectedIndex.value = (selectedIndex.value + props.items.length - 1) % props.items.length
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (event.key === 'ArrowDown') {
|
||||||
|
selectedIndex.value = (selectedIndex.value + 1) % props.items.length
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
selectItem(selectedIndex.value)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose für Tiptap Render Logic
|
||||||
|
defineExpose({ onKeyDown })
|
||||||
|
</script>
|
||||||
65
frontend/composables/useWikiSuggestion.ts
Normal file
65
frontend/composables/useWikiSuggestion.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { VueRenderer } from '@tiptap/vue-3'
|
||||||
|
import tippy from 'tippy.js'
|
||||||
|
import WikiPageList from '~/components/wiki/WikiPageList.vue'
|
||||||
|
|
||||||
|
// Wir brauchen Zugriff auf die rohen Items aus useWikiTree
|
||||||
|
// Da wir hier ausserhalb von setup() sind, müssen wir den State direkt holen oder übergeben.
|
||||||
|
// Einfacher: Wir nutzen useNuxtApp() oder übergeben die Items in der Config.
|
||||||
|
|
||||||
|
export default {
|
||||||
|
items: ({ query }: { query: string }) => {
|
||||||
|
// 1. Zugriff auf unsere Wiki Items
|
||||||
|
const { items } = useWikiTree()
|
||||||
|
|
||||||
|
// 2. Filtern
|
||||||
|
const allItems = items.value || []
|
||||||
|
return allItems
|
||||||
|
.filter(item => item.title.toLowerCase().includes(query.toLowerCase()))
|
||||||
|
.slice(0, 10) // Max 10 Vorschläge
|
||||||
|
},
|
||||||
|
|
||||||
|
render: () => {
|
||||||
|
let component: any
|
||||||
|
let popup: any
|
||||||
|
|
||||||
|
return {
|
||||||
|
onStart: (props: any) => {
|
||||||
|
component = new VueRenderer(WikiPageList, {
|
||||||
|
props,
|
||||||
|
editor: props.editor,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!props.clientRect) return
|
||||||
|
|
||||||
|
popup = tippy('body', {
|
||||||
|
getReferenceClientRect: props.clientRect,
|
||||||
|
appendTo: () => document.body,
|
||||||
|
content: component.element,
|
||||||
|
showOnCreate: true,
|
||||||
|
interactive: true,
|
||||||
|
trigger: 'manual',
|
||||||
|
placement: 'bottom-start',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onUpdate(props: any) {
|
||||||
|
component.updateProps(props)
|
||||||
|
if (!props.clientRect) return
|
||||||
|
popup[0].setProps({ getReferenceClientRect: props.clientRect })
|
||||||
|
},
|
||||||
|
|
||||||
|
onKeyDown(props: any) {
|
||||||
|
if (props.event.key === 'Escape') {
|
||||||
|
popup[0].hide()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return component.ref?.onKeyDown(props)
|
||||||
|
},
|
||||||
|
|
||||||
|
onExit() {
|
||||||
|
popup[0].destroy()
|
||||||
|
component.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ export default defineNuxtConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
modules: ['@pinia/nuxt', '@nuxt/ui', '@nuxtjs/supabase', "nuxt-editorjs", '@nuxtjs/fontaine', 'nuxt-viewport', 'nuxt-tiptap-editor', '@nuxtjs/leaflet', '@vueuse/nuxt'],
|
modules: ['@pinia/nuxt', '@nuxt/ui', '@nuxtjs/supabase', "nuxt-editorjs", '@nuxtjs/fontaine', 'nuxt-viewport', '@nuxtjs/leaflet', '@vueuse/nuxt'],
|
||||||
|
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
|
||||||
@@ -41,9 +41,42 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
|
resolve: {
|
||||||
|
dedupe: [
|
||||||
|
'vue',
|
||||||
|
'@tiptap/vue-3',
|
||||||
|
'prosemirror-model',
|
||||||
|
'prosemirror-view',
|
||||||
|
'prosemirror-state',
|
||||||
|
'prosemirror-commands',
|
||||||
|
'prosemirror-schema-list',
|
||||||
|
'prosemirror-transform',
|
||||||
|
'prosemirror-history',
|
||||||
|
'prosemirror-gapcursor',
|
||||||
|
'prosemirror-dropcursor',
|
||||||
|
'prosemirror-tables'
|
||||||
|
]
|
||||||
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ["@editorjs/editorjs", "dayjs",'@tiptap/vue-3','@tiptap/extension-code-block-lowlight',
|
include: [
|
||||||
'lowlight',],
|
"@editorjs/editorjs",
|
||||||
|
"dayjs",
|
||||||
|
'@tiptap/vue-3',
|
||||||
|
'@tiptap/extension-code-block-lowlight',
|
||||||
|
'lowlight',
|
||||||
|
'vue',
|
||||||
|
'@tiptap/extension-task-item',
|
||||||
|
'@tiptap/extension-task-list',
|
||||||
|
'@tiptap/extension-table',
|
||||||
|
'@tiptap/extension-mention',
|
||||||
|
'prosemirror-model',
|
||||||
|
'prosemirror-view',
|
||||||
|
'prosemirror-state',
|
||||||
|
'prosemirror-commands',
|
||||||
|
'prosemirror-transform',
|
||||||
|
'tippy.js',
|
||||||
|
'prosemirror-tables',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -55,11 +88,6 @@ export default defineNuxtConfig({
|
|||||||
preference: 'system'
|
preference: 'system'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
tiptap: {
|
|
||||||
prefix: "Tiptap"
|
|
||||||
},
|
|
||||||
|
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
|
|
||||||
public: {
|
public: {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
"@vueuse/core": "^14.1.0",
|
"@vueuse/core": "^14.1.0",
|
||||||
"@vueuse/nuxt": "^14.1.0",
|
"@vueuse/nuxt": "^14.1.0",
|
||||||
"nuxt": "^3.14.1592",
|
"nuxt": "^3.14.1592",
|
||||||
"nuxt-tiptap-editor": "^1.2.0",
|
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5"
|
||||||
},
|
},
|
||||||
@@ -53,6 +52,7 @@
|
|||||||
"@tiptap/extension-highlight": "^3.17.1",
|
"@tiptap/extension-highlight": "^3.17.1",
|
||||||
"@tiptap/extension-image": "^3.17.1",
|
"@tiptap/extension-image": "^3.17.1",
|
||||||
"@tiptap/extension-link": "^3.17.1",
|
"@tiptap/extension-link": "^3.17.1",
|
||||||
|
"@tiptap/extension-mention": "^3.17.1",
|
||||||
"@tiptap/extension-placeholder": "^3.17.1",
|
"@tiptap/extension-placeholder": "^3.17.1",
|
||||||
"@tiptap/extension-table": "^3.17.1",
|
"@tiptap/extension-table": "^3.17.1",
|
||||||
"@tiptap/extension-table-cell": "^3.17.1",
|
"@tiptap/extension-table-cell": "^3.17.1",
|
||||||
@@ -95,6 +95,7 @@
|
|||||||
"sass": "^1.69.7",
|
"sass": "^1.69.7",
|
||||||
"socket.io-client": "^4.7.2",
|
"socket.io-client": "^4.7.2",
|
||||||
"tailwindcss-safe-area-capacitor": "^0.5.1",
|
"tailwindcss-safe-area-capacitor": "^0.5.1",
|
||||||
|
"tippy.js": "^6.3.7",
|
||||||
"uuid": "^11.0.3",
|
"uuid": "^11.0.3",
|
||||||
"uuidv4": "^6.2.13",
|
"uuidv4": "^6.2.13",
|
||||||
"v-calendar": "^3.1.2",
|
"v-calendar": "^3.1.2",
|
||||||
|
|||||||
Reference in New Issue
Block a user