Browse Source

feat: markdown editor toolbar + default group rules fix

Nick 6 years ago
parent
commit
ca4e0ada30

+ 2 - 1
client/components/editor.vue

@@ -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: {

+ 137 - 47
client/components/editor/editor-markdown.vue

@@ -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 - 0
client/components/editor/editor-modal-blocks.vue

@@ -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>

+ 1 - 1
client/components/editor/editor-modal-media.vue

@@ -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}}

+ 13 - 2
server/app/data.yml

@@ -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'

+ 1 - 1
server/graph/resolvers/group.js

@@ -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()