mirror of
https://codeberg.org/Sevichecc/Seigwai.git
synced 2025-04-30 16:09:31 +08:00
Compare commits
10 commits
fdf9a55767
...
c13b7b5b76
Author | SHA1 | Date | |
---|---|---|---|
c13b7b5b76 | |||
c96ddd6ce6 | |||
98837926fe | |||
8adf00715b | |||
deb6fa9702 | |||
3ae0fc76a7 | |||
e34e051f41 | |||
d2f26c1f64 | |||
4cf8033568 | |||
7cc16ebf40 |
13 changed files with 1660 additions and 2049 deletions
3
.husky/pre-commit
Executable file
3
.husky/pre-commit
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
npx --no-install lint-staged
|
42
package.json
42
package.json
|
@ -1,49 +1,47 @@
|
||||||
{
|
{
|
||||||
"name": "seigwai",
|
"name": "seigwai",
|
||||||
"private": true,
|
|
||||||
"version": "0.0.0",
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc && vite build",
|
"build": "vue-tsc && vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@egoist/tailwindcss-icons": "^1.0.7",
|
"@egoist/tailwindcss-icons": "^1.0.7",
|
||||||
"@iconify/json": "^2.2.40",
|
"@iconify/json": "^2.2.40",
|
||||||
"@milkdown/core": "^7.1.0",
|
|
||||||
"@milkdown/ctx": "^7.1.0",
|
|
||||||
"@milkdown/plugin-block": "^7.1.0",
|
|
||||||
"@milkdown/plugin-clipboard": "^7.1.0",
|
|
||||||
"@milkdown/plugin-cursor": "^7.1.0",
|
|
||||||
"@milkdown/plugin-emoji": "^7.1.0",
|
|
||||||
"@milkdown/plugin-history": "^7.1.0",
|
|
||||||
"@milkdown/plugin-indent": "^7.1.0",
|
|
||||||
"@milkdown/plugin-math": "^7.1.0",
|
|
||||||
"@milkdown/plugin-slash": "^7.1.0",
|
|
||||||
"@milkdown/plugin-tooltip": "^7.1.0",
|
|
||||||
"@milkdown/preset-commonmark": "^7.1.0",
|
|
||||||
"@milkdown/preset-gfm": "^7.1.0",
|
|
||||||
"@milkdown/prose": "^7.1.0",
|
|
||||||
"@milkdown/theme-nord": "^7.1.0",
|
|
||||||
"@milkdown/transformer": "^7.1.0",
|
|
||||||
"@milkdown/utils": "^7.1.0",
|
|
||||||
"@milkdown/vue": "^7.1.0",
|
|
||||||
"@prosemirror-adapter/vue": "^0.2.3",
|
"@prosemirror-adapter/vue": "^0.2.3",
|
||||||
|
"@tiptap/extension-bubble-menu": "2.0.0-beta.220",
|
||||||
|
"@tiptap/extension-character-count": "2.0.0-beta.220",
|
||||||
|
"@tiptap/extension-link": "2.0.0-beta.220",
|
||||||
|
"@tiptap/extension-typography": "2.0.0-beta.220",
|
||||||
|
"@tiptap/pm": "2.0.0-beta.220",
|
||||||
|
"@tiptap/starter-kit": "2.0.0-beta.220",
|
||||||
|
"@tiptap/vue-3": "2.0.0-beta.220",
|
||||||
|
"install": "^0.13.0",
|
||||||
"masto": "^5.10.0",
|
"masto": "^5.10.0",
|
||||||
"vue": "^3.2.47"
|
"vue": "^3.2.47"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "^0.37.0",
|
"@antfu/eslint-config": "^0.37.0",
|
||||||
|
"@iconify-json/tabler": "^1.1.68",
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@vitejs/plugin-vue": "^4.1.0",
|
"@vitejs/plugin-vue": "^4.1.0",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"daisyui": "^2.51.5",
|
"daisyui": "^2.51.5",
|
||||||
"eslint": "^8.36.0",
|
"eslint": "^8.36.0",
|
||||||
|
"husky": "^8.0.3",
|
||||||
|
"lint-staged": "^13.2.0",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
"tailwindcss": "^3.2.7",
|
"tailwindcss": "^3.2.7",
|
||||||
"typescript": "^5.0.2",
|
"typescript": "^5.0.2",
|
||||||
"vite": "^4.2.1",
|
"vite": "^4.2.1",
|
||||||
"vue-tsc": "^1.2.0"
|
"vue-tsc": "^1.2.0"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*": "eslint --fix"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
3137
pnpm-lock.yaml
3137
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import EditorWrapper from './components/Milkdown/EditroWrapper.vue'
|
import Editor from './components/Tiptap/Editor.vue'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<EditorWrapper />
|
<Editor />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { BlockProvider } from '@milkdown/plugin-block'
|
|
||||||
import { useInstance } from '@milkdown/vue'
|
|
||||||
import { usePluginViewContext } from '@prosemirror-adapter/vue'
|
|
||||||
import { onUnmounted, ref, watch } from 'vue'
|
|
||||||
|
|
||||||
import type { VNodeRef } from 'vue'
|
|
||||||
const { view } = usePluginViewContext()
|
|
||||||
const [loading, get] = useInstance()
|
|
||||||
|
|
||||||
const divRef = ref<VNodeRef>()
|
|
||||||
|
|
||||||
let tooltipProvider: BlockProvider | undefined
|
|
||||||
|
|
||||||
watch([loading], () => {
|
|
||||||
const editor = get()
|
|
||||||
// eslint-disable-next-line antfu/if-newline
|
|
||||||
if (loading.value || !editor || tooltipProvider) return
|
|
||||||
|
|
||||||
editor.action((ctx) => {
|
|
||||||
tooltipProvider = new BlockProvider({
|
|
||||||
ctx,
|
|
||||||
content: divRef.value as any,
|
|
||||||
})
|
|
||||||
|
|
||||||
tooltipProvider.update(view.value)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
watch([view], () => {
|
|
||||||
tooltipProvider?.update(view.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
tooltipProvider?.destroy()
|
|
||||||
tooltipProvider = undefined
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
ref="divRef"
|
|
||||||
className="w-6 bg-slate-200 rounded hover:bg-slate-300 cursor-grab"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
strokeWidth="{1.5}"
|
|
||||||
stroke="currentColor"
|
|
||||||
className="w-6 h-6"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
d="M12 6.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 12.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 18.75a.75.75 0 110-1.5.75.75 0 010 1.5z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
|
@ -1,67 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { Editor, defaultValueCtx, rootCtx } from '@milkdown/core'
|
|
||||||
import { commonmark } from '@milkdown/preset-commonmark'
|
|
||||||
import { nord } from '@milkdown/theme-nord'
|
|
||||||
import { history } from '@milkdown/plugin-history'
|
|
||||||
import { math } from '@milkdown/plugin-math'
|
|
||||||
import { Milkdown, useEditor } from '@milkdown/vue'
|
|
||||||
import { tooltipFactory } from '@milkdown/plugin-tooltip'
|
|
||||||
import { usePluginViewFactory } from '@prosemirror-adapter/vue'
|
|
||||||
import { gfm } from '@milkdown/preset-gfm'
|
|
||||||
import { clipboard } from '@milkdown/plugin-clipboard'
|
|
||||||
import { emoji } from '@milkdown/plugin-emoji'
|
|
||||||
import { block } from '@milkdown/plugin-block'
|
|
||||||
import { cursor } from '@milkdown/plugin-cursor'
|
|
||||||
|
|
||||||
import Tooltip from './Tooltip.vue'
|
|
||||||
import Slash from './Slash.vue'
|
|
||||||
import Block from './Block.vue'
|
|
||||||
|
|
||||||
const tooltip = tooltipFactory('Text')
|
|
||||||
const slash = tooltipFactory('Text')
|
|
||||||
|
|
||||||
const markdown = `# Milkdown Vue Commonmark
|
|
||||||
> You're scared of a world where you're needed.
|
|
||||||
|
|
||||||
This is a demo for using Milkdown with **Vue**.`
|
|
||||||
|
|
||||||
const pluginViewFactory = usePluginViewFactory()
|
|
||||||
|
|
||||||
useEditor((root) => {
|
|
||||||
return Editor.make()
|
|
||||||
.config(nord)
|
|
||||||
.config((ctx) => {
|
|
||||||
ctx.set(rootCtx, root)
|
|
||||||
ctx.set(defaultValueCtx, markdown)
|
|
||||||
ctx.set(tooltip.key, {
|
|
||||||
view: pluginViewFactory({
|
|
||||||
component: Tooltip,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
ctx.set(slash.key, {
|
|
||||||
view: pluginViewFactory({
|
|
||||||
component: Slash,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
ctx.set(block.key, {
|
|
||||||
view: pluginViewFactory({
|
|
||||||
component: Block,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.use(commonmark)
|
|
||||||
.use(tooltip)
|
|
||||||
.use(slash)
|
|
||||||
.use(history)
|
|
||||||
.use(math)
|
|
||||||
.use(gfm)
|
|
||||||
.use(clipboard)
|
|
||||||
.use(emoji)
|
|
||||||
.use(block)
|
|
||||||
.use(cursor)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Milkdown />
|
|
||||||
</template>
|
|
|
@ -1,13 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { MilkdownProvider } from '@milkdown/vue'
|
|
||||||
import { ProsemirrorAdapterProvider } from '@prosemirror-adapter/vue'
|
|
||||||
import Editor from './Editor.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<MilkdownProvider>
|
|
||||||
<ProsemirrorAdapterProvider>
|
|
||||||
<Editor />
|
|
||||||
</ProsemirrorAdapterProvider>
|
|
||||||
</MilkdownProvider>
|
|
||||||
</template>
|
|
|
@ -1,106 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
|
||||||
import { useInstance } from '@milkdown/vue'
|
|
||||||
import { usePluginViewContext } from '@prosemirror-adapter/vue'
|
|
||||||
import { editorViewCtx } from '@milkdown/core'
|
|
||||||
import { SlashProvider } from '@milkdown/plugin-slash'
|
|
||||||
import { callCommand } from '@milkdown/utils'
|
|
||||||
|
|
||||||
import type { VNodeRef } from 'vue'
|
|
||||||
import type { CmdKey } from '@milkdown/core'
|
|
||||||
import {
|
|
||||||
createCodeBlockCommand,
|
|
||||||
insertHrCommand,
|
|
||||||
wrapInBlockquoteCommand,
|
|
||||||
wrapInBulletListCommand,
|
|
||||||
wrapInHeadingCommand,
|
|
||||||
wrapInOrderedListCommand,
|
|
||||||
} from '@milkdown/preset-commonmark'
|
|
||||||
|
|
||||||
const [loading, get] = useInstance()
|
|
||||||
|
|
||||||
const call = <T>(command: CmdKey<T>, payload?: T) => {
|
|
||||||
return get()!.action((ctx) => {
|
|
||||||
const view = ctx.get(editorViewCtx)
|
|
||||||
const { dispatch, state } = view
|
|
||||||
const { tr, selection } = state
|
|
||||||
const { from } = selection
|
|
||||||
dispatch(tr.deleteRange(from - 1, from))
|
|
||||||
return callCommand(command, payload)(ctx)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let tooltipProvider: SlashProvider
|
|
||||||
|
|
||||||
const { view, prevState } = usePluginViewContext()
|
|
||||||
const divRef = ref<VNodeRef>()
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
tooltipProvider = new SlashProvider({
|
|
||||||
content: divRef.value as any,
|
|
||||||
})
|
|
||||||
|
|
||||||
tooltipProvider.update(view.value, prevState.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
watch([view, prevState], () => {
|
|
||||||
tooltipProvider?.update(view.value, prevState.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
tooltipProvider.destroy()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="loading" ref="divRef">
|
|
||||||
<button
|
|
||||||
class="text-gray-600 bg-slate-200 px-2 py-1 rounded-lg hover:bg-slate-300 border hover:text-gray-900"
|
|
||||||
@click.prevent="call(createCodeBlockCommand.key)"
|
|
||||||
>
|
|
||||||
Code Block
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="text-gray-600 bg-slate-200 px-2 py-1 rounded-lg hover:bg-slate-300 border hover:text-gray-900"
|
|
||||||
@click.prevent="call(wrapInHeadingCommand.key, 1)"
|
|
||||||
>
|
|
||||||
Heading1
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="text-gray-600 bg-slate-200 px-2 py-1 rounded-lg hover:bg-slate-300 border hover:text-gray-900"
|
|
||||||
@click.prevent="call(wrapInHeadingCommand.key, 2)"
|
|
||||||
>
|
|
||||||
Heading2
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="text-gray-600 bg-slate-200 px-2 py-1 rounded-lg hover:bg-slate-300 border hover:text-gray-900"
|
|
||||||
@click.prevent="call(wrapInHeadingCommand.key, 3)"
|
|
||||||
>
|
|
||||||
Heading3
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="text-gray-600 bg-slate-200 px-2 py-1 rounded-lg hover:bg-slate-300 border hover:text-gray-900"
|
|
||||||
@click.prevent="call(wrapInBulletListCommand.key)"
|
|
||||||
>
|
|
||||||
BulletList
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="text-gray-600 bg-slate-200 px-2 py-1 rounded-lg hover:bg-slate-300 border hover:text-gray-900"
|
|
||||||
@click.prevent="call(wrapInOrderedListCommand.key)"
|
|
||||||
>
|
|
||||||
OrderList
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="text-gray-600 bg-slate-200 px-2 py-1 rounded-lg hover:bg-slate-300 border hover:text-gray-900"
|
|
||||||
@click.prevent="call(wrapInBlockquoteCommand.key)"
|
|
||||||
>
|
|
||||||
Quote
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="text-gray-600 bg-slate-200 px-2 py-1 rounded-lg hover:bg-slate-300 border hover:text-gray-900"
|
|
||||||
@click.prevent="call(insertHrCommand.key)"
|
|
||||||
>
|
|
||||||
Hr
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
|
@ -1,87 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useInstance } from '@milkdown/vue'
|
|
||||||
import { TooltipProvider } from '@milkdown/plugin-tooltip'
|
|
||||||
import { usePluginViewContext } from '@prosemirror-adapter/vue'
|
|
||||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
|
||||||
import type { CmdKey } from '@milkdown/core'
|
|
||||||
import type { VNodeRef } from 'vue'
|
|
||||||
import { callCommand } from '@milkdown/utils'
|
|
||||||
import {
|
|
||||||
toggleEmphasisCommand,
|
|
||||||
toggleInlineCodeCommand,
|
|
||||||
toggleStrongCommand,
|
|
||||||
wrapInBlockquoteCommand,
|
|
||||||
} from '@milkdown/preset-commonmark'
|
|
||||||
|
|
||||||
import { toggleStrikethroughCommand } from '@milkdown/preset-gfm'
|
|
||||||
import LinkWidge from './LinkWidge.vue'
|
|
||||||
|
|
||||||
const [loading, get] = useInstance()
|
|
||||||
|
|
||||||
const call = <T>(command: CmdKey<T>, payload?: T) => {
|
|
||||||
return get()?.action(callCommand(command, payload))
|
|
||||||
}
|
|
||||||
|
|
||||||
const { view, prevState } = usePluginViewContext()
|
|
||||||
|
|
||||||
const divRef = ref<VNodeRef>()
|
|
||||||
|
|
||||||
let tooltipProvider: TooltipProvider
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
tooltipProvider = new TooltipProvider({
|
|
||||||
content: divRef.value as any,
|
|
||||||
})
|
|
||||||
|
|
||||||
tooltipProvider.update(view.value, prevState.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
watch([view, prevState], () => {
|
|
||||||
tooltipProvider?.update(view.value, prevState.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
tooltipProvider.destroy()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="loading" ref="divRef">
|
|
||||||
<div class="flex text-gray-700 bg-slate-50 border rounded-md grass">
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-ghost"
|
|
||||||
@click.prevent="call(toggleStrongCommand.key)"
|
|
||||||
>
|
|
||||||
B
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-ghost i-mingcute-code-line"
|
|
||||||
@click.prevent="call(toggleInlineCodeCommand.key)"
|
|
||||||
></button>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-ghost"
|
|
||||||
@click.prevent="call(toggleEmphasisCommand.key)"
|
|
||||||
>
|
|
||||||
Italic
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-ghost"
|
|
||||||
@click.prevent="call(wrapInBlockquoteCommand.key)"
|
|
||||||
>
|
|
||||||
Quote
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-ghost"
|
|
||||||
@click.prevent="call(toggleStrikethroughCommand.key)"
|
|
||||||
>
|
|
||||||
StrikeThrough
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-ghost"
|
|
||||||
@click.prevent="call(toggleStrikethroughCommand.key)"
|
|
||||||
>
|
|
||||||
Link
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
125
src/components/Tiptap/BubbleMenu.vue
Normal file
125
src/components/Tiptap/BubbleMenu.vue
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Editor } from '@tiptap/vue-3'
|
||||||
|
import { BubbleMenu } from '@tiptap/vue-3'
|
||||||
|
import { nextTick, ref } from 'vue'
|
||||||
|
|
||||||
|
const { editor } = defineProps<{ editor: Editor }>()
|
||||||
|
|
||||||
|
const url = ref('')
|
||||||
|
const showUrlInput = ref(false)
|
||||||
|
const inputUrl = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
|
const openLinkInput = () => {
|
||||||
|
showUrlInput.value = true
|
||||||
|
nextTick(() =>
|
||||||
|
inputUrl.value?.focus(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setLink = () => {
|
||||||
|
const previousUrl = editor.getAttributes('link').href
|
||||||
|
|
||||||
|
if (previousUrl)
|
||||||
|
inputUrl.value = previousUrl
|
||||||
|
|
||||||
|
// cancelled
|
||||||
|
if (url.value === null)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (url.value === '') {
|
||||||
|
editor.chain().focus().extendMarkRange('link').unsetAllMarks().run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.focus()
|
||||||
|
.extendMarkRange('link')
|
||||||
|
.setLink({ href: url.value })
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<BubbleMenu
|
||||||
|
:editor="editor"
|
||||||
|
:tippy-options="{ duration: 50 }"
|
||||||
|
class="flex text-gray-700 bg-white grass rounded-md p-[2px] shadow-xl border-slate-100 border"
|
||||||
|
>
|
||||||
|
<div v-show="!showUrlInput">
|
||||||
|
<button
|
||||||
|
class="menu-btn"
|
||||||
|
:class="{ 'btn-active': editor.isActive('bold') }"
|
||||||
|
@click="editor.chain().focus().toggleBold().run()"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
editor.isActive('bold') ? 'i-tabler-bold-off' : 'i-tabler-bold',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="menu-btn"
|
||||||
|
:class="{ 'btn-active': editor.isActive('italic') }"
|
||||||
|
@click="editor.chain().focus().toggleItalic().run()"
|
||||||
|
>
|
||||||
|
<span class="i-tabler-italic" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="menu-btn"
|
||||||
|
:class="{ 'btn-active': editor.isActive('strike') }"
|
||||||
|
@click="editor.chain().focus().toggleStrike().run()"
|
||||||
|
>
|
||||||
|
<span class="i-tabler-strikethrough" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="menu-btn"
|
||||||
|
:class="{ 'btn-active': editor.isActive('code') }"
|
||||||
|
@click="editor.chain().focus().toggleCode().run()"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
editor.isActive('code') ? 'i-tabler-code-off' : 'i-tabler-code',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="menu-btn"
|
||||||
|
:class="{ 'btn-active': editor.isActive('blockquote') }"
|
||||||
|
@click="editor.chain().focus().toggleBlockquote().run()"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
editor.isActive('blockquote') ? 'i-tabler-quote-off' : 'i-tabler-quote',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:class="{ 'btn-active': editor.isActive('link') }"
|
||||||
|
class="menu-btn"
|
||||||
|
@click="openLinkInput"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
editor.isActive('link') ? 'i-tabler-unlink' : 'i-tabler-link',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-show="showUrlInput"
|
||||||
|
class="input-group input-group-sm border-slate-300 border-1"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
ref="inputUrl"
|
||||||
|
v-model.trim="url"
|
||||||
|
class="input input-sm focus:outline-none"
|
||||||
|
placeholder="Add Link to text"
|
||||||
|
@blur="showUrlInput = false"
|
||||||
|
>
|
||||||
|
<button class="btn btn-sm btn-square" @click="setLink">
|
||||||
|
<span class="i-tabler-link p-2" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</BubbleMenu>
|
||||||
|
</template>
|
35
src/components/Tiptap/Editor.vue
Normal file
35
src/components/Tiptap/Editor.vue
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { EditorContent, useEditor } from '@tiptap/vue-3'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import Typography from '@tiptap/extension-typography'
|
||||||
|
import Link from '@tiptap/extension-link'
|
||||||
|
import BubbleMenu from './BubbleMenu.vue'
|
||||||
|
|
||||||
|
const editor = useEditor({
|
||||||
|
content: `<p>
|
||||||
|
Wow, this editor has support for links to the whole <a href="https://en.wikipedia.org/wiki/World_Wide_Web">world wide web</a>. We tested a lot of URLs and I think you can add *every URL* you want. Isn’t that cool? Let’s try <a href="https://statamic.com/">another one!</a> Yep, seems to work.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
By default every link will get a <code>rel="noopener noreferrer nofollow"</code> attribute. It’s configurable though.
|
||||||
|
</p>`,
|
||||||
|
extensions: [
|
||||||
|
StarterKit,
|
||||||
|
Typography,
|
||||||
|
Link.configure({
|
||||||
|
openOnClick: false,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
editable: true,
|
||||||
|
autofocus: true,
|
||||||
|
editorProps: {
|
||||||
|
attributes: {
|
||||||
|
class: 'prose',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<BubbleMenu v-if="editor" :editor="editor" />
|
||||||
|
<EditorContent :editor="editor" />
|
||||||
|
</template>
|
|
@ -9,3 +9,31 @@
|
||||||
.editor {
|
.editor {
|
||||||
@apply mx-auto;
|
@apply mx-auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-ghost {
|
||||||
|
@apply hover:bg-slate-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-active{
|
||||||
|
@apply bg-slate-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn {
|
||||||
|
@apply btn btn-ghost btn-sm rounded-md p-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn > span {
|
||||||
|
@apply w-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-btn.btn-active {
|
||||||
|
@apply bg-slate-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group{
|
||||||
|
@apply h-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #68CEF8;
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ module.exports = {
|
||||||
require('@tailwindcss/typography'),
|
require('@tailwindcss/typography'),
|
||||||
require('daisyui'),
|
require('daisyui'),
|
||||||
iconsPlugin({
|
iconsPlugin({
|
||||||
collections: getIconCollections(['mdi', 'lucide']),
|
collections: getIconCollections(['tabler']),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue