feat: markdown editor toolbar + default group rules fix
This commit is contained in:
parent
2ba1004024
commit
ca4e0ada30
@ -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-action
|
v-list-tile(@click='setHeaderLine(n)', :key='idx')
|
||||||
v-icon font_download
|
v-list-tile-action
|
||||||
v-list-tile-title Heading {{n}}
|
v-icon(:size='24 - (idx - 1) * 2') title
|
||||||
v-tooltip(top)
|
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-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()
|
||||||
|
Loading…
Reference in New Issue
Block a user