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'), | ||||
|     editorModalProperties: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-properties.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: { | ||||
|     locale: { | ||||
|   | ||||
| @@ -1,91 +1,130 @@ | ||||
| <template lang='pug'> | ||||
|   .editor-markdown | ||||
|     v-toolbar.editor-markdown-toolbar(dense, color='primary', dark, flat) | ||||
|       v-tooltip(top) | ||||
|         v-btn(icon, slot='activator').mx-0 | ||||
|       v-tooltip(bottom, color='primary') | ||||
|         v-btn(icon, slot='activator', @click='toggleMarkup({ start: `**` })').mx-0 | ||||
|           v-icon format_bold | ||||
|         span Bold | ||||
|       v-tooltip(top) | ||||
|         v-btn(icon, slot='activator').mx-0 | ||||
|       v-tooltip(bottom, color='primary') | ||||
|         v-btn(icon, slot='activator', @click='toggleMarkup({ start: `*` })').mx-0 | ||||
|           v-icon format_italic | ||||
|         span Italic | ||||
|       v-tooltip(top) | ||||
|         v-btn(icon, slot='activator').mx-0 | ||||
|       v-tooltip(bottom, color='primary') | ||||
|         v-btn(icon, slot='activator', @click='toggleMarkup({ start: `~~` })').mx-0 | ||||
|           v-icon format_strikethrough | ||||
|         span Strikethrough | ||||
|       v-menu(offset-y, open-on-hover) | ||||
|         v-btn(icon, slot='activator').mx-0 | ||||
|           v-icon font_download | ||||
|         v-list | ||||
|           v-list-tile(v-for='(n, idx) in 6', @click='', :key='idx') | ||||
|             v-list-tile-action | ||||
|               v-icon font_download | ||||
|             v-list-tile-title Heading {{n}} | ||||
|       v-tooltip(top) | ||||
|           v-icon text_fields | ||||
|         v-list.py-0 | ||||
|           template(v-for='(n, idx) in 6') | ||||
|             v-list-tile(@click='setHeaderLine(n)', :key='idx') | ||||
|               v-list-tile-action | ||||
|                 v-icon(:size='24 - (idx - 1) * 2') title | ||||
|               v-list-tile-title Heading {{n}} | ||||
|             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-icon format_quote | ||||
|         span Blockquote | ||||
|       v-tooltip(top) | ||||
|         v-btn(icon, slot='activator').mx-0 | ||||
|         v-list.py-0 | ||||
|           v-list-tile(@click='insertBeforeEachLine({ content: `> `})') | ||||
|             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 | ||||
|         span Unordered List | ||||
|       v-tooltip(top) | ||||
|         v-btn(icon, slot='activator').mx-0 | ||||
|       v-tooltip(bottom, color='primary') | ||||
|         v-btn(icon, slot='activator', @click='insertBeforeEachLine({ content: `1. `})').mx-0 | ||||
|           v-icon format_list_numbered | ||||
|         span Ordered List | ||||
|       v-tooltip(top) | ||||
|         v-btn(icon, slot='activator').mx-0 | ||||
|           v-icon insert_link | ||||
|         span Link | ||||
|       v-tooltip(top) | ||||
|         v-btn(icon, slot='activator').mx-0 | ||||
|       v-tooltip(bottom, color='primary') | ||||
|         v-btn(icon, slot='activator', @click='toggleMarkup({ start: "`" })').mx-0 | ||||
|           v-icon space_bar | ||||
|         span Inline Code | ||||
|       v-tooltip(top) | ||||
|         v-btn(icon, slot='activator').mx-0 | ||||
|           v-icon code | ||||
|         span Code Block | ||||
|       v-tooltip(top) | ||||
|         v-btn(icon, slot='activator').mx-0 | ||||
|       v-tooltip(bottom, color='primary') | ||||
|         v-btn(icon, slot='activator', @click='toggleMarkup({ start: `<kbd>`, end: `</kbd>` })').mx-0 | ||||
|           v-icon font_download | ||||
|         span Keyboard Key | ||||
|       v-tooltip(bottom, color='primary') | ||||
|         v-btn(icon, slot='activator', @click='insertAfter({ content: `---`, newLine: true })').mx-0 | ||||
|           v-icon remove | ||||
|         span Horizontal Bar | ||||
|     .editor-markdown-main | ||||
|       .editor-markdown-sidebar | ||||
|         v-tooltip(right, color='primary') | ||||
|           v-btn(icon, slot='activator', dark).mx-0 | ||||
|         v-tooltip(right, color='teal') | ||||
|           v-btn(icon, slot='activator', dark, disabled).mx-0 | ||||
|             v-icon link | ||||
|           span Insert Link | ||||
|         v-tooltip(right) | ||||
|         v-tooltip(right, color='teal') | ||||
|           v-btn(icon, slot='activator', dark, @click='toggleModal(`editorModalMedia`)').mx-0 | ||||
|             v-icon(:color='activeModal === `editorModalMedia` ? `teal` : ``') image | ||||
|           span Insert Image | ||||
|         v-tooltip(right, color='primary') | ||||
|           v-btn(icon, slot='activator', dark).mx-0 | ||||
|         v-tooltip(right, color='teal') | ||||
|           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 | ||||
|           span Insert File | ||||
|         v-tooltip(right, color='primary') | ||||
|           v-btn(icon, slot='activator', dark).mx-0 | ||||
|         v-tooltip(right, color='teal') | ||||
|           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 | ||||
|           span Insert Video / Audio | ||||
|         v-tooltip(right, color='primary') | ||||
|           v-btn(icon, slot='activator', dark).mx-0 | ||||
|         v-tooltip(right, color='teal') | ||||
|           v-btn(icon, slot='activator', dark, disabled).mx-0 | ||||
|             v-icon multiline_chart | ||||
|           span Insert Diagram | ||||
|         v-tooltip(right, color='primary') | ||||
|           v-btn(icon, slot='activator', dark).mx-0 | ||||
|         v-tooltip(right, color='teal') | ||||
|           v-btn(icon, slot='activator', dark, disabled).mx-0 | ||||
|             v-icon functions | ||||
|           span Insert Math Expression | ||||
|         v-tooltip(right, color='primary') | ||||
|           v-btn(icon, slot='activator', dark).mx-0 | ||||
|         v-tooltip(right, color='teal') | ||||
|           v-btn(icon, slot='activator', dark, disabled).mx-0 | ||||
|             v-icon border_outer | ||||
|           span Table Helper | ||||
|         v-spacer | ||||
|         v-tooltip(right, color='primary') | ||||
|         v-tooltip(right, color='teal') | ||||
|           v-btn(icon, slot='activator', dark, @click='toggleFullscreen').mx-0 | ||||
|             v-icon crop_free | ||||
|           span Fullscreen Editor | ||||
|         v-tooltip(right, color='primary') | ||||
|           v-btn(icon, slot='activator', dark).mx-0 | ||||
|           span Distraction Free Mode | ||||
|         v-tooltip(right, color='teal') | ||||
|           v-btn(icon, slot='activator', dark, disabled).mx-0 | ||||
|             v-icon help | ||||
|           span Markdown Formatting Help | ||||
|       .editor-markdown-editor | ||||
| @@ -273,6 +312,57 @@ export default { | ||||
|  | ||||
|       // 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 | ||||
|      */ | ||||
|   | ||||
							
								
								
									
										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]') | ||||
|                   v-list-tile(@click='') | ||||
|                     v-list-tile-avatar | ||||
|                       v-avatar.radius-7(color='teal', tile) | ||||
|                       v-avatar.radius-7(color='teal') | ||||
|                         v-icon(dark) image | ||||
|                     v-list-tile-content | ||||
|                       v-list-tile-title Image {{item}} | ||||
|   | ||||
| @@ -63,10 +63,21 @@ jobs: | ||||
|     schedule: P1D | ||||
| groups: | ||||
|   defaultPermissions: | ||||
|     - 'manage:pages' | ||||
|     - 'write:assets' | ||||
|     - 'read:pages' | ||||
|     - 'read:assets' | ||||
|     - 'read:comments' | ||||
|     - 'write:comments' | ||||
|   defaultPageRules: | ||||
|     - id: default | ||||
|       deny: false | ||||
|       match: START | ||||
|       roles: | ||||
|         - 'read:pages' | ||||
|         - 'read:assets' | ||||
|         - 'read:comments' | ||||
|         - 'write:comments' | ||||
|       path: '' | ||||
|       locales: [] | ||||
| telemetry: | ||||
|   BUGSNAG_ID: 'bb4b324d0675bcbba10025617fd2cec8' | ||||
|   BUGSNAG_REMOTE: 'https://notify.bugsnag.com' | ||||
|   | ||||
| @@ -43,7 +43,7 @@ module.exports = { | ||||
|       const group = await WIKI.models.groups.query().insertAndFetch({ | ||||
|         name: args.name, | ||||
|         permissions: JSON.stringify(WIKI.data.groups.defaultPermissions), | ||||
|         pageRules: JSON.stringify([]), | ||||
|         pageRules: JSON.stringify(WIKI.data.groups.defaultPageRules), | ||||
|         isSystem: false | ||||
|       }) | ||||
|       await WIKI.auth.reloadGroups() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user