feat: remove milkdown, write rich text editor by myself

This commit is contained in:
KUN1007 2023-09-01 17:20:43 +08:00
parent 5d7c8cb8de
commit 42bc64aa03
12 changed files with 25 additions and 314 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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)
},
},
})
})

View file

@ -0,0 +1,11 @@
<script setup lang='ts'>
</script>
<template>
</template>
<style lang='scss' scoped>
</style>

View file

@ -0,0 +1,11 @@
<script setup lang='ts'>
</script>
<template>
</template>
<style lang='scss' scoped>
</style>

View file

@ -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>

View file

@ -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>
<!-- 内容区的底部 -->