You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

439 lines
14 KiB

6 years ago
5 years ago
5 years ago
5 years ago
6 years ago
  1. <template lang='pug'>
  2. v-dialog(
  3. v-model='isShown'
  4. persistent
  5. width='1000'
  6. :fullscreen='$vuetify.breakpoint.smAndDown'
  7. )
  8. .dialog-header
  9. v-icon(color='white') mdi-tag-text-outline
  10. .subtitle-1.white--text.ml-3 {{$t('editor:props.pageProperties')}}
  11. v-spacer
  12. v-btn.mx-0(
  13. outlined
  14. dark
  15. @click.native='close'
  16. )
  17. v-icon(left) mdi-check
  18. span {{ $t('common:actions.ok') }}
  19. v-card(tile)
  20. v-tabs(color='white', background-color='blue darken-1', dark, centered, v-model='currentTab')
  21. v-tab {{$t('editor:props.info')}}
  22. v-tab {{$t('editor:props.scheduling')}}
  23. v-tab(:disabled='!hasScriptPermission') {{$t('editor:props.scripts')}}
  24. v-tab(disabled) {{$t('editor:props.social')}}
  25. v-tab(:disabled='!hasStylePermission') {{$t('editor:props.styles')}}
  26. v-tab-item(transition='fade-transition', reverse-transition='fade-transition')
  27. v-card-text.pt-5
  28. .overline.pb-5 {{$t('editor:props.pageInfo')}}
  29. v-text-field(
  30. ref='iptTitle'
  31. outlined
  32. :label='$t(`editor:props.title`)'
  33. counter='255'
  34. v-model='title'
  35. )
  36. v-text-field(
  37. outlined
  38. :label='$t(`editor:props.shortDescription`)'
  39. counter='255'
  40. v-model='description'
  41. persistent-hint
  42. :hint='$t(`editor:props.shortDescriptionHint`)'
  43. )
  44. v-divider
  45. v-card-text.grey.pt-5(:class='$vuetify.theme.dark ? `darken-3-d3` : `lighten-5`')
  46. .overline.pb-5 {{$t('editor:props.path')}}
  47. v-container.pa-0(fluid, grid-list-lg)
  48. v-layout(row, wrap)
  49. v-flex(xs12, md2)
  50. v-select(
  51. outlined
  52. :label='$t(`editor:props.locale`)'
  53. suffix='/'
  54. :items='namespaces'
  55. v-model='locale'
  56. hide-details
  57. )
  58. v-flex(xs12, md10)
  59. v-text-field(
  60. outlined
  61. :label='$t(`editor:props.path`)'
  62. append-icon='mdi-folder-search'
  63. v-model='path'
  64. :hint='$t(`editor:props.pathHint`)'
  65. persistent-hint
  66. @click:append='showPathSelector'
  67. )
  68. v-divider
  69. v-card-text.grey.pt-5(:class='$vuetify.theme.dark ? `darken-3-d5` : `lighten-4`')
  70. .overline.pb-5 {{$t('editor:props.categorization')}}
  71. v-chip-group.radius-5.mb-5(column, v-if='tags && tags.length > 0')
  72. v-chip(
  73. v-for='tag of tags'
  74. :key='`tag-` + tag'
  75. close
  76. label
  77. color='teal'
  78. text-color='teal lighten-5'
  79. @click:close='removeTag(tag)'
  80. ) {{tag}}
  81. v-combobox(
  82. :label='$t(`editor:props.tags`)'
  83. outlined
  84. v-model='newTag'
  85. :hint='$t(`editor:props.tagsHint`)'
  86. :items='newTagSuggestions'
  87. :loading='$apollo.queries.newTagSuggestions.loading'
  88. persistent-hint
  89. hide-no-data
  90. :search-input.sync='newTagSearch'
  91. )
  92. v-tab-item(transition='fade-transition', reverse-transition='fade-transition')
  93. v-card-text
  94. .overline {{$t('editor:props.publishState')}}
  95. v-switch(
  96. :label='$t(`editor:props.publishToggle`)'
  97. v-model='isPublished'
  98. color='primary'
  99. :hint='$t(`editor:props.publishToggleHint`)'
  100. persistent-hint
  101. inset
  102. )
  103. v-divider
  104. v-card-text.grey.pt-5(:class='$vuetify.theme.dark ? `darken-3-d3` : `lighten-5`')
  105. v-container.pa-0(fluid, grid-list-lg)
  106. v-row
  107. v-col(cols='6')
  108. v-dialog(
  109. ref='menuPublishStart'
  110. :close-on-content-click='false'
  111. v-model='isPublishStartShown'
  112. :return-value.sync='publishStartDate'
  113. width='460px'
  114. :disabled='!isPublished'
  115. )
  116. template(v-slot:activator='{ on }')
  117. v-text-field(
  118. v-on='on'
  119. :label='$t(`editor:props.publishStart`)'
  120. v-model='publishStartDate'
  121. prepend-icon='mdi-calendar-check'
  122. readonly
  123. outlined
  124. clearable
  125. :hint='$t(`editor:props.publishStartHint`)'
  126. persistent-hint
  127. :disabled='!isPublished'
  128. )
  129. v-date-picker(
  130. v-model='publishStartDate'
  131. :min='(new Date()).toISOString().substring(0, 10)'
  132. color='primary'
  133. reactive
  134. scrollable
  135. landscape
  136. )
  137. v-spacer
  138. v-btn(
  139. text
  140. color='primary'
  141. @click='isPublishStartShown = false'
  142. ) {{$t('common:actions.cancel')}}
  143. v-btn(
  144. text
  145. color='primary'
  146. @click='$refs.menuPublishStart.save(publishStartDate)'
  147. ) {{$t('common:actions.ok')}}
  148. v-col(cols='6')
  149. v-dialog(
  150. ref='menuPublishEnd'
  151. :close-on-content-click='false'
  152. v-model='isPublishEndShown'
  153. :return-value.sync='publishEndDate'
  154. width='460px'
  155. :disabled='!isPublished'
  156. )
  157. template(v-slot:activator='{ on }')
  158. v-text-field(
  159. v-on='on'
  160. :label='$t(`editor:props.publishEnd`)'
  161. v-model='publishEndDate'
  162. prepend-icon='mdi-calendar-remove'
  163. readonly
  164. outlined
  165. clearable
  166. :hint='$t(`editor:props.publishEndHint`)'
  167. persistent-hint
  168. :disabled='!isPublished'
  169. )
  170. v-date-picker(
  171. v-model='publishEndDate'
  172. :min='(new Date()).toISOString().substring(0, 10)'
  173. color='primary'
  174. reactive
  175. scrollable
  176. landscape
  177. )
  178. v-spacer
  179. v-btn(
  180. text
  181. color='primary'
  182. @click='isPublishEndShown = false'
  183. ) {{$t('common:actions.cancel')}}
  184. v-btn(
  185. text
  186. color='primary'
  187. @click='$refs.menuPublishEnd.save(publishEndDate)'
  188. ) {{$t('common:actions.ok')}}
  189. v-tab-item(:transition='false', :reverse-transition='false')
  190. .editor-props-codeeditor-title
  191. .overline {{$t('editor:props.html')}}
  192. .editor-props-codeeditor
  193. textarea(ref='codejs')
  194. .editor-props-codeeditor-hint
  195. .caption {{$t('editor:props.htmlHint')}}
  196. v-tab-item(transition='fade-transition', reverse-transition='fade-transition')
  197. v-card-text
  198. .overline {{$t('editor:props.socialFeatures')}}
  199. v-switch(
  200. :label='$t(`editor:props.allowComments`)'
  201. v-model='isPublished'
  202. color='primary'
  203. :hint='$t(`editor:props.allowCommentsHint`)'
  204. persistent-hint
  205. inset
  206. )
  207. v-switch(
  208. :label='$t(`editor:props.allowRatings`)'
  209. v-model='isPublished'
  210. color='primary'
  211. :hint='$t(`editor:props.allowRatingsHint`)'
  212. persistent-hint
  213. disabled
  214. inset
  215. )
  216. v-switch(
  217. :label='$t(`editor:props.displayAuthor`)'
  218. v-model='isPublished'
  219. color='primary'
  220. :hint='$t(`editor:props.displayAuthorHint`)'
  221. persistent-hint
  222. inset
  223. )
  224. v-switch(
  225. :label='$t(`editor:props.displaySharingBar`)'
  226. v-model='isPublished'
  227. color='primary'
  228. :hint='$t(`editor:props.displaySharingBarHint`)'
  229. persistent-hint
  230. inset
  231. )
  232. v-tab-item(:transition='false', :reverse-transition='false')
  233. .editor-props-codeeditor-title
  234. .overline {{$t('editor:props.css')}}
  235. .editor-props-codeeditor
  236. textarea(ref='codecss')
  237. .editor-props-codeeditor-hint
  238. .caption {{$t('editor:props.cssHint')}}
  239. page-selector(:mode='pageSelectorMode', v-model='pageSelectorShown', :path='path', :locale='locale', :open-handler='setPath')
  240. </template>
  241. <script>
  242. import _ from 'lodash'
  243. import { sync, get } from 'vuex-pathify'
  244. import gql from 'graphql-tag'
  245. import CodeMirror from 'codemirror'
  246. import 'codemirror/lib/codemirror.css'
  247. import 'codemirror/mode/htmlmixed/htmlmixed.js'
  248. import 'codemirror/mode/css/css.js'
  249. /* global siteLangs, siteConfig */
  250. export default {
  251. props: {
  252. value: {
  253. type: Boolean,
  254. default: false
  255. }
  256. },
  257. data () {
  258. return {
  259. isPublishStartShown: false,
  260. isPublishEndShown: false,
  261. pageSelectorShown: false,
  262. namespaces: siteLangs.length ? siteLangs.map(ns => ns.code) : [siteConfig.lang],
  263. newTag: '',
  264. newTagSuggestions: [],
  265. newTagSearch: '',
  266. currentTab: 0,
  267. cm: null
  268. }
  269. },
  270. computed: {
  271. isShown: {
  272. get() { return this.value },
  273. set(val) { this.$emit('input', val) }
  274. },
  275. mode: get('editor/mode'),
  276. title: sync('page/title'),
  277. description: sync('page/description'),
  278. locale: sync('page/locale'),
  279. tags: sync('page/tags'),
  280. path: sync('page/path'),
  281. isPublished: sync('page/isPublished'),
  282. publishStartDate: sync('page/publishStartDate'),
  283. publishEndDate: sync('page/publishEndDate'),
  284. scriptJs: sync('page/scriptJs'),
  285. scriptCss: sync('page/scriptCss'),
  286. hasScriptPermission: get('page/effectivePermissions@pages.script'),
  287. hasStylePermission: get('page/effectivePermissions@pages.style'),
  288. pageSelectorMode () {
  289. return (this.mode === 'create') ? 'create' : 'move'
  290. }
  291. },
  292. watch: {
  293. value (newValue, oldValue) {
  294. if (newValue) {
  295. _.delay(() => {
  296. this.$refs.iptTitle.focus()
  297. }, 500)
  298. }
  299. },
  300. newTag (newValue, oldValue) {
  301. const tagClean = _.trim(newValue || '').toLowerCase()
  302. if (tagClean && tagClean.length > 0) {
  303. if (!_.includes(this.tags, tagClean)) {
  304. this.tags = [...this.tags, tagClean]
  305. }
  306. this.$nextTick(() => {
  307. this.newTag = null
  308. })
  309. }
  310. },
  311. currentTab (newValue, oldValue) {
  312. if (this.cm) {
  313. this.cm.toTextArea()
  314. }
  315. if (newValue === 2) {
  316. this.$nextTick(() => {
  317. setTimeout(() => {
  318. this.loadEditor(this.$refs.codejs, 'html')
  319. }, 100)
  320. })
  321. } else if (newValue === 4) {
  322. this.$nextTick(() => {
  323. setTimeout(() => {
  324. this.loadEditor(this.$refs.codecss, 'css')
  325. }, 100)
  326. })
  327. }
  328. }
  329. },
  330. methods: {
  331. removeTag (tag) {
  332. this.tags = _.without(this.tags, tag)
  333. },
  334. close() {
  335. this.isShown = false
  336. },
  337. showPathSelector() {
  338. this.pageSelectorShown = true
  339. },
  340. setPath({ path, locale }) {
  341. this.locale = locale
  342. this.path = path
  343. },
  344. loadEditor(ref, mode) {
  345. this.cm = CodeMirror.fromTextArea(ref, {
  346. tabSize: 2,
  347. mode: `text/${mode}`,
  348. theme: 'wikijs-dark',
  349. lineNumbers: true,
  350. lineWrapping: true,
  351. line: true,
  352. styleActiveLine: true,
  353. viewportMargin: 50,
  354. inputStyle: 'contenteditable',
  355. direction: 'ltr'
  356. })
  357. switch (mode) {
  358. case 'html':
  359. this.cm.setValue(this.scriptJs)
  360. this.cm.on('change', c => {
  361. this.scriptJs = c.getValue()
  362. })
  363. break
  364. case 'css':
  365. this.cm.setValue(this.scriptCss)
  366. this.cm.on('change', c => {
  367. this.scriptCss = c.getValue()
  368. })
  369. break
  370. default:
  371. console.warn('Invalid Editor Mode')
  372. break
  373. }
  374. this.cm.setSize(null, '500px')
  375. this.$nextTick(() => {
  376. this.cm.refresh()
  377. this.cm.focus()
  378. })
  379. }
  380. },
  381. apollo: {
  382. newTagSuggestions: {
  383. query: gql`
  384. query ($query: String!) {
  385. pages {
  386. searchTags (query: $query)
  387. }
  388. }
  389. `,
  390. variables () {
  391. return {
  392. query: this.newTagSearch
  393. }
  394. },
  395. fetchPolicy: 'cache-first',
  396. update: (data) => _.get(data, 'pages.searchTags', []),
  397. skip () {
  398. return !this.value || _.isEmpty(this.newTagSearch)
  399. },
  400. throttle: 500
  401. }
  402. }
  403. }
  404. </script>
  405. <style lang='scss'>
  406. .editor-props-codeeditor {
  407. background-color: mc('grey', '900');
  408. min-height: 500px;
  409. > textarea {
  410. visibility: hidden;
  411. }
  412. &-title {
  413. background-color: mc('grey', '900');
  414. border-bottom: 1px solid lighten(mc('grey', '900'), 10%);
  415. color: #FFF;
  416. padding: 10px;
  417. }
  418. &-hint {
  419. background-color: mc('grey', '900');
  420. border-top: 1px solid lighten(mc('grey', '900'), 5%);
  421. color: mc('grey', '500');
  422. padding: 5px 10px;
  423. }
  424. }
  425. </style>