editor.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  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.radius-7(color='blue darken-3', dark)
  43. v-card-text.text-xs-center.py-4
  44. .subheading Which editor do you want to use for this page?
  45. v-container(grid-list-lg, fluid)
  46. v-layout(row, wrap, justify-center)
  47. v-flex(xs4)
  48. v-card.radius-7(
  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.radius-7(
  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.radius-7.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. .caption.blue--text.text--lighten-2 This cannot be changed later.
  75. v-snackbar(
  76. :color='notification.style'
  77. bottom,
  78. right,
  79. multi-line,
  80. v-model='notificationState'
  81. )
  82. .text-xs-left
  83. v-icon.mr-3(dark) {{ notification.icon }}
  84. span {{ notification.message }}
  85. </template>
  86. <script>
  87. import _ from 'lodash'
  88. import { get, sync } from 'vuex-pathify'
  89. import { AtomSpinner } from 'epic-spinners'
  90. import createPageMutation from 'gql/editor/create.gql'
  91. import updatePageMutation from 'gql/editor/update.gql'
  92. import editorStore from '@/store/editor'
  93. /* global WIKI */
  94. WIKI.$store.registerModule('editor', editorStore)
  95. export default {
  96. components: {
  97. AtomSpinner,
  98. editorCode: () => import(/* webpackChunkName: "editor-code", webpackMode: "lazy" */ './editor/editor-code.vue'),
  99. editorMarkdown: () => import(/* webpackChunkName: "editor-markdown", webpackMode: "lazy" */ './editor/editor-markdown.vue'),
  100. editorWysiwyg: () => import(/* webpackChunkName: "editor-wysiwyg", webpackMode: "lazy" */ './editor/editor-wysiwyg.vue'),
  101. editorModalProperties: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-properties.vue')
  102. },
  103. props: {
  104. locale: {
  105. type: String,
  106. default: 'en'
  107. },
  108. path: {
  109. type: String,
  110. default: 'home'
  111. },
  112. title: {
  113. type: String,
  114. default: 'Untitled Page'
  115. },
  116. description: {
  117. type: String,
  118. default: ''
  119. },
  120. tags: {
  121. type: Array,
  122. default: () => ([])
  123. },
  124. isPublished: {
  125. type: Boolean,
  126. default: false
  127. },
  128. initEditor: {
  129. type: String,
  130. default: null
  131. },
  132. initMode: {
  133. type: String,
  134. default: 'create'
  135. },
  136. initContent: {
  137. type: String,
  138. default: null
  139. },
  140. pageId: {
  141. type: Number,
  142. default: 0
  143. }
  144. },
  145. data() {
  146. return {
  147. currentModal: '',
  148. currentEditor: '',
  149. dialogProgress: false,
  150. dialogEditorSelector: false
  151. }
  152. },
  153. computed: {
  154. mode: get('editor/mode'),
  155. notification: get('notification'),
  156. notificationState: sync('notification@isActive')
  157. },
  158. created() {
  159. this.$store.commit('page/SET_ID', this.pageId)
  160. this.$store.commit('page/SET_DESCRIPTION', this.description)
  161. this.$store.commit('page/SET_IS_PUBLISHED', this.isPublished)
  162. this.$store.commit('page/SET_LOCALE', this.locale)
  163. this.$store.commit('page/SET_PATH', this.path)
  164. this.$store.commit('page/SET_TAGS', this.tags)
  165. this.$store.commit('page/SET_TITLE', this.title)
  166. },
  167. mounted() {
  168. this.$store.set('editor/mode', this.initMode || 'create')
  169. this.$store.set('editor/content', this.initContent ? window.atob(this.initContent) : '# Header\n\nYour content here')
  170. if (this.mode === 'create') {
  171. _.delay(() => {
  172. this.dialogEditorSelector = true
  173. }, 500)
  174. } else {
  175. this.selectEditor(this.initEditor || 'markdown')
  176. }
  177. },
  178. methods: {
  179. selectEditor(name) {
  180. this.currentEditor = `editor${_.startCase(name)}`
  181. this.dialogEditorSelector = false
  182. if (this.mode === 'create') {
  183. _.delay(() => {
  184. this.openModal('properties')
  185. }, 500)
  186. }
  187. },
  188. openModal(name) {
  189. this.currentModal = `editorModal${_.startCase(name)}`
  190. },
  191. closeModal() {
  192. _.delay(() => {
  193. this.currentModal = ``
  194. }, 500)
  195. },
  196. showProgressDialog(textKey) {
  197. this.dialogProgress = true
  198. },
  199. hideProgressDialog() {
  200. this.dialogProgress = false
  201. },
  202. async save() {
  203. this.showProgressDialog('saving')
  204. try {
  205. if (this.$store.get('editor/mode') === 'create') {
  206. // --------------------------------------------
  207. // -> CREATE PAGE
  208. // --------------------------------------------
  209. let resp = await this.$apollo.mutate({
  210. mutation: createPageMutation,
  211. variables: {
  212. content: this.$store.get('editor/content'),
  213. description: this.$store.get('page/description'),
  214. editor: 'markdown',
  215. locale: this.$store.get('page/locale'),
  216. isPrivate: false,
  217. isPublished: this.$store.get('page/isPublished'),
  218. path: this.$store.get('page/path'),
  219. publishEndDate: this.$store.get('page/publishEndDate') || '',
  220. publishStartDate: this.$store.get('page/publishStartDate') || '',
  221. tags: this.$store.get('page/tags'),
  222. title: this.$store.get('page/title')
  223. }
  224. })
  225. resp = _.get(resp, 'data.pages.create', {})
  226. if (_.get(resp, 'responseResult.succeeded')) {
  227. this.$store.commit('showNotification', {
  228. message: this.$t('editor:save.success'),
  229. style: 'success',
  230. icon: 'check'
  231. })
  232. this.$store.set('editor/id', _.get(resp, 'page.id'))
  233. this.$store.set('editor/mode', 'update')
  234. } else {
  235. throw new Error(_.get(resp, 'responseResult.message'))
  236. }
  237. } else {
  238. // --------------------------------------------
  239. // -> UPDATE EXISTING PAGE
  240. // --------------------------------------------
  241. let resp = await this.$apollo.mutate({
  242. mutation: updatePageMutation,
  243. variables: {
  244. id: this.$store.get('page/id'),
  245. content: this.$store.get('editor/content'),
  246. description: this.$store.get('page/description'),
  247. editor: 'markdown',
  248. locale: this.$store.get('page/locale'),
  249. isPrivate: false,
  250. isPublished: this.$store.get('page/isPublished'),
  251. path: this.$store.get('page/path'),
  252. publishEndDate: this.$store.get('page/publishEndDate') || '',
  253. publishStartDate: this.$store.get('page/publishStartDate') || '',
  254. tags: this.$store.get('page/tags'),
  255. title: this.$store.get('page/title')
  256. }
  257. })
  258. resp = _.get(resp, 'data.pages.update', {})
  259. if (_.get(resp, 'responseResult.succeeded')) {
  260. this.$store.commit('showNotification', {
  261. message: this.$t('editor:save.success'),
  262. style: 'success',
  263. icon: 'check'
  264. })
  265. } else {
  266. throw new Error(_.get(resp, 'responseResult.message'))
  267. }
  268. }
  269. } catch (err) {
  270. this.$store.commit('showNotification', {
  271. message: err.message,
  272. style: 'error',
  273. icon: 'warning'
  274. })
  275. }
  276. this.hideProgressDialog()
  277. }
  278. }
  279. }
  280. </script>
  281. <style lang='scss'>
  282. .editor {
  283. background-color: mc('grey', '900');
  284. min-height: 100vh;
  285. }
  286. .atom-spinner.is-inline {
  287. display: inline-block;
  288. }
  289. </style>