index.vue 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. <template>
  2. <div class="editor editor-squished">
  3. <basic-menu :editor="editor">
  4. <template #saveButton>
  5. <button
  6. @click="emitUpdate"
  7. :disabled="isSaving"
  8. class="button is-success button-save">
  9. Save
  10. </button>
  11. </template>
  12. </basic-menu>
  13. <bubble-menu :editor="editor" />
  14. <editor-content
  15. class="editor__content"
  16. :editor="editor"
  17. />
  18. </div>
  19. </template>
  20. <script>
  21. import { Editor, EditorContent } from "tiptap"
  22. import BubbleMenu from '~/components/editor/BubbleMenu'
  23. import BasicMenu from '~/components/editor/BasicMenu'
  24. import {
  25. Heading,
  26. Bold,
  27. Code,
  28. Italic,
  29. Strike,
  30. Underline,
  31. History,
  32. Blockquote,
  33. HorizontalRule,
  34. OrderedList,
  35. BulletList,
  36. ListItem,
  37. CodeBlockHighlight,
  38. Placeholder
  39. } from 'tiptap-extensions'
  40. import Title from '~/components/editor/components/Title'
  41. import Subtitle from '~/components/editor/components/Subtitle'
  42. import Doc from '~/components/editor/components/Doc'
  43. import javascript from 'highlight.js/lib/languages/javascript'
  44. import css from 'highlight.js/lib/languages/css'
  45. export default {
  46. components: {
  47. EditorContent,
  48. BubbleMenu,
  49. BasicMenu
  50. },
  51. props: {
  52. isSaving: {
  53. required: false,
  54. default: false
  55. }
  56. },
  57. data(){
  58. return {
  59. editor: null
  60. }
  61. },
  62. // This is called only on client (in browser)
  63. mounted(){
  64. this.editor = new Editor({
  65. extensions: [
  66. new Doc(),
  67. new Title(),
  68. new Subtitle(),
  69. new Placeholder({
  70. showOnlyCurrent: false,
  71. emptyNodeText: node => {
  72. if (node.type.name === 'title') {
  73. return 'Inspirational Title'
  74. }
  75. if (node.type.name === 'subtitle') {
  76. return 'Some catchy subtitle'
  77. }
  78. return 'Write your story...'
  79. }
  80. }),
  81. new Heading({ levels: [1, 2, 3]}),
  82. new Bold(),
  83. new Code(),
  84. new Italic(),
  85. new Strike(),
  86. new Underline(),
  87. new History(),
  88. new Blockquote(),
  89. new HorizontalRule(),
  90. new OrderedList(),
  91. new BulletList(),
  92. new ListItem(),
  93. new CodeBlockHighlight({
  94. languages: {
  95. javascript,
  96. css,
  97. }
  98. })
  99. ]
  100. })
  101. this.$emit('editorMounted', this.setInitialContent)
  102. },
  103. beforeDestroy(){
  104. // Always destroy your editor instance when it's no longer needed
  105. this.editor && this.editor.destroy()
  106. },
  107. methods: {
  108. emitUpdate() {
  109. const content = this.getContent()
  110. this.$emit('editorUpdated', content)
  111. },
  112. getContent() {
  113. const html = this.editor.getHTML()
  114. const title = this.getNodeValueByName('title')
  115. const subtitle = this.getNodeValueByName('subtitle')
  116. return { html, title, subtitle}
  117. },
  118. getNodeValueByName(name) {
  119. const docContent = this.editor.state.doc.content
  120. const nodes = docContent.content
  121. const node = nodes.find(n => n.type.name === name)
  122. if (!node) return ''
  123. return node.textContent
  124. },
  125. setInitialContent(content) {
  126. this.editor.setContent(content)
  127. }
  128. }
  129. }
  130. </script>
  131. <style lang="scss" scoped>
  132. .button-save {
  133. float: right;
  134. background-color: #23d160;
  135. &:hover {
  136. background-color: #2bc76c;
  137. }
  138. &:disabled {
  139. cursor: not-allowed;
  140. }
  141. }
  142. </style>