RichTextEditor.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. <template>
  2. <div class="editor" v-if="editor">
  3. <menu-bar class="editor__header" :editor="editor"/>
  4. <editor-content :editor="editor"/>
  5. </div>
  6. </template>
  7. <script>
  8. import {Editor, EditorContent, VueNodeViewRenderer} from '@tiptap/vue-2'
  9. import StarterKit from '@tiptap/starter-kit'
  10. import Document from '@tiptap/extension-document'
  11. import Paragraph from '@tiptap/extension-paragraph'
  12. import Highlight from '@tiptap/extension-highlight'
  13. import Text from '@tiptap/extension-text'
  14. import TaskList from '@tiptap/extension-task-list'
  15. import TaskItem from '@tiptap/extension-task-item'
  16. import CharacterCount from '@tiptap/extension-character-count'
  17. import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
  18. import CodeBlockComponent from './CodeBlockComponent'
  19. import MenuBar from './MenuBar.vue'
  20. import lowlight from 'lowlight'
  21. export default {
  22. components: {
  23. EditorContent,
  24. MenuBar,
  25. },
  26. data() {
  27. return {
  28. editor: null,
  29. }
  30. },
  31. props: {
  32. value: {
  33. type: String,
  34. default: '',
  35. },
  36. },
  37. model: {
  38. prop: 'value',
  39. event: 'changeValue'
  40. },
  41. watch: {
  42. value(value) {
  43. // HTML
  44. const isSame = this.editor.getHTML() === value
  45. // JSON
  46. // const isSame = this.editor.getJSON().toString() === value.toString()
  47. if (isSame) {
  48. return
  49. }
  50. this.editor.commands.setContent(this.value, false)
  51. },
  52. },
  53. created() {
  54. const that = this
  55. this.editor = new Editor({
  56. onUpdate({editor}) {
  57. that.$emit('changeValue', editor.getHTML())
  58. },
  59. content: '',
  60. extensions: [
  61. StarterKit,
  62. Document,
  63. Paragraph,
  64. Text,
  65. TaskList,
  66. TaskItem,
  67. CharacterCount,
  68. Highlight,
  69. CodeBlockLowlight
  70. .extend({
  71. addNodeView() {
  72. return VueNodeViewRenderer(CodeBlockComponent)
  73. },
  74. }).configure({lowlight}),
  75. ],
  76. })
  77. },
  78. mounted() {
  79. this.editor.commands.setContent(this.value, false)
  80. },
  81. beforeDestroy() {
  82. this.editor.destroy()
  83. },
  84. }
  85. </script>
  86. <style lang="less">
  87. .ant-affix {
  88. z-index: 8 !important;
  89. }
  90. </style>
  91. <style lang="less" scoped>
  92. .editor {
  93. display: flex;
  94. flex-direction: column;
  95. border-radius: 0.75rem;
  96. @gray: rgba(0, 0, 0, 0.2);
  97. background-color: #FFFFFF;
  98. @media (prefers-color-scheme: dark) {
  99. @gray: #666666;
  100. border: 1px solid @gray;
  101. background-color: #28292c;
  102. &__header {
  103. border-bottom: 1px solid @gray;
  104. }
  105. }
  106. border: 1px solid @gray;
  107. line-height: 1.5 !important;
  108. &__header {
  109. display: flex;
  110. align-items: center;
  111. flex: 0 0 auto;
  112. flex-wrap: wrap;
  113. padding: 0.25rem;
  114. border-bottom: 1px solid @gray;
  115. }
  116. &__content {
  117. padding: 1.25rem 1rem;
  118. flex: 1 1 auto;
  119. overflow-x: hidden;
  120. overflow-y: auto;
  121. -webkit-overflow-scrolling: touch;
  122. }
  123. }
  124. </style>
  125. <style lang="less">
  126. @import "style";
  127. </style>
  128. <style lang="less">
  129. .editor .ProseMirror {
  130. height: 500px;
  131. overflow: scroll;
  132. padding: 15px;
  133. }
  134. </style>