feat(editor): link insertion full UI and flow
This commit is contained in:
1 parent
03b39e64f2
commit
6f9ae6207a
16 files changed
+2865
-14
No files matched your search
@@ -1,9 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { t } from '@/i18n'
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref, shallowRef } from 'vue'
|
||||
import type { EditorToolbarItem } from '@nuxt/ui'
|
||||
import type { Editor } from '@tiptap/core'
|
||||
import LinkModal from '@/components/LinkModal.vue'
|
||||
import LinkPopover from '@/components/LinkPopover.vue'
|
||||
import { useArticleLink } from '@/composables/useArticleLink'
|
||||
import { useScrollEdges } from '@/composables/useScrollEdges'
|
||||
import { ArticleLink } from '@/lib/articleLink'
|
||||
import { ArticleIndent } from '@/lib/articleIndent'
|
||||
import { ArticleTextAlign } from '@/lib/articleAlign'
|
||||
import { ArticleImage, findArticleImagePos } from '@/lib/articleImage'
|
||||
@@ -25,10 +30,18 @@ const emit = defineEmits<{
|
||||
'request-remove-image': [imageId: number]
|
||||
}>()
|
||||
|
||||
const editorInstance = ref<Editor | null>(null)
|
||||
const editorInstance = shallowRef<Editor | null>(null)
|
||||
const toolbarRef = ref<{ $el: HTMLElement } | null>(null)
|
||||
const toolbarElement = computed(() => toolbarRef.value?.$el ?? null)
|
||||
const { hiddenBefore, hiddenAfter } = useScrollEdges(toolbarElement)
|
||||
const link = useArticleLink(editorInstance, () => props.disabled)
|
||||
const extensions = [
|
||||
ArticleIndent,
|
||||
ArticleTextAlign,
|
||||
ArticleLink.configure({
|
||||
onRequestDialog: () => link.openDialog(),
|
||||
onRequestPopover: (target) => link.openPopover(target),
|
||||
}),
|
||||
ArticleImage.configure({
|
||||
onRequestRemove: (imageId: number) => emit('request-remove-image', imageId),
|
||||
}),
|
||||
@@ -276,6 +289,7 @@ function buildItems(editor: Editor): EditorToolbarItem[][] {
|
||||
},
|
||||
],
|
||||
[
|
||||
link.toolbarItem(editor),
|
||||
{
|
||||
kind: 'clearFormatting',
|
||||
icon: 'i-lucide-remove-formatting',
|
||||
@@ -342,6 +356,7 @@ defineExpose({
|
||||
v-slot="{ editor }"
|
||||
:model-value="props.modelValue"
|
||||
:extensions="extensions"
|
||||
:starter-kit="{ link: false }"
|
||||
:image="false"
|
||||
content-type="html"
|
||||
:editable="!props.disabled"
|
||||
@@ -351,18 +366,51 @@ defineExpose({
|
||||
>
|
||||
<span class="hidden">{{ captureEditor(editor) }}</span>
|
||||
<UEditorDragHandle :editor="editor" />
|
||||
<UEditorToolbar
|
||||
:editor="editor"
|
||||
:items="buildItems(editor)"
|
||||
layout="fixed"
|
||||
class="col-span-full row-start-1 min-w-0 overflow-x-auto border-b border-default bg-default px-6 py-1.5 [&>*]:shrink-0"
|
||||
/>
|
||||
<div class="relative col-span-full row-start-1 min-w-0">
|
||||
<UEditorToolbar
|
||||
ref="toolbarRef"
|
||||
:editor="editor"
|
||||
:items="buildItems(editor)"
|
||||
layout="fixed"
|
||||
class="article-toolbar min-w-0 overflow-x-auto border-b border-default bg-default px-2 py-1.5 sm:px-6 [&>*]:shrink-0"
|
||||
/>
|
||||
<span
|
||||
v-if="hiddenBefore"
|
||||
data-testid="toolbar-fade-start"
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none absolute inset-y-0 left-0 w-6 bg-linear-to-r from-default to-transparent"
|
||||
/>
|
||||
<span
|
||||
v-if="hiddenAfter"
|
||||
data-testid="toolbar-fade-end"
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none absolute inset-y-0 right-0 w-8 bg-linear-to-l from-default to-transparent"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-start-1 row-start-3">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
<div class="col-start-2 row-span-2 row-start-2 hidden min-h-0 lg:block">
|
||||
<slot name="aside" />
|
||||
</div>
|
||||
<LinkModal
|
||||
v-model:open="link.dialogOpen.value"
|
||||
:mode="link.dialog.value.mode"
|
||||
:text="link.dialog.value.text"
|
||||
:href="link.dialog.value.href"
|
||||
:new-tab="link.dialog.value.newTab"
|
||||
@submit="link.apply"
|
||||
@remove="link.removeFromDialog"
|
||||
/>
|
||||
<LinkPopover
|
||||
v-model:open="link.popoverOpen.value"
|
||||
:href="link.popover.value.href"
|
||||
:text="link.popover.value.text"
|
||||
:reference="link.popover.value.element"
|
||||
@edit="link.editFromPopover"
|
||||
@open-link="link.openFromPopover"
|
||||
@remove="link.removeFromPopover"
|
||||
/>
|
||||
</UEditor>
|
||||
</template>
|
||||
|
||||
@@ -380,6 +428,30 @@ defineExpose({
|
||||
padding: 2.5rem 3rem;
|
||||
outline: none;
|
||||
}
|
||||
.article-editor :deep(.ProseMirror ul),
|
||||
.article-editor :deep(.ProseMirror ol) {
|
||||
padding-left: 1.75rem;
|
||||
}
|
||||
.article-editor :deep(.ProseMirror li) {
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
.article-editor :deep(.ProseMirror h2) {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.article-toolbar {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.article-toolbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 39.999rem) {
|
||||
.article-toolbar :deep(button) {
|
||||
min-width: 2.5rem;
|
||||
min-height: 2.5rem;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
@media (max-width: 47.999rem) {
|
||||
.article-editor :deep(.ProseMirror) {
|
||||
padding: 1.5rem 1rem;
|
||||
|
||||
Reference in new issue
Block a user