feat: markdown editor toolbar + default group rules fix
This commit is contained in:
		| @@ -67,7 +67,8 @@ export default { | |||||||
|     editorModalEditorselect: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-editorselect.vue'), |     editorModalEditorselect: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-editorselect.vue'), | ||||||
|     editorModalProperties: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-properties.vue'), |     editorModalProperties: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-properties.vue'), | ||||||
|     editorModalUnsaved: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-unsaved.vue'), |     editorModalUnsaved: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-unsaved.vue'), | ||||||
|     editorModalMedia: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-media.vue') |     editorModalMedia: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-media.vue'), | ||||||
|  |     editorModalBlocks: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-blocks.vue') | ||||||
|   }, |   }, | ||||||
|   props: { |   props: { | ||||||
|     locale: { |     locale: { | ||||||
|   | |||||||
| @@ -1,91 +1,130 @@ | |||||||
| <template lang='pug'> | <template lang='pug'> | ||||||
|   .editor-markdown |   .editor-markdown | ||||||
|     v-toolbar.editor-markdown-toolbar(dense, color='primary', dark, flat) |     v-toolbar.editor-markdown-toolbar(dense, color='primary', dark, flat) | ||||||
|       v-tooltip(top) |       v-tooltip(bottom, color='primary') | ||||||
|         v-btn(icon, slot='activator').mx-0 |         v-btn(icon, slot='activator', @click='toggleMarkup({ start: `**` })').mx-0 | ||||||
|           v-icon format_bold |           v-icon format_bold | ||||||
|         span Bold |         span Bold | ||||||
|       v-tooltip(top) |       v-tooltip(bottom, color='primary') | ||||||
|         v-btn(icon, slot='activator').mx-0 |         v-btn(icon, slot='activator', @click='toggleMarkup({ start: `*` })').mx-0 | ||||||
|           v-icon format_italic |           v-icon format_italic | ||||||
|         span Italic |         span Italic | ||||||
|       v-tooltip(top) |       v-tooltip(bottom, color='primary') | ||||||
|         v-btn(icon, slot='activator').mx-0 |         v-btn(icon, slot='activator', @click='toggleMarkup({ start: `~~` })').mx-0 | ||||||
|           v-icon format_strikethrough |           v-icon format_strikethrough | ||||||
|         span Strikethrough |         span Strikethrough | ||||||
|       v-menu(offset-y, open-on-hover) |       v-menu(offset-y, open-on-hover) | ||||||
|         v-btn(icon, slot='activator').mx-0 |         v-btn(icon, slot='activator').mx-0 | ||||||
|           v-icon font_download |           v-icon text_fields | ||||||
|         v-list |         v-list.py-0 | ||||||
|           v-list-tile(v-for='(n, idx) in 6', @click='', :key='idx') |           template(v-for='(n, idx) in 6') | ||||||
|  |             v-list-tile(@click='setHeaderLine(n)', :key='idx') | ||||||
|               v-list-tile-action |               v-list-tile-action | ||||||
|               v-icon font_download |                 v-icon(:size='24 - (idx - 1) * 2') title | ||||||
|               v-list-tile-title Heading {{n}} |               v-list-tile-title Heading {{n}} | ||||||
|       v-tooltip(top) |             v-divider(v-if='idx < 5') | ||||||
|  |       v-tooltip(bottom, color='primary') | ||||||
|  |         v-btn(icon, slot='activator', @click='toggleMarkup({ start: `~` })').mx-0 | ||||||
|  |           v-icon vertical_align_bottom | ||||||
|  |         span Subscript | ||||||
|  |       v-tooltip(bottom, color='primary') | ||||||
|  |         v-btn(icon, slot='activator', @click='toggleMarkup({ start: `^` })').mx-0 | ||||||
|  |           v-icon vertical_align_top | ||||||
|  |         span Superscript | ||||||
|  |       v-menu(offset-y, open-on-hover) | ||||||
|         v-btn(icon, slot='activator').mx-0 |         v-btn(icon, slot='activator').mx-0 | ||||||
|           v-icon format_quote |           v-icon format_quote | ||||||
|         span Blockquote |         v-list.py-0 | ||||||
|       v-tooltip(top) |           v-list-tile(@click='insertBeforeEachLine({ content: `> `})') | ||||||
|         v-btn(icon, slot='activator').mx-0 |             v-list-tile-action | ||||||
|  |               v-icon format_quote | ||||||
|  |             v-list-tile-title Blockquote | ||||||
|  |           v-divider | ||||||
|  |           v-list-tile(@click='insertBeforeEachLine({ content: `> `, after: `{.is-info}`})') | ||||||
|  |             v-list-tile-action | ||||||
|  |               v-icon(color='blue') format_quote | ||||||
|  |             v-list-tile-title Info Blockquote | ||||||
|  |           v-divider | ||||||
|  |           v-list-tile(@click='insertBeforeEachLine({ content: `> `, after: `{.is-success}`})') | ||||||
|  |             v-list-tile-action | ||||||
|  |               v-icon(color='success') format_quote | ||||||
|  |             v-list-tile-title Success Blockquote | ||||||
|  |           v-divider | ||||||
|  |           v-list-tile(@click='insertBeforeEachLine({ content: `> `, after: `{.is-warning}`})') | ||||||
|  |             v-list-tile-action | ||||||
|  |               v-icon(color='warning') format_quote | ||||||
|  |             v-list-tile-title Warning Blockquote | ||||||
|  |           v-divider | ||||||
|  |           v-list-tile(@click='insertBeforeEachLine({ content: `> `, after: `{.is-error}`})') | ||||||
|  |             v-list-tile-action | ||||||
|  |               v-icon(color='error') format_quote | ||||||
|  |             v-list-tile-title Error Blockquote | ||||||
|  |           v-divider | ||||||
|  |       v-tooltip(bottom, color='primary') | ||||||
|  |         v-btn(icon, slot='activator', @click='insertBeforeEachLine({ content: `- `})').mx-0 | ||||||
|           v-icon format_list_bulleted |           v-icon format_list_bulleted | ||||||
|         span Unordered List |         span Unordered List | ||||||
|       v-tooltip(top) |       v-tooltip(bottom, color='primary') | ||||||
|         v-btn(icon, slot='activator').mx-0 |         v-btn(icon, slot='activator', @click='insertBeforeEachLine({ content: `1. `})').mx-0 | ||||||
|           v-icon format_list_numbered |           v-icon format_list_numbered | ||||||
|         span Ordered List |         span Ordered List | ||||||
|       v-tooltip(top) |       v-tooltip(bottom, color='primary') | ||||||
|         v-btn(icon, slot='activator').mx-0 |         v-btn(icon, slot='activator', @click='toggleMarkup({ start: "`" })').mx-0 | ||||||
|           v-icon insert_link |  | ||||||
|         span Link |  | ||||||
|       v-tooltip(top) |  | ||||||
|         v-btn(icon, slot='activator').mx-0 |  | ||||||
|           v-icon space_bar |           v-icon space_bar | ||||||
|         span Inline Code |         span Inline Code | ||||||
|       v-tooltip(top) |       v-tooltip(bottom, color='primary') | ||||||
|         v-btn(icon, slot='activator').mx-0 |         v-btn(icon, slot='activator', @click='toggleMarkup({ start: `<kbd>`, end: `</kbd>` })').mx-0 | ||||||
|           v-icon code |           v-icon font_download | ||||||
|         span Code Block |         span Keyboard Key | ||||||
|       v-tooltip(top) |       v-tooltip(bottom, color='primary') | ||||||
|         v-btn(icon, slot='activator').mx-0 |         v-btn(icon, slot='activator', @click='insertAfter({ content: `---`, newLine: true })').mx-0 | ||||||
|           v-icon remove |           v-icon remove | ||||||
|         span Horizontal Bar |         span Horizontal Bar | ||||||
|     .editor-markdown-main |     .editor-markdown-main | ||||||
|       .editor-markdown-sidebar |       .editor-markdown-sidebar | ||||||
|         v-tooltip(right, color='primary') |         v-tooltip(right, color='teal') | ||||||
|           v-btn(icon, slot='activator', dark).mx-0 |           v-btn(icon, slot='activator', dark, disabled).mx-0 | ||||||
|             v-icon link |             v-icon link | ||||||
|           span Insert Link |           span Insert Link | ||||||
|         v-tooltip(right) |         v-tooltip(right, color='teal') | ||||||
|           v-btn(icon, slot='activator', dark, @click='toggleModal(`editorModalMedia`)').mx-0 |           v-btn(icon, slot='activator', dark, @click='toggleModal(`editorModalMedia`)').mx-0 | ||||||
|             v-icon(:color='activeModal === `editorModalMedia` ? `teal` : ``') image |             v-icon(:color='activeModal === `editorModalMedia` ? `teal` : ``') image | ||||||
|           span Insert Image |           span Insert Image | ||||||
|         v-tooltip(right, color='primary') |         v-tooltip(right, color='teal') | ||||||
|           v-btn(icon, slot='activator', dark).mx-0 |           v-btn(icon, slot='activator', dark, @click='toggleModal(`editorModalBlocks`)').mx-0 | ||||||
|  |             v-icon(:color='activeModal === `editorModalBlocks` ? `teal` : ``') dashboard | ||||||
|  |           span Insert Block | ||||||
|  |         v-tooltip(right, color='teal') | ||||||
|  |           v-btn(icon, slot='activator', dark, disabled).mx-0 | ||||||
|             v-icon insert_drive_file |             v-icon insert_drive_file | ||||||
|           span Insert File |           span Insert File | ||||||
|         v-tooltip(right, color='primary') |         v-tooltip(right, color='teal') | ||||||
|           v-btn(icon, slot='activator', dark).mx-0 |           v-btn(icon, slot='activator', dark, disabled).mx-0 | ||||||
|  |             v-icon code | ||||||
|  |           span Insert Code Block | ||||||
|  |         v-tooltip(right, color='teal') | ||||||
|  |           v-btn(icon, slot='activator', dark, disabled).mx-0 | ||||||
|             v-icon play_circle_outline |             v-icon play_circle_outline | ||||||
|           span Insert Video / Audio |           span Insert Video / Audio | ||||||
|         v-tooltip(right, color='primary') |         v-tooltip(right, color='teal') | ||||||
|           v-btn(icon, slot='activator', dark).mx-0 |           v-btn(icon, slot='activator', dark, disabled).mx-0 | ||||||
|             v-icon multiline_chart |             v-icon multiline_chart | ||||||
|           span Insert Diagram |           span Insert Diagram | ||||||
|         v-tooltip(right, color='primary') |         v-tooltip(right, color='teal') | ||||||
|           v-btn(icon, slot='activator', dark).mx-0 |           v-btn(icon, slot='activator', dark, disabled).mx-0 | ||||||
|             v-icon functions |             v-icon functions | ||||||
|           span Insert Math Expression |           span Insert Math Expression | ||||||
|         v-tooltip(right, color='primary') |         v-tooltip(right, color='teal') | ||||||
|           v-btn(icon, slot='activator', dark).mx-0 |           v-btn(icon, slot='activator', dark, disabled).mx-0 | ||||||
|             v-icon border_outer |             v-icon border_outer | ||||||
|           span Table Helper |           span Table Helper | ||||||
|         v-spacer |         v-spacer | ||||||
|         v-tooltip(right, color='primary') |         v-tooltip(right, color='teal') | ||||||
|           v-btn(icon, slot='activator', dark, @click='toggleFullscreen').mx-0 |           v-btn(icon, slot='activator', dark, @click='toggleFullscreen').mx-0 | ||||||
|             v-icon crop_free |             v-icon crop_free | ||||||
|           span Fullscreen Editor |           span Distraction Free Mode | ||||||
|         v-tooltip(right, color='primary') |         v-tooltip(right, color='teal') | ||||||
|           v-btn(icon, slot='activator', dark).mx-0 |           v-btn(icon, slot='activator', dark, disabled).mx-0 | ||||||
|             v-icon help |             v-icon help | ||||||
|           span Markdown Formatting Help |           span Markdown Formatting Help | ||||||
|       .editor-markdown-editor |       .editor-markdown-editor | ||||||
| @@ -273,6 +312,57 @@ export default { | |||||||
|  |  | ||||||
|       // console.info(token) |       // console.info(token) | ||||||
|     }, |     }, | ||||||
|  |     toggleMarkup({ start, end }) { | ||||||
|  |       if (!end) { end = start } | ||||||
|  |       if (!this.cm.doc.somethingSelected()) { | ||||||
|  |         return this.$store.commit('showNotification', { | ||||||
|  |           message: 'You must select something first!', | ||||||
|  |           style: 'warning', | ||||||
|  |           icon: 'warning' | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |       this.cm.doc.replaceSelections(this.cm.doc.getSelections().map(s => start + s + end)) | ||||||
|  |     }, | ||||||
|  |     setHeaderLine(lvl) { | ||||||
|  |       const curLine = this.cm.doc.getCursor('head').line | ||||||
|  |       let lineContent = this.cm.doc.getLine(curLine) | ||||||
|  |       const lineLength = lineContent.length | ||||||
|  |       if (_.startsWith(lineContent, '#')) { | ||||||
|  |         lineContent = lineContent.replace(/^(#+ )/, '') | ||||||
|  |       } | ||||||
|  |       lineContent = _.times(lvl, n => '#').join('') + ` ` + lineContent | ||||||
|  |       this.cm.doc.replaceRange(lineContent, { line: curLine, ch: 0 }, { line: curLine, ch: lineLength }) | ||||||
|  |     }, | ||||||
|  |     insertAfter({ content, newLine }) { | ||||||
|  |       const curLine = this.cm.doc.getCursor('to').line | ||||||
|  |       const lineLength = this.cm.doc.getLine(curLine).length | ||||||
|  |       this.cm.doc.replaceRange(newLine ? `\n${content}\n` : content, { line: curLine, ch: lineLength + 1 }) | ||||||
|  |     }, | ||||||
|  |     insertBeforeEachLine({ content, after }) { | ||||||
|  |       let lines = [] | ||||||
|  |       if (!this.cm.doc.somethingSelected()) { | ||||||
|  |         lines.push(this.cm.doc.getCursor('head').line) | ||||||
|  |       } else { | ||||||
|  |         lines = _.flatten(this.cm.doc.listSelections().map(sl => { | ||||||
|  |           const range = Math.abs(sl.anchor.line - sl.head.line) + 1 | ||||||
|  |           const lowestLine = (sl.anchor.line > sl.head.line) ? sl.head.line : sl.anchor.line | ||||||
|  |           return _.times(range, l => l + lowestLine) | ||||||
|  |         })) | ||||||
|  |       } | ||||||
|  |       lines.forEach(ln => { | ||||||
|  |         let lineContent = this.cm.doc.getLine(ln) | ||||||
|  |         const lineLength = lineContent.length | ||||||
|  |         if (_.startsWith(lineContent, content)) { | ||||||
|  |           lineContent = lineContent.substring(content.length) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         this.cm.doc.replaceRange(content + lineContent, { line: ln, ch: 0 }, { line: ln, ch: lineLength }) | ||||||
|  |       }) | ||||||
|  |       if (after) { | ||||||
|  |         const lastLine = _.last(lines) | ||||||
|  |         this.cm.doc.replaceRange(`\n${after}\n`, { line: lastLine, ch: this.cm.doc.getLine(lastLine).length + 1 }) | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     /** |     /** | ||||||
|      * Update scroll sync |      * Update scroll sync | ||||||
|      */ |      */ | ||||||
|   | |||||||
							
								
								
									
										86
									
								
								client/components/editor/editor-modal-blocks.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								client/components/editor/editor-modal-blocks.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | <template lang='pug'> | ||||||
|  |   v-card.editor-modal-blocks.animated.fadeInLeft(flat, tile) | ||||||
|  |     v-container.pa-3(grid-list-lg, fluid) | ||||||
|  |       v-layout(row, wrap) | ||||||
|  |         v-flex(xs3) | ||||||
|  |           v-card.radius-7(light) | ||||||
|  |             v-card-text | ||||||
|  |               .d-flex | ||||||
|  |                 v-toolbar.radius-7(color='teal lighten-5', dense, flat, height='44') | ||||||
|  |                   .body-2.teal--text Blocks | ||||||
|  |               v-list(two-line) | ||||||
|  |                 template(v-for='(item, idx) of blocks') | ||||||
|  |                   v-list-tile(@click='selectBlock(item)') | ||||||
|  |                     v-list-tile-avatar | ||||||
|  |                       v-avatar.radius-7(color='teal') | ||||||
|  |                         v-icon(dark) dashboard | ||||||
|  |                     v-list-tile-content | ||||||
|  |                       v-list-tile-title.body-2 {{item.title}} | ||||||
|  |                       v-list-tile-sub-title {{item.description}} | ||||||
|  |                     v-list-tile-avatar(v-if='block.key === item.key') | ||||||
|  |                       v-icon.animated.fadeInLeft(color='teal') arrow_forward_ios | ||||||
|  |                   v-divider(v-if='idx < blocks.length - 1') | ||||||
|  |  | ||||||
|  |         v-flex(xs3) | ||||||
|  |           v-card.radius-7.animated.fadeInLeft(light, v-if='block.key') | ||||||
|  |             v-card-text | ||||||
|  |               v-toolbar.radius-7(color='teal lighten-5', dense, flat) | ||||||
|  |                 v-icon.mr-3(color='teal') dashboard | ||||||
|  |                 .body-2.teal--text {{block.title}} | ||||||
|  |               .d-flex.mt-3 | ||||||
|  |                 v-toolbar.radius-7(flat, color='grey lighten-4', dense, height='44') | ||||||
|  |                   .body-2 Coming soon | ||||||
|  |                 v-btn.ml-3.my-0.mr-0.radius-7(color='teal', large, @click='', disabled) | ||||||
|  |                   v-icon(left) save_alt | ||||||
|  |                   span Insert | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | import _ from 'lodash' | ||||||
|  | import { sync } from 'vuex-pathify' | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   props: { | ||||||
|  |     value: { | ||||||
|  |       type: Boolean, | ||||||
|  |       default: false | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       blocks: [ | ||||||
|  |         { key: 'hero', title: 'Hero', description: 'A large banner with a title.' }, | ||||||
|  |         { key: 'toc', title: 'Table of Contents', description: 'A list of children pages.' } | ||||||
|  |         // { key: 'form', title: 'Form', description: '' } | ||||||
|  |       ], | ||||||
|  |       block: { | ||||||
|  |         key: false | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   computed: { | ||||||
|  |     isShown: { | ||||||
|  |       get() { return this.value }, | ||||||
|  |       set(val) { this.$emit('input', val) } | ||||||
|  |     }, | ||||||
|  |     activeModal: sync('editor/activeModal') | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     selectBlock (item) { | ||||||
|  |       this.block = _.cloneDeep(item) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang='scss'> | ||||||
|  | .editor-modal-blocks { | ||||||
|  |     position: fixed; | ||||||
|  |     top: 112px; | ||||||
|  |     left: 64px; | ||||||
|  |     z-index: 10; | ||||||
|  |     width: calc(100vw - 64px - 17px); | ||||||
|  |     height: calc(100vh - 112px - 24px); | ||||||
|  |     background-color: rgba(darken(mc('grey', '900'), 3%), .9) !important; | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @@ -18,7 +18,7 @@ | |||||||
|                 template(v-for='(item, idx) of [1,2,3,4,5,6,7,8,9,10]') |                 template(v-for='(item, idx) of [1,2,3,4,5,6,7,8,9,10]') | ||||||
|                   v-list-tile(@click='') |                   v-list-tile(@click='') | ||||||
|                     v-list-tile-avatar |                     v-list-tile-avatar | ||||||
|                       v-avatar.radius-7(color='teal', tile) |                       v-avatar.radius-7(color='teal') | ||||||
|                         v-icon(dark) image |                         v-icon(dark) image | ||||||
|                     v-list-tile-content |                     v-list-tile-content | ||||||
|                       v-list-tile-title Image {{item}} |                       v-list-tile-title Image {{item}} | ||||||
|   | |||||||
| @@ -63,10 +63,21 @@ jobs: | |||||||
|     schedule: P1D |     schedule: P1D | ||||||
| groups: | groups: | ||||||
|   defaultPermissions: |   defaultPermissions: | ||||||
|     - 'manage:pages' |     - 'read:pages' | ||||||
|     - 'write:assets' |     - 'read:assets' | ||||||
|     - 'read:comments' |     - 'read:comments' | ||||||
|     - 'write:comments' |     - 'write:comments' | ||||||
|  |   defaultPageRules: | ||||||
|  |     - id: default | ||||||
|  |       deny: false | ||||||
|  |       match: START | ||||||
|  |       roles: | ||||||
|  |         - 'read:pages' | ||||||
|  |         - 'read:assets' | ||||||
|  |         - 'read:comments' | ||||||
|  |         - 'write:comments' | ||||||
|  |       path: '' | ||||||
|  |       locales: [] | ||||||
| telemetry: | telemetry: | ||||||
|   BUGSNAG_ID: 'bb4b324d0675bcbba10025617fd2cec8' |   BUGSNAG_ID: 'bb4b324d0675bcbba10025617fd2cec8' | ||||||
|   BUGSNAG_REMOTE: 'https://notify.bugsnag.com' |   BUGSNAG_REMOTE: 'https://notify.bugsnag.com' | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ module.exports = { | |||||||
|       const group = await WIKI.models.groups.query().insertAndFetch({ |       const group = await WIKI.models.groups.query().insertAndFetch({ | ||||||
|         name: args.name, |         name: args.name, | ||||||
|         permissions: JSON.stringify(WIKI.data.groups.defaultPermissions), |         permissions: JSON.stringify(WIKI.data.groups.defaultPermissions), | ||||||
|         pageRules: JSON.stringify([]), |         pageRules: JSON.stringify(WIKI.data.groups.defaultPageRules), | ||||||
|         isSystem: false |         isSystem: false | ||||||
|       }) |       }) | ||||||
|       await WIKI.auth.reloadGroups() |       await WIKI.auth.reloadGroups() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user