editor.vue 10 KB

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