editor.vue 9.9 KB

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