forked from opentiny/tiny-vue
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:
parent
b2cd6ab80f
commit
a592dd9f67
|
@ -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>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
import { handleChange, setLink, handleMove, handleClickOutside, removeClickOutside, handleClick, shouldShow } from './index'
|
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 api = ['state', 'setLink', 'handleChange', 'box', 'handleMove', 'handleClickOutside', 'removeClickOutside', 'handleClick', 'shouldShow']
|
||||||
export const renderless = (
|
export const renderless = (
|
||||||
props,
|
props,
|
||||||
{ computed, onMounted, onBeforeUnmount, reactive, ref },
|
{ computed, onMounted, onBeforeUnmount, reactive, ref },
|
||||||
{ vm, emit, parent },
|
{ 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 ydoc = new Y.Doc()
|
||||||
const provider = new WebrtcProvider('tiny-examsple-document', ydoc)
|
const provider = new WebrtcProvider('tiny-examsple-document', ydoc)
|
||||||
|
@ -72,11 +73,18 @@ export const renderless = (
|
||||||
types: ['heading', 'paragraph'],
|
types: ['heading', 'paragraph'],
|
||||||
}),
|
}),
|
||||||
CustomParagraph,
|
CustomParagraph,
|
||||||
|
CodeBlockLowlight.extend({
|
||||||
|
addNodeView() {
|
||||||
|
return VueNodeViewRenderer(Codehighlight(NodeViewContent, nodeViewProps, NodeViewWrapper))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.configure({ lowlight }),
|
||||||
],
|
],
|
||||||
content: 'Example Tesxt',
|
content: 'Example Tesxt',
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
editable: true,
|
editable: true,
|
||||||
injectCSS: false,
|
injectCSS: false,
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const box = ref(null)
|
const box = ref(null)
|
||||||
|
|
|
@ -214,10 +214,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bubble-menu {
|
.tiny-rich-text-editor {
|
||||||
|
.bubble-menu {
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background-color: #f1f3f5;
|
background-color: #f1f3f5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,163 +241,251 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror {
|
.tiny-rich-text-editor {
|
||||||
outline: none !important;
|
.ProseMirror {
|
||||||
}
|
outline: none !important;
|
||||||
|
|
||||||
.ProseMirror {
|
|
||||||
>*+* {
|
|
||||||
margin-top: 0.75em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ul,
|
.ProseMirror {
|
||||||
ol {
|
>*+* {
|
||||||
padding: 0 1rem;
|
margin-top: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
ol {
|
ul,
|
||||||
list-style: auto;
|
ol {
|
||||||
}
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
ul {
|
ol {
|
||||||
list-style: disc;
|
list-style: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
ul {
|
||||||
h2,
|
list-style: disc;
|
||||||
h3,
|
}
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
h1,
|
||||||
background-color: rgba(#616161, 0.1);
|
h2,
|
||||||
color: #616161;
|
h3,
|
||||||
}
|
h4,
|
||||||
|
h5,
|
||||||
pre {
|
h6 {
|
||||||
background: #0D0D0D;
|
line-height: 1.1;
|
||||||
color: #FFF;
|
}
|
||||||
font-family: 'JetBrainsMono', monospace;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
|
|
||||||
code {
|
code {
|
||||||
color: inherit;
|
background-color: rgba(#616161, 0.1);
|
||||||
padding: 0;
|
color: #616161;
|
||||||
background: none;
|
}
|
||||||
font-size: 0.8rem;
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
padding-left: 1rem;
|
||||||
|
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: none;
|
||||||
|
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||||
|
margin: 2rem 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
.ProseMirror {
|
||||||
max-width: 100%;
|
.img-button {
|
||||||
height: auto;
|
resize: both;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
min-width: 1em;
|
||||||
|
border: 2px solid #ced4da;
|
||||||
|
padding: 3px 5px;
|
||||||
|
vertical-align: top;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
>* {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
background-color: #f1f3f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectedCell:after {
|
||||||
|
z-index: 2;
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(200, 200, 255, 0.4);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-resize-handle {
|
||||||
|
position: absolute;
|
||||||
|
right: -2px;
|
||||||
|
top: 0;
|
||||||
|
bottom: -2px;
|
||||||
|
width: 4px;
|
||||||
|
background-color: #adf;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableWrapper {
|
||||||
|
padding: 1rem 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-cursor {
|
||||||
|
cursor: ew-resize;
|
||||||
|
cursor: col-resize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
.ProseMirror {
|
||||||
padding-left: 1rem;
|
|
||||||
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
ul[data-type="taskList"] {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
>label {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
>div {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
.ProseMirror {
|
||||||
border: none;
|
>*+* {
|
||||||
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
margin-top: 0.75em;
|
||||||
margin: 2rem 0;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ProseMirror {
|
.code-block {
|
||||||
.img-button {
|
|
||||||
resize: both;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
table-layout: fixed;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
td,
|
|
||||||
th {
|
|
||||||
min-width: 1em;
|
|
||||||
border: 2px solid #ced4da;
|
|
||||||
padding: 3px 5px;
|
|
||||||
vertical-align: top;
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
>* {
|
select {
|
||||||
margin-bottom: 0;
|
position: absolute;
|
||||||
|
top: .5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
border: 0;
|
||||||
|
background: gray;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
th {
|
pre {
|
||||||
font-weight: bold;
|
background: #0d0d0d;
|
||||||
text-align: left;
|
color: #fff;
|
||||||
background-color: #f1f3f5;
|
font-family: "JetBrainsMono", monospace;
|
||||||
}
|
padding: 0.75rem 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
|
||||||
.selectedCell:after {
|
code {
|
||||||
z-index: 2;
|
color: inherit;
|
||||||
position: absolute;
|
padding: 0;
|
||||||
content: "";
|
background: none;
|
||||||
left: 0;
|
font-size: 0.8rem;
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(200, 200, 255, 0.4);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.column-resize-handle {
|
|
||||||
position: absolute;
|
|
||||||
right: -2px;
|
|
||||||
top: 0;
|
|
||||||
bottom: -2px;
|
|
||||||
width: 4px;
|
|
||||||
background-color: #adf;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableWrapper {
|
|
||||||
padding: 1rem 0;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resize-cursor {
|
|
||||||
cursor: ew-resize;
|
|
||||||
cursor: col-resize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ProseMirror {
|
|
||||||
|
|
||||||
ul[data-type="taskList"] {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
>label {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
user-select: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
>div {
|
.hljs-comment,
|
||||||
flex: 1 1 auto;
|
.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tiptap/core": "^2.1.6",
|
||||||
"@tiptap/extension-collaboration": "^2.0.4",
|
"@tiptap/extension-collaboration": "^2.0.4",
|
||||||
"@tiptap/extension-color": "^2.0.4",
|
"@tiptap/extension-color": "^2.0.4",
|
||||||
"@tiptap/extension-highlight": "^2.0.3",
|
"@tiptap/extension-highlight": "^2.0.3",
|
||||||
|
@ -25,12 +26,16 @@
|
||||||
"@tiptap/extension-task-item": "^2.0.4",
|
"@tiptap/extension-task-item": "^2.0.4",
|
||||||
"@tiptap/extension-task-list": "^2.0.4",
|
"@tiptap/extension-task-list": "^2.0.4",
|
||||||
"@tiptap/extension-text-align": "^2.0.4",
|
"@tiptap/extension-text-align": "^2.0.4",
|
||||||
|
"@tiptap/extension-paragraph": "^2.1.6",
|
||||||
"@tiptap/pm": "^2.0.4",
|
"@tiptap/pm": "^2.0.4",
|
||||||
"@tiptap/starter-kit": "^2.0.4",
|
"@tiptap/starter-kit": "^2.0.4",
|
||||||
"@tiptap/vue-3": "^2.0.4",
|
"@tiptap/vue-3": "^2.0.4",
|
||||||
"y-prosemirror": "^1.2.1",
|
"y-prosemirror": "^1.2.1",
|
||||||
"y-webrtc": "^10.2.5",
|
"y-webrtc": "^10.2.5",
|
||||||
"yjs": "^13.6.7",
|
"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-common": "workspace:~",
|
||||||
"@opentiny/vue-renderless": "workspace:~",
|
"@opentiny/vue-renderless": "workspace:~",
|
||||||
"@opentiny/vue-theme": "workspace:~"
|
"@opentiny/vue-theme": "workspace:~"
|
||||||
|
|
|
@ -314,7 +314,15 @@ import {
|
||||||
iconRichTextUnderline,
|
iconRichTextUnderline,
|
||||||
iconRichTextUndo
|
iconRichTextUndo
|
||||||
} from '@opentiny/vue-icon'
|
} 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 StarterKit from '@tiptap/starter-kit'
|
||||||
// 段落包
|
// 段落包
|
||||||
import Paragraph from '@tiptap/extension-paragraph'
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
@ -343,6 +351,17 @@ import TaskItem from '@tiptap/extension-task-item'
|
||||||
import TaskList from '@tiptap/extension-task-list'
|
import TaskList from '@tiptap/extension-task-list'
|
||||||
// textalign
|
// textalign
|
||||||
import TextAlign from '@tiptap/extension-text-align'
|
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 包
|
// collaboration 包
|
||||||
import Collaboration from '@tiptap/extension-collaboration'
|
import Collaboration from '@tiptap/extension-collaboration'
|
||||||
import * as Y from 'yjs'
|
import * as Y from 'yjs'
|
||||||
|
@ -432,7 +451,13 @@ export default defineComponent({
|
||||||
TaskList,
|
TaskList,
|
||||||
TextAlign,
|
TextAlign,
|
||||||
Paragraph,
|
Paragraph,
|
||||||
mergeAttributes
|
mergeAttributes,
|
||||||
|
CodeBlockLowlight,
|
||||||
|
lowlight,
|
||||||
|
VueNodeViewRenderer,
|
||||||
|
NodeViewContent,
|
||||||
|
nodeViewProps,
|
||||||
|
NodeViewWrapper
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue