feat: remove milkdown, write rich text editor by myself
This commit is contained in:
parent
5d7c8cb8de
commit
42bc64aa03
|
@ -1,94 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { defaultValueCtx, Editor, rootCtx } from '@milkdown/core'
|
||||
import { Milkdown, useEditor } from '@milkdown/vue'
|
||||
import { commonmark } from '@milkdown/preset-commonmark'
|
||||
import MilkdownMenu from './MilkdownMenu.vue'
|
||||
|
||||
/**
|
||||
* 编辑器插件
|
||||
*/
|
||||
|
||||
// import { slash } from '@milkdown/plugin-slash'
|
||||
// Ctrl + Z
|
||||
import { history, historyKeymap } from '@milkdown/plugin-history'
|
||||
// 数学公式插件
|
||||
import { math } from '@milkdown/plugin-math'
|
||||
// 表情
|
||||
import { emoji } from '@milkdown/plugin-emoji'
|
||||
// 代码语法高亮插件
|
||||
import { prism } from '@milkdown/plugin-prism'
|
||||
import { tooltipFactory } from '@milkdown/plugin-tooltip'
|
||||
import { indent } from '@milkdown/plugin-indent'
|
||||
import { trailing } from '@milkdown/plugin-trailing'
|
||||
import { upload } from '@milkdown/plugin-upload'
|
||||
import { cursor } from '@milkdown/plugin-cursor'
|
||||
import { clipboard } from '@milkdown/plugin-clipboard'
|
||||
import { listener, listenerCtx } from '@milkdown/plugin-listener'
|
||||
|
||||
// prosemirror
|
||||
import { usePluginViewFactory } from '@prosemirror-adapter/vue'
|
||||
const pluginViewFactory = usePluginViewFactory()
|
||||
|
||||
// Tooltip
|
||||
import Tooltip from './MilkdownTooltip.vue'
|
||||
const tooltip = tooltipFactory('Text')
|
||||
|
||||
// code highlight
|
||||
import { milkShiki } from './shiki'
|
||||
|
||||
const markdown = `啊这可海星`
|
||||
|
||||
useEditor((root) =>
|
||||
Editor.make()
|
||||
.config((ctx) => {
|
||||
const listener = ctx.get(listenerCtx)
|
||||
|
||||
// listener.markdownUpdated((ctx, markdown, prevMarkdown) => {
|
||||
// if (markdown !== prevMarkdown) {
|
||||
// YourMarkdownUpdater(markdown)
|
||||
// }
|
||||
// })
|
||||
|
||||
// Ctrl + z
|
||||
ctx.set(historyKeymap.key, {
|
||||
// Remap to one shortcut.
|
||||
Undo: 'Mod-z',
|
||||
// Remap to multiple shortcuts.
|
||||
Redo: ['Mod-y', 'Shift-Mod-z'],
|
||||
})
|
||||
ctx.set(rootCtx, root)
|
||||
ctx.set(defaultValueCtx, markdown)
|
||||
ctx.set(tooltip.key, {
|
||||
view: pluginViewFactory({
|
||||
component: Tooltip,
|
||||
}),
|
||||
})
|
||||
})
|
||||
.use(listener)
|
||||
.use(milkShiki)
|
||||
.use(commonmark)
|
||||
.use(history)
|
||||
.use(math)
|
||||
.use(emoji)
|
||||
.use(prism)
|
||||
.use(tooltip)
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="editor">
|
||||
<MilkdownMenu />
|
||||
<div class="text-area">
|
||||
<Milkdown />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.text-area {
|
||||
min-height: 200px;
|
||||
margin-top: 10px;
|
||||
white-space: pre-wrap;
|
||||
border: 1px solid var(--kungalgame-red-4);
|
||||
}
|
||||
</style>
|
|
@ -1,64 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
turnIntoTextCommand,
|
||||
wrapInBlockquoteCommand,
|
||||
wrapInHeadingCommand,
|
||||
downgradeHeadingCommand,
|
||||
createCodeBlockCommand,
|
||||
insertHardbreakCommand,
|
||||
insertHrCommand,
|
||||
insertImageCommand,
|
||||
updateImageCommand,
|
||||
wrapInOrderedListCommand,
|
||||
wrapInBulletListCommand,
|
||||
sinkListItemCommand,
|
||||
splitListItemCommand,
|
||||
liftListItemCommand,
|
||||
liftFirstListItemCommand,
|
||||
toggleEmphasisCommand,
|
||||
toggleInlineCodeCommand,
|
||||
toggleStrongCommand,
|
||||
toggleLinkCommand,
|
||||
updateLinkCommand,
|
||||
} from '@milkdown/preset-commonmark'
|
||||
// 编辑器 Ctrl + z 插件
|
||||
import { redoCommand, undoCommand } from '@milkdown/plugin-history'
|
||||
|
||||
import {
|
||||
insertTableCommand,
|
||||
toggleStrikethroughCommand,
|
||||
} from '@milkdown/preset-gfm'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="menu">
|
||||
<div icon="undo" @click="undoCommand">undo</div>
|
||||
<div icon="redo" @click="redoCommand">redo</div>
|
||||
<div icon="format_bold" @click="toggleStrongCommand">format_bold</div>
|
||||
<div icon="format_italic" @click="toggleEmphasisCommand">format_italic</div>
|
||||
<div icon="format_strikethrough" @click="toggleStrikethroughCommand">
|
||||
format_strikethrough
|
||||
</div>
|
||||
<div icon="table" @click="insertTableCommand">table</div>
|
||||
<div icon="format_list_bulleted" @click="wrapInBulletListCommand">
|
||||
format_list_bulleted
|
||||
</div>
|
||||
<div icon="format_list_numbered" @click="wrapInOrderedListCommand">
|
||||
format_list_numbered
|
||||
</div>
|
||||
<div icon="format_quote" @click="wrapInBlockquoteCommand">format_quote</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu {
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
div {
|
||||
margin: 10px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: var(--kungalgame-blue-4);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,13 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { MilkdownProvider } from '@milkdown/vue'
|
||||
import MilkdownEditor from './MilkdownEditor.vue'
|
||||
import { ProsemirrorAdapterProvider } from '@prosemirror-adapter/vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MilkdownProvider>
|
||||
<ProsemirrorAdapterProvider>
|
||||
<MilkdownEditor />
|
||||
</ProsemirrorAdapterProvider>
|
||||
</MilkdownProvider>
|
||||
</template>
|
|
@ -1,52 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { TooltipProvider } from '@milkdown/plugin-tooltip'
|
||||
import { toggleStrongCommand } from '@milkdown/preset-commonmark'
|
||||
import { callCommand } from '@milkdown/utils'
|
||||
import { useInstance } from '@milkdown/vue'
|
||||
import { usePluginViewContext } from '@prosemirror-adapter/vue'
|
||||
import { onMounted, onUnmounted, ref, VNodeRef, watch } from 'vue'
|
||||
|
||||
const { view, prevState } = usePluginViewContext()
|
||||
const [loading, get] = useInstance()
|
||||
|
||||
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()
|
||||
})
|
||||
|
||||
const toggleBold = (e: Event) => {
|
||||
if (loading.value) return
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
get()!.action(callCommand(toggleStrongCommand.key))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="divRef">
|
||||
<button class="tooltip" @mousedown="toggleBold">Bold</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tooltip {
|
||||
padding: 7px;
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
}
|
||||
</style>
|
|
@ -1,88 +0,0 @@
|
|||
import { getHighlighter, Highlighter } from 'shiki'
|
||||
import { $proseAsync } from '@milkdown/utils'
|
||||
import { Node } from '@milkdown/prose/model'
|
||||
import { Plugin, PluginKey } from '@milkdown/prose/state'
|
||||
import { Decoration, DecorationSet } from '@milkdown/prose/view'
|
||||
import { findChildren } from '@milkdown/prose'
|
||||
import { codeBlockSchema } from '@milkdown/preset-commonmark'
|
||||
|
||||
function getDecorations(doc: Node, highlighter: Highlighter) {
|
||||
const decorations: Decoration[] = []
|
||||
|
||||
const children = findChildren((node) => node.type === codeBlockSchema.type())(
|
||||
doc
|
||||
)
|
||||
|
||||
children.forEach(async (block) => {
|
||||
let from = block.pos + 1
|
||||
const { language } = block.node.attrs
|
||||
if (!language) return
|
||||
const nodes = highlighter
|
||||
.codeToThemedTokens(block.node.textContent, language)
|
||||
.map((token) =>
|
||||
token.map(({ content, color }) => ({
|
||||
content,
|
||||
color,
|
||||
}))
|
||||
)
|
||||
nodes.forEach((block) => {
|
||||
block.forEach((node) => {
|
||||
const to = from + node.content.length
|
||||
const decoration = Decoration.inline(from, to, {
|
||||
style: `color: ${node.color}`,
|
||||
})
|
||||
decorations.push(decoration)
|
||||
from = to
|
||||
})
|
||||
from += 1
|
||||
})
|
||||
})
|
||||
|
||||
return DecorationSet.create(doc, decorations)
|
||||
}
|
||||
|
||||
export const milkShiki = $proseAsync(async () => {
|
||||
const highlighter = await getHighlighter({
|
||||
theme: 'nord',
|
||||
langs: ['javascript', 'tsx', 'markdown'],
|
||||
})
|
||||
const key = new PluginKey('shiki')
|
||||
|
||||
return new Plugin({
|
||||
key,
|
||||
state: {
|
||||
init: (_, { doc }) => getDecorations(doc, highlighter),
|
||||
apply: (tr, value, oldState, newState) => {
|
||||
const codeBlockType = codeBlockSchema.type()
|
||||
const isNodeName =
|
||||
newState.selection.$head.parent.type === codeBlockType
|
||||
const isPreviousNodeName =
|
||||
oldState.selection.$head.parent.type === codeBlockType
|
||||
const oldNode = findChildren((node) => node.type === codeBlockType)(
|
||||
oldState.doc
|
||||
)
|
||||
const newNode = findChildren((node) => node.type === codeBlockType)(
|
||||
newState.doc
|
||||
)
|
||||
|
||||
const codeBlockChanged =
|
||||
tr.docChanged &&
|
||||
(isNodeName ||
|
||||
isPreviousNodeName ||
|
||||
oldNode.length !== newNode.length ||
|
||||
oldNode[0]?.node.attrs.language !== newNode[0]?.node.attrs.language)
|
||||
|
||||
if (codeBlockChanged) {
|
||||
return getDecorations(tr.doc, highlighter)
|
||||
}
|
||||
|
||||
return value.map(tr.mapping, tr.doc)
|
||||
},
|
||||
},
|
||||
props: {
|
||||
decorations(state) {
|
||||
return key.getState(state)
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
11
src/components/editor/KUNGalgameEditor.vue
Normal file
11
src/components/editor/KUNGalgameEditor.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<script setup lang='ts'>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
</style>
|
11
src/components/editor/KUNGalgameEditormenu.vue
Normal file
11
src/components/editor/KUNGalgameEditormenu.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<script setup lang='ts'>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
</style>
|
|
@ -5,7 +5,7 @@ import 'animate.css'
|
|||
|
||||
import { currBackground } from '@/hooks/useBackgroundPicture'
|
||||
|
||||
import KUNGalgameTopBar from '@/components/TopBar/KUNGalgameTopBar.vue'
|
||||
import KUNGalgameTopBar from '@/components/top-bar/KUNGalgameTopBar.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { onBeforeMount, ref } from 'vue'
|
||||
import Milkdown from '@/components/Milkdown/MilkdownProvider.vue'
|
||||
import KUNGalgameEditor from '@/components/editor/KUNGalgameEditor.vue'
|
||||
import Tags from './components/Tags.vue'
|
||||
import Footer from './components/Footer.vue'
|
||||
import KUNGalgameFooter from '@/components/KUNGalgameFooter.vue'
|
||||
|
@ -68,7 +68,7 @@ const handelInput = () => {
|
|||
</div>
|
||||
</div>
|
||||
<!-- 编辑器 -->
|
||||
<Milkdown />
|
||||
<KUNGalgameEditor />
|
||||
</div>
|
||||
|
||||
<!-- 内容区的底部 -->
|
||||
|
|
Loading…
Reference in a new issue