diff --git a/.gitignore b/.gitignore index b921decf..5704d131 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ config.yml /repo /data /uploads + +# IDE exclude +.idea diff --git a/.vscode/settings.json b/.vscode/settings.json index 8fe7823e..1456bd73 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,6 @@ "eslint.enable": true, "puglint.enable": true, "standard.enable": false, - "editor.formatOnSave": true + "editor.formatOnSave": true, + "editor.tabSize": 2 } diff --git a/client/js/app.js b/client/js/app.js index 4f33826a..d86811e3 100644 --- a/client/js/app.js +++ b/client/js/app.js @@ -4,7 +4,6 @@ /* eslint-disable no-new */ import $ from 'jquery' -import _ from 'lodash' import Vue from 'vue' import VueResource from 'vue-resource' import VueClipboards from 'vue-clipboards' @@ -17,6 +16,33 @@ import VueI18Next from '@panter/vue-i18next' import 'jquery-smooth-scroll' import 'jquery-sticky' +// ==================================== +// Load minimal lodash +// ==================================== + +import concat from 'lodash/concat' +import cloneDeep from 'lodash/cloneDeep' +import debounce from 'lodash/debounce' +import deburr from 'lodash/deburr' +import delay from 'lodash/delay' +import filter from 'lodash/filter' +import find from 'lodash/find' +import findKey from 'lodash/findKey' +import forEach from 'lodash/forEach' +import includes from 'lodash/includes' +import isEmpty from 'lodash/isEmpty' +import isNil from 'lodash/isNil' +import join from 'lodash/join' +import kebabCase from 'lodash/kebabCase' +import last from 'lodash/last' +import map from 'lodash/map' +import pullAt from 'lodash/pullAt' +import reject from 'lodash/reject' +import slice from 'lodash/slice' +import split from 'lodash/split' +import trim from 'lodash/trim' +import toUpper from 'lodash/toUpper' + // ==================================== // Load Helpers // ==================================== @@ -30,6 +56,7 @@ import helpers from './helpers' import alertComponent from './components/alert.vue' import anchorComponent from './components/anchor.vue' import colorPickerComponent from './components/color-picker.vue' +import editorCodeblockComponent from './components/editor-codeblock.vue' import loadingSpinnerComponent from './components/loading-spinner.vue' import modalCreatePageComponent from './components/modal-create-page.vue' import modalCreateUserComponent from './components/modal-create-user.vue' @@ -45,6 +72,35 @@ import contentViewComponent from './pages/content-view.component.js' import editorComponent from './components/editor.component.js' import sourceViewComponent from './pages/source-view.component.js' +// ==================================== +// Build lodash object +// ==================================== + +const _ = { + deburr, + concat, + cloneDeep, + debounce, + delay, + filter, + find, + findKey, + forEach, + includes, + isEmpty, + isNil, + join, + kebabCase, + last, + map, + pullAt, + reject, + slice, + split, + toUpper, + trim +} + // ==================================== // Initialize Vue Modules // ==================================== @@ -101,6 +157,7 @@ $(() => { colorPicker: colorPickerComponent, contentView: contentViewComponent, editor: editorComponent, + editorCodeblock: editorCodeblockComponent, loadingSpinner: loadingSpinnerComponent, modalCreatePage: modalCreatePageComponent, modalCreateUser: modalCreateUserComponent, diff --git a/client/js/components/editor-codeblock.vue b/client/js/components/editor-codeblock.vue new file mode 100644 index 00000000..bd9900ef --- /dev/null +++ b/client/js/components/editor-codeblock.vue @@ -0,0 +1,51 @@ + + + diff --git a/client/js/components/editor.component.js b/client/js/components/editor.component.js index aacaddc0..95053795 100644 --- a/client/js/components/editor.component.js +++ b/client/js/components/editor.component.js @@ -1,6 +1,7 @@ 'use strict' -import SimpleMDE from 'simplemde' +/* global FuseBox */ + import filesize from 'filesize.js' import $ from 'jquery' @@ -45,180 +46,186 @@ export default { }, mounted() { let self = this - mde = new SimpleMDE({ - autofocus: true, - autoDownloadFontAwesome: false, - element: this.$refs.editorTextArea, - placeholder: 'Enter Markdown formatted content here...', - spellChecker: false, - status: false, - toolbar: [ - { - name: 'bold', - action: SimpleMDE.toggleBold, - className: 'icon-bold', - title: 'Bold' - }, - { - name: 'italic', - action: SimpleMDE.toggleItalic, - className: 'icon-italic', - title: 'Italic' - }, - { - name: 'strikethrough', - action: SimpleMDE.toggleStrikethrough, - className: 'icon-strikethrough', - title: 'Strikethrough' - }, - '|', - { - name: 'heading-1', - action: SimpleMDE.toggleHeading1, - className: 'icon-header fa-header-x fa-header-1', - title: 'Big Heading' - }, - { - name: 'heading-2', - action: SimpleMDE.toggleHeading2, - className: 'icon-header fa-header-x fa-header-2', - title: 'Medium Heading' - }, - { - name: 'heading-3', - action: SimpleMDE.toggleHeading3, - className: 'icon-header fa-header-x fa-header-3', - title: 'Small Heading' - }, - { - name: 'quote', - action: SimpleMDE.toggleBlockquote, - className: 'icon-quote-left', - title: 'Quote' - }, - '|', - { - name: 'unordered-list', - action: SimpleMDE.toggleUnorderedList, - className: 'icon-th-list', - title: 'Bullet List' - }, - { - name: 'ordered-list', - action: SimpleMDE.toggleOrderedList, - className: 'icon-list-ol', - title: 'Numbered List' - }, - '|', - { - name: 'link', - action: (editor) => { - /* if(!mdeModalOpenState) { - mdeModalOpenState = true; - $('#modal-editor-link').slideToggle(); - } */ - window.alert('Coming soon!') + FuseBox.import('/js/simplemde/simplemde.min.js', (SimpleMDE) => { + mde = new SimpleMDE({ + autofocus: true, + autoDownloadFontAwesome: false, + element: this.$refs.editorTextArea, + placeholder: 'Enter Markdown formatted content here...', + spellChecker: false, + status: false, + toolbar: [ + { + name: 'bold', + action: SimpleMDE.toggleBold, + className: 'icon-bold', + title: 'Bold' }, - className: 'icon-link2', - title: 'Insert Link' - }, - { - name: 'image', - action: (editor) => { - if (!mdeModalOpenState) { - vueImage.open() - } + { + name: 'italic', + action: SimpleMDE.toggleItalic, + className: 'icon-italic', + title: 'Italic' }, - className: 'icon-image', - title: 'Insert Image' - }, - { - name: 'file', - action: (editor) => { - if (!mdeModalOpenState) { - vueFile.open() - } + { + name: 'strikethrough', + action: SimpleMDE.toggleStrikethrough, + className: 'icon-strikethrough', + title: 'Strikethrough' }, - className: 'icon-paper', - title: 'Insert File' - }, - { - name: 'video', - action: (editor) => { - if (!mdeModalOpenState) { - vueVideo.open() - } + '|', + { + name: 'heading-1', + action: SimpleMDE.toggleHeading1, + className: 'icon-header fa-header-x fa-header-1', + title: 'Big Heading' }, - className: 'icon-video-camera2', - title: 'Insert Video Player' - }, - '|', - { - name: 'inline-code', - action: (editor) => { - if (!editor.codemirror.doc.somethingSelected()) { - return alerts.pushError('Invalid selection', 'You must select at least 1 character first.') - } - let curSel = editor.codemirror.doc.getSelections() - curSel = _.map(curSel, (s) => { - return '`' + s + '`' - }) - editor.codemirror.doc.replaceSelections(curSel) + { + name: 'heading-2', + action: SimpleMDE.toggleHeading2, + className: 'icon-header fa-header-x fa-header-2', + title: 'Medium Heading' }, - className: 'icon-terminal', - title: 'Inline Code' - }, - { - name: 'code-block', - action: (editor) => { - if (!mdeModalOpenState) { - if (mde.codemirror.doc.somethingSelected()) { - vueCodeBlock.initContent = mde.codemirror.doc.getSelection() + { + name: 'heading-3', + action: SimpleMDE.toggleHeading3, + className: 'icon-header fa-header-x fa-header-3', + title: 'Small Heading' + }, + { + name: 'quote', + action: SimpleMDE.toggleBlockquote, + className: 'icon-quote-left', + title: 'Quote' + }, + '|', + { + name: 'unordered-list', + action: SimpleMDE.toggleUnorderedList, + className: 'icon-th-list', + title: 'Bullet List' + }, + { + name: 'ordered-list', + action: SimpleMDE.toggleOrderedList, + className: 'icon-list-ol', + title: 'Numbered List' + }, + '|', + { + name: 'link', + action: (editor) => { + /* if(!mdeModalOpenState) { + mdeModalOpenState = true; + $('#modal-editor-link').slideToggle(); + } */ + window.alert('Coming soon!') + }, + className: 'icon-link2', + title: 'Insert Link' + }, + { + name: 'image', + action: (editor) => { + // if (!mdeModalOpenState) { + // vueImage.open() + // } + }, + className: 'icon-image', + title: 'Insert Image' + }, + { + name: 'file', + action: (editor) => { + // if (!mdeModalOpenState) { + // vueFile.open() + // } + }, + className: 'icon-paper', + title: 'Insert File' + }, + { + name: 'video', + action: (editor) => { + // if (!mdeModalOpenState) { + // vueVideo.open() + // } + }, + className: 'icon-video-camera2', + title: 'Insert Video Player' + }, + '|', + { + name: 'inline-code', + action: (editor) => { + if (!editor.codemirror.doc.somethingSelected()) { + return self.$store.dispatch('alert', { + style: 'orange', + icon: 'marquee', + msg: 'Invalid selection. Select at least 1 character.' + }) } - - vueCodeBlock.open() - } + let curSel = editor.codemirror.doc.getSelections() + curSel = self._.map(curSel, (s) => { + return '`' + s + '`' + }) + editor.codemirror.doc.replaceSelections(curSel) + }, + className: 'icon-terminal', + title: 'Inline Code' }, - className: 'icon-code', - title: 'Code Block' - }, - '|', - { - name: 'table', - action: (editor) => { - window.alert('Coming soon!') - // todo + { + name: 'code-block', + action: (editor) => { + // if (!mdeModalOpenState) { + // if (mde.codemirror.doc.somethingSelected()) { + // vueCodeBlock.initContent = mde.codemirror.doc.getSelection() + // } + + // vueCodeBlock.open() + // } + }, + className: 'icon-code', + title: 'Code Block' }, - className: 'icon-table', - title: 'Insert Table' - }, - { - name: 'horizontal-rule', - action: SimpleMDE.drawHorizontalRule, - className: 'icon-minus2', - title: 'Horizontal Rule' + '|', + { + name: 'table', + action: (editor) => { + window.alert('Coming soon!') + // todo + }, + className: 'icon-table', + title: 'Insert Table' + }, + { + name: 'horizontal-rule', + action: SimpleMDE.drawHorizontalRule, + className: 'icon-minus2', + title: 'Horizontal Rule' + } + ], + shortcuts: { + 'toggleBlockquote': null, + 'toggleFullScreen': null } - ], - shortcuts: { - 'toggleBlockquote': null, - 'toggleFullScreen': null - } - }) + }) - // Save + // Save - this.$root.$on('editor-save', this.save) - $(window).bind('keydown', (ev) => { - if (ev.ctrlKey || ev.metaKey) { - switch (String.fromCharCode(ev.which).toLowerCase()) { - case 's': - ev.preventDefault() - self.save() - break + this.$root.$on('editor-save', this.save) + $(window).bind('keydown', (ev) => { + if (ev.ctrlKey || ev.metaKey) { + switch (String.fromCharCode(ev.which).toLowerCase()) { + case 's': + ev.preventDefault() + self.save() + break + } } - } - }) + }) - this.$store.dispatch('pageLoader/complete') + this.$store.dispatch('pageLoader/complete') + }) } } diff --git a/client/js/helpers/pages.js b/client/js/helpers/pages.js index 9b57f247..20781aec 100644 --- a/client/js/helpers/pages.js +++ b/client/js/helpers/pages.js @@ -1,6 +1,13 @@ 'use strict' -import _ from 'lodash' +import deburr from 'lodash/deburr' +import filter from 'lodash/filter' +import isEmpty from 'lodash/isEmpty' +import join from 'lodash/join' +import kebabCase from 'lodash/kebabCase' +import map from 'lodash/map' +import split from 'lodash/split' +import trim from 'lodash/trim' module.exports = { /** @@ -9,11 +16,11 @@ module.exports = { * @returns {string} Safe path */ makeSafePath: (rawPath) => { - let rawParts = _.split(_.trim(rawPath), '/') - rawParts = _.map(rawParts, (r) => { - return _.kebabCase(_.deburr(_.trim(r))) + let rawParts = split(trim(rawPath), '/') + rawParts = map(rawParts, (r) => { + return kebabCase(deburr(trim(r))) }) - return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r) }), '/') + return join(filter(rawParts, (r) => { return !isEmpty(r) }), '/') } } diff --git a/client/js/store/index.js b/client/js/store/index.js index 302db230..1e346d8f 100644 --- a/client/js/store/index.js +++ b/client/js/store/index.js @@ -4,6 +4,7 @@ import Vuex from 'vuex' import alert from './modules/alert' import anchor from './modules/anchor' import editor from './modules/editor' +import editorCodeblock from './modules/editor-codeblock' import modalCreatePage from './modules/modal-create-page' import modalCreateUser from './modules/modal-create-user' import modalDiscardPage from './modules/modal-discard-page' @@ -28,6 +29,7 @@ export default new Vuex.Store({ alert, anchor, editor, + editorCodeblock, modalCreatePage, modalCreateUser, modalDiscardPage, diff --git a/client/js/store/modules/alert.js b/client/js/store/modules/alert.js index 79d5cee1..27cfb74f 100644 --- a/client/js/store/modules/alert.js +++ b/client/js/store/modules/alert.js @@ -1,6 +1,6 @@ 'use strict' -import _ from 'lodash' +import debounce from 'lodash/debounce' export default { state: { @@ -24,7 +24,7 @@ export default { commit('alertChange', opts) dispatch('alertDismiss') }, - alertDismiss: _.debounce(({ commit }) => { + alertDismiss: debounce(({ commit }) => { let opts = { shown: false } commit('alertChange', opts) }, 3000) diff --git a/client/js/store/modules/editor-codeblock.js b/client/js/store/modules/editor-codeblock.js new file mode 100644 index 00000000..5b1d08c9 --- /dev/null +++ b/client/js/store/modules/editor-codeblock.js @@ -0,0 +1,16 @@ +'use strict' + +export default { + namespaced: true, + state: { + shown: false + }, + getters: {}, + mutations: { + shownChange: (state, shownState) => { state.shown = shownState } + }, + actions: { + open({ commit }) { commit('shownChange', true) }, + close({ commit }) { commit('shownChange', false) } + } +} diff --git a/fuse.js b/fuse.js index e4b6d80f..7c5e2a12 100644 --- a/fuse.js +++ b/fuse.js @@ -79,6 +79,22 @@ const SHIMS = { console.info(colors.white('└── ') + colors.green('Running global tasks...')) let globalTasks = Promise.mapSeries([ + /** + * SimpleMDE + */ + () => { + return fs.accessAsync('./assets/js/simplemde').then(() => { + console.info(colors.white(' └── ') + colors.magenta('SimpleMDE directory already exists. Task aborted.')) + return true + }).catch(err => { + if (err.code === 'ENOENT') { + console.info(colors.white(' └── ') + colors.green('Copy + Minify SimpleMDE to assets...')) + return fs.copy('./node_modules/simplemde/dist/simplemde.min.js', './assets/js/simplemde/simplemde.min.js') + } else { + throw err + } + }) + }, /** * ACE Modes */