feat(rich-text-editor): support code highlight (#440)

* feat(rich-text-editor): support code highlight

* feat(rich-text-editor): support code highlight

* style(rich-text-editor): code style adjust

* style(rich-text-editor): code style adjust

---------

Co-authored-by: 常浩-BJS21325 <changhao01@youdao>
This commit is contained in:
Caesar-ch 2023-08-24 15:29:26 +08:00 committed by GitHub
parent b2cd6ab80f
commit a592dd9f67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 308 additions and 139 deletions

View File

@ -0,0 +1,41 @@
export default function (NodeViewContent, nodeViewProps, NodeViewWrapper) {
return {
name: 'CodeHighlight',
components: {
NodeViewWrapper,
NodeViewContent,
},
props: nodeViewProps,
data() {
return {
languages: this.extension.options.lowlight.listLanguages(),
}
},
computed: {
selectedLanguage: {
get() {
return this.node.attrs.language
},
set(language) {
this.updateAttributes({ language })
},
},
},
render() {
return (
<NodeViewWrapper class="code-block">
<select contenteditable="false" v-model={this.selectedLanguage}>
<option value="null">
auto
</option>
<option disabled>
</option>
{this.languages.map((item, index) => <option value={item} key={index}> {item} </option>)}
</select>
<pre><code><NodeViewContent /></code></pre>
</NodeViewWrapper>
)
},
}
}

View File

@ -1,10 +1,11 @@
import { handleChange, setLink, handleMove, handleClickOutside, removeClickOutside, handleClick, shouldShow } from './index'
import Codehighlight from './code-highlight'
export const api = ['state', 'setLink', 'handleChange', 'box', 'handleMove', 'handleClickOutside', 'removeClickOutside', 'handleClick', 'shouldShow']
export const renderless = (
props,
{ computed, onMounted, onBeforeUnmount, reactive, ref },
{ vm, emit, parent },
{ useEditor, Collaboration, Y, WebrtcProvider, StarterKit, Table, TableCell, TableHeader, TableRow, Color, TextStyle, Image, Highlight, Link, Underline, Subscript, Superscript, TaskItem, TaskList, TextAlign, Paragraph, mergeAttributes }
{ useEditor, Collaboration, Y, WebrtcProvider, StarterKit, Table, TableCell, TableHeader, TableRow, Color, TextStyle, Image, Highlight, Link, Underline, Subscript, Superscript, TaskItem, TaskList, TextAlign, Paragraph, mergeAttributes, CodeBlockLowlight, lowlight, VueNodeViewRenderer, NodeViewContent, nodeViewProps, NodeViewWrapper }
) => {
const ydoc = new Y.Doc()
const provider = new WebrtcProvider('tiny-examsple-document', ydoc)
@ -72,11 +73,18 @@ export const renderless = (
types: ['heading', 'paragraph'],
}),
CustomParagraph,
CodeBlockLowlight.extend({
addNodeView() {
return VueNodeViewRenderer(Codehighlight(NodeViewContent, nodeViewProps, NodeViewWrapper))
},
})
.configure({ lowlight }),
],
content: 'Example Tesxt',
autofocus: true,
editable: true,
injectCSS: false,
})
const box = ref(null)

View File

@ -214,12 +214,14 @@
}
}
.tiny-rich-text-editor {
.bubble-menu {
button {
background-color: #f1f3f5;
}
}
}
// 富文本编辑器的主题区域,需要对高度进行调整,达到融为一体
.small-box {
@ -239,6 +241,7 @@
}
}
.tiny-rich-text-editor {
.ProseMirror {
outline: none !important;
}
@ -400,3 +403,90 @@
}
}
}
.ProseMirror {
>*+* {
margin-top: 0.75em;
}
.code-block {
position: relative;
select {
position: absolute;
top: .5rem;
right: 0.5rem;
border: 0;
background: gray;
border-radius: 4px;
}
}
pre {
background: #0d0d0d;
color: #fff;
font-family: "JetBrainsMono", monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
code {
color: inherit;
padding: 0;
background: none;
font-size: 0.8rem;
}
.hljs-comment,
.hljs-quote {
color: #616161;
}
.hljs-variable,
.hljs-template-variable,
.hljs-attribute,
.hljs-tag,
.hljs-name,
.hljs-regexp,
.hljs-link,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #f98181;
}
.hljs-number,
.hljs-meta,
.hljs-built_in,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params {
color: #fbbc88;
}
.hljs-string,
.hljs-symbol,
.hljs-bullet {
color: #b9f18d;
}
.hljs-title,
.hljs-section {
color: #faf594;
}
.hljs-keyword,
.hljs-selector-tag {
color: #70cff8;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: 700;
}
}
}
}

View File

@ -9,6 +9,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"@tiptap/core": "^2.1.6",
"@tiptap/extension-collaboration": "^2.0.4",
"@tiptap/extension-color": "^2.0.4",
"@tiptap/extension-highlight": "^2.0.3",
@ -25,12 +26,16 @@
"@tiptap/extension-task-item": "^2.0.4",
"@tiptap/extension-task-list": "^2.0.4",
"@tiptap/extension-text-align": "^2.0.4",
"@tiptap/extension-paragraph": "^2.1.6",
"@tiptap/pm": "^2.0.4",
"@tiptap/starter-kit": "^2.0.4",
"@tiptap/vue-3": "^2.0.4",
"y-prosemirror": "^1.2.1",
"y-webrtc": "^10.2.5",
"yjs": "^13.6.7",
"@tiptap/extension-code-block-lowlight": "^2.0.4",
"highlight.js": "^11.8.0",
"lowlight": "^2.9.0",
"@opentiny/vue-common": "workspace:~",
"@opentiny/vue-renderless": "workspace:~",
"@opentiny/vue-theme": "workspace:~"

View File

@ -314,7 +314,15 @@ import {
iconRichTextUnderline,
iconRichTextUndo
} from '@opentiny/vue-icon'
import { useEditor, EditorContent, BubbleMenu, VueNodeViewRenderer } from '@tiptap/vue-3'
import {
useEditor,
EditorContent,
BubbleMenu,
VueNodeViewRenderer,
NodeViewContent,
nodeViewProps,
NodeViewWrapper
} from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
//
import Paragraph from '@tiptap/extension-paragraph'
@ -343,6 +351,17 @@ import TaskItem from '@tiptap/extension-task-item'
import TaskList from '@tiptap/extension-task-list'
// textalign
import TextAlign from '@tiptap/extension-text-align'
// code high light
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
import css from 'highlight.js/lib/languages/css'
import js from 'highlight.js/lib/languages/javascript'
import ts from 'highlight.js/lib/languages/typescript'
import html from 'highlight.js/lib/languages/xml'
import { lowlight } from 'lowlight'
lowlight.registerLanguage('html', html)
lowlight.registerLanguage('css', css)
lowlight.registerLanguage('js', js)
lowlight.registerLanguage('ts', ts)
// collaboration
import Collaboration from '@tiptap/extension-collaboration'
import * as Y from 'yjs'
@ -432,7 +451,13 @@ export default defineComponent({
TaskList,
TextAlign,
Paragraph,
mergeAttributes
mergeAttributes,
CodeBlockLowlight,
lowlight,
VueNodeViewRenderer,
NodeViewContent,
nodeViewProps,
NodeViewWrapper
}
})
}