88import { Editor , EditorContent } from ' @tiptap/vue-3'
99
1010import { Document } from ' @tiptap/extension-document'
11- import { Paragraph } from ' @tiptap/extension-paragraph'
1211import { Text } from ' @tiptap/extension-text'
1312
13+ const CodeEditorDocument = Document .extend ({
14+ content: ' codeBlock' ,
15+ })
16+
1417export default {
1518 name: ' CodeEditor' ,
1619 components: {
@@ -36,13 +39,23 @@ export default {
3639 watch: {
3740 modelValue (newCode ) {
3841 if (this .editor && newCode !== this .getCurrentCode ()) {
39- this .editor .commands .setContent (this .generateCodeBlock (newCode))
42+ this .editor .commands .setContent (
43+ this .generateCodeBlock (newCode),
44+ false ,
45+ {
46+ preserveWhitespace: ' full' ,
47+ }
48+ )
4049 }
4150 },
4251 language () {
4352 if (this .editor ) {
4453 this .editor .commands .setContent (
45- this .generateCodeBlock (this .getCurrentCode ())
54+ this .generateCodeBlock (this .getCurrentCode ()),
55+ false ,
56+ {
57+ preserveWhitespace: ' full' ,
58+ }
4659 )
4760 }
4861 },
@@ -60,17 +73,44 @@ export default {
6073 lowlight .register (' javascript' , javascript)
6174 lowlight .register (' css' , css)
6275
76+ const LockedCodeBlockLowlight = CodeBlockLowlight .extend ({
77+ addKeyboardShortcuts () {
78+ const parentShortcuts = this .parent ? .() || {}
79+
80+ return {
81+ ... parentShortcuts,
82+ Backspace : () => {
83+ const { empty , $anchor } = this .editor .state .selection
84+
85+ if (! empty || $anchor .parent .type .name !== this .name ) {
86+ return false
87+ }
88+
89+ if ($anchor .pos === 1 || ! $anchor .parent .textContent .length ) {
90+ return true
91+ }
92+
93+ return false
94+ },
95+ }
96+ },
97+ })
98+
6399 this .editor = new Editor ({
64100 extensions: [
65- Document ,
66- Paragraph,
101+ CodeEditorDocument,
67102 Text ,
68- CodeBlockLowlight .configure ({
103+ LockedCodeBlockLowlight .configure ({
69104 lowlight,
105+ exitOnTripleEnter: false ,
106+ exitOnArrowDown: false ,
107+ HTMLAttributes: {
108+ class: ' code-editor__code-wrapper' ,
109+ },
70110 }),
71111 ],
72112 content: this .generateCodeBlock (this .modelValue ),
73- onUpdate : ({ editor } ) => {
113+ onUpdate : () => {
74114 this .$emit (' update:modelValue' , this .getCurrentCode ())
75115 },
76116 })
@@ -80,21 +120,21 @@ export default {
80120 },
81121 methods: {
82122 generateCodeBlock (code ) {
83- return ` <pre class="code-editor__code-wrapper"><code class="code-editor__code code-editor__code--language-${
84- this .language
85- } ">${ this .escapeHtml (code)} </code></pre>`
123+ return {
124+ type: ' doc' ,
125+ content: [
126+ {
127+ type: ' codeBlock' ,
128+ attrs: {
129+ language: this .language ,
130+ },
131+ content: code ? [{ type: ' text' , text: code }] : [],
132+ },
133+ ],
134+ }
86135 },
87136 getCurrentCode () {
88- const codeNode = this .editor ? .getJSON ()? .content ? .[0 ]? .content ? .[0 ]
89- return codeNode? .text || ' '
90- },
91- escapeHtml (string ) {
92- return string
93- .replace (/ &/ g , ' &' )
94- .replace (/ </ g , ' <' )
95- .replace (/ >/ g , ' >' )
96- .replace (/ "/ g , ' "' )
97- .replace (/ '/ g , ' '' )
137+ return this .editor ? .getText () || ' '
98138 },
99139 },
100140}
0 commit comments