editor.vue 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. <template lang="pug">
  2. .editor
  3. nav-header(dense)
  4. template(slot='actions')
  5. v-btn(
  6. outline
  7. color='green'
  8. @click.native.stop='save'
  9. :class='{ "is-icon": $vuetify.breakpoint.mdAndDown }'
  10. )
  11. v-icon(color='green', :left='$vuetify.breakpoint.lgAndUp') check
  12. span.white--text(v-if='$vuetify.breakpoint.lgAndUp') {{ mode === 'create' ? $t('common:actions.create') : $t('common:actions.save') }}
  13. v-btn.mx-0(
  14. outline
  15. color='red'
  16. :class='{ "is-icon": $vuetify.breakpoint.mdAndDown }'
  17. )
  18. v-icon(color='red', :left='$vuetify.breakpoint.lgAndUp') close
  19. span.white--text(v-if='$vuetify.breakpoint.lgAndUp') {{ $t('common:actions.discard') }}
  20. v-btn(
  21. outline
  22. color='blue'
  23. @click.native.stop='openModal(`properties`)'
  24. :class='{ "is-icon": $vuetify.breakpoint.mdAndDown }'
  25. )
  26. v-icon(color='blue', :left='$vuetify.breakpoint.lgAndUp') sort_by_alpha
  27. span.white--text(v-if='$vuetify.breakpoint.lgAndUp') {{ $t('editor:page') }}
  28. v-content
  29. component(:is='currentEditor')
  30. component(:is='currentModal')
  31. v-dialog(v-model='dialogProgress', persistent, max-width='350')
  32. v-card(color='blue darken-3', dark)
  33. v-card-text.text-xs-center.py-4
  34. atom-spinner.is-inline(
  35. :animation-duration='1000'
  36. :size='60'
  37. color='#FFF'
  38. )
  39. .subheading {{ $t('editor:save.processing') }}
  40. .caption.blue--text.text--lighten-3 {{ $t('editor:save.pleaseWait') }}
  41. v-dialog(v-model='dialogEditorSelector', persistent, max-width='550')
  42. v-card(color='blue darken-3', dark)
  43. v-card-text.text-xs-center.py-4
  44. .subheading Which editor do you want to use?
  45. v-container(grid-list-lg, fluid)
  46. v-layout(row, wrap, justify-center)
  47. v-flex(xs4)
  48. v-card(
  49. hover
  50. light
  51. ripple
  52. )
  53. v-card-text.text-xs-center(@click='selectEditor("code")')
  54. v-icon(large, color='primary') code
  55. .body-2.mt-2 Code
  56. v-flex(xs4)
  57. v-card(
  58. hover
  59. light
  60. ripple
  61. )
  62. v-card-text.text-xs-center(@click='selectEditor("markdown")')
  63. v-icon(large, color='primary') list_alt
  64. .body-2.mt-2 Markdown
  65. v-flex(xs4)
  66. v-card.grey(
  67. hover
  68. light
  69. ripple
  70. )
  71. v-card-text.text-xs-center(@click='selectEditor("wysiwyg")')
  72. v-icon(large, color='grey darken-1') web
  73. .body-2.mt-2.grey--text.text--darken-2 Visual Builder
  74. v-snackbar(
  75. :color='notification.style'
  76. bottom,
  77. right,
  78. multi-line,
  79. v-model='notificationState'
  80. )
  81. .text-xs-left
  82. v-icon.mr-3(dark) {{ notification.icon }}
  83. span {{ notification.message }}
  84. </template>
  85. <script>
  86. import _ from 'lodash'
  87. import { get, sync } from 'vuex-pathify'
  88. import { AtomSpinner } from 'epic-spinners'
  89. import createPageMutation from 'gql/editor/create.gql'
  90. import updatePageMutation from 'gql/editor/update.gql'
  91. import editorStore from '@/store/editor'
  92. /* global WIKI */
  93. WIKI.$store.registerModule('editor', editorStore)
  94. export default {
  95. components: {
  96. AtomSpinner,
  97. editorCode: () => import(/* webpackChunkName: "editor-code", webpackMode: "lazy" */ './editor/editor-code.vue'),
  98. editorMarkdown: () => import(/* webpackChunkName: "editor-markdown", webpackMode: "lazy" */ './editor/editor-markdown.vue'),
  99. editorWysiwyg: () => import(/* webpackChunkName: "editor-wysiwyg", webpackMode: "lazy" */ './editor/editor-wysiwyg.vue'),
  100. editorModalProperties: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-properties.vue')
  101. },
  102. props: {
  103. locale: {
  104. type: String,
  105. default: 'en'
  106. },
  107. path: {
  108. type: String,
  109. default: 'home'
  110. },
  111. title: {
  112. type: String,
  113. default: 'Untitled Page'
  114. },
  115. description: {
  116. type: String,
  117. default: ''
  118. },
  119. tags: {
  120. type: Array,
  121. default: () => ([])
  122. },
  123. isPublished: {
  124. type: Boolean,
  125. default: false
  126. },
  127. initEditor: {
  128. type: String,
  129. default: null
  130. },
  131. initMode: {
  132. type: String,
  133. default: 'create'
  134. },
  135. initContent: {
  136. type: String,
  137. default: null
  138. }
  139. },
  140. data() {
  141. return {
  142. currentModal: '',
  143. currentEditor: '',
  144. dialogProgress: false,
  145. dialogEditorSelector: false
  146. }
  147. },
  148. computed: {
  149. mode: get('editor/mode'),
  150. notification: get('notification'),
  151. notificationState: sync('notification@isActive')
  152. },
  153. created() {
  154. this.$store.commit('page/SET_DESCRIPTION', this.description)
  155. this.$store.commit('page/SET_IS_PUBLISHED', this.isPublished)
  156. this.$store.commit('page/SET_LOCALE', this.locale)
  157. this.$store.commit('page/SET_PATH', this.path)
  158. this.$store.commit('page/SET_TAGS', this.tags)
  159. this.$store.commit('page/SET_TITLE', this.title)
  160. },
  161. mounted() {
  162. this.$store.set('editor/mode', this.initMode || 'create')
  163. this.$store.set('editor/content', this.initContent ? window.atob(this.initContent) : '# Header\n\nYour content here')
  164. if (this.mode === 'create') {
  165. _.delay(() => {
  166. this.dialogEditorSelector = true
  167. }, 500)
  168. } else {
  169. this.selectEditor(this.initEditor || 'markdown')
  170. }
  171. },
  172. methods: {
  173. selectEditor(name) {
  174. this.currentEditor = `editor${_.startCase(name)}`
  175. this.dialogEditorSelector = false
  176. if (this.mode === 'create') {
  177. _.delay(() => {
  178. this.openModal('properties')
  179. }, 500)
  180. }
  181. },
  182. openModal(name) {
  183. this.currentModal = `editorModal${_.startCase(name)}`
  184. },
  185. closeModal() {
  186. _.delay(() => {
  187. this.currentModal = ``
  188. }, 500)
  189. },
  190. showProgressDialog(textKey) {
  191. this.dialogProgress = true
  192. },
  193. hideProgressDialog() {
  194. this.dialogProgress = false
  195. },
  196. async save() {
  197. this.showProgressDialog('saving')
  198. try {
  199. if (this.$store.get('editor/mode') === 'create') {
  200. // --------------------------------------------
  201. // -> CREATE PAGE
  202. // --------------------------------------------
  203. let resp = await this.$apollo.mutate({
  204. mutation: createPageMutation,
  205. variables: {
  206. content: this.$store.get('editor/content'),
  207. description: this.$store.get('editor/description'),
  208. editor: 'markdown',
  209. locale: this.$store.get('editor/locale'),
  210. isPrivate: false,
  211. isPublished: this.$store.get('editor/isPublished'),
  212. path: this.$store.get('editor/path'),
  213. publishEndDate: this.$store.get('editor/publishEndDate'),
  214. publishStartDate: this.$store.get('editor/publishStartDate'),
  215. tags: this.$store.get('editor/tags'),
  216. title: this.$store.get('editor/title')
  217. }
  218. })
  219. resp = _.get(resp, 'data.pages.create', {})
  220. if (_.get(resp, 'responseResult.succeeded')) {
  221. this.$store.commit('showNotification', {
  222. message: this.$t('editor:save.success'),
  223. style: 'success',
  224. icon: 'check'
  225. })
  226. this.$store.set('editor/id', _.get(resp, 'page.id'))
  227. this.$store.set('editor/mode', 'update')
  228. } else {
  229. throw new Error(_.get(resp, 'responseResult.message'))
  230. }
  231. } else {
  232. // --------------------------------------------
  233. // -> UPDATE EXISTING PAGE
  234. // --------------------------------------------
  235. let resp = await this.$apollo.mutate({
  236. mutation: updatePageMutation,
  237. variables: {
  238. id: this.$store.get('editor/id'),
  239. content: this.$store.get('editor/content'),
  240. description: this.$store.get('editor/description'),
  241. editor: 'markdown',
  242. locale: this.$store.get('editor/locale'),
  243. isPrivate: false,
  244. isPublished: this.$store.get('editor/isPublished'),
  245. path: this.$store.get('editor/path'),
  246. publishEndDate: this.$store.get('editor/publishEndDate'),
  247. publishStartDate: this.$store.get('editor/publishStartDate'),
  248. tags: this.$store.get('editor/tags'),
  249. title: this.$store.get('editor/title')
  250. }
  251. })
  252. resp = _.get(resp, 'data.pages.update', {})
  253. if (_.get(resp, 'responseResult.succeeded')) {
  254. this.$store.commit('showNotification', {
  255. message: this.$t('editor:save.success'),
  256. style: 'success',
  257. icon: 'check'
  258. })
  259. } else {
  260. throw new Error(_.get(resp, 'responseResult.message'))
  261. }
  262. }
  263. } catch (err) {
  264. this.$store.commit('showNotification', {
  265. message: err.message,
  266. style: 'error',
  267. icon: 'warning'
  268. })
  269. }
  270. this.hideProgressDialog()
  271. }
  272. }
  273. }
  274. </script>
  275. <style lang='scss'>
  276. .editor {
  277. background-color: mc('grey', '900');
  278. min-height: 100vh;
  279. }
  280. .atom-spinner.is-inline {
  281. display: inline-block;
  282. }
  283. </style>