Przeglądaj źródła

Fuse-box client scripts integration + deps update

NGPixel 8 lat temu
rodzic
commit
fe0c4ce0c0

Plik diff jest za duży
+ 0 - 64
assets/js/bundle.min.js


Plik diff jest za duży
+ 0 - 53
assets/js/configure.min.js


+ 0 - 2
client/index.js

@@ -8,8 +8,6 @@ switch (logic) {
     require('./js/login.js')
     break
   default:
-    require('./node_modules/highlight.js/styles/tomorrow.css')
-    require('./node_modules/simplemde/dist/simplemde.min.css')
     require('./scss/app.scss')
     require('./js/app.js')
     break

+ 11 - 18
client/js/app.js

@@ -2,14 +2,14 @@
 
 /* global alertsData */
 
-import jQuery from 'jquery'
+import $ from 'jquery'
 import _ from 'lodash'
-import Sticky from 'sticky-js'
 import io from 'socket.io-client'
 import Alerts from './components/alerts.js'
-/* eslint-disable spaced-comment */
+import 'jquery-smooth-scroll'
+import Sticky from 'sticky-js'
 
-jQuery(document).ready(function ($) {
+$(() => {
   // ====================================
   // Scroll
   // ====================================
@@ -45,24 +45,17 @@ jQuery(document).ready(function ($) {
   // Establish WebSocket connection
   // ====================================
 
-  var socket = io(window.location.origin) // eslint-disable-line no-unused-vars
+  var socket = io(window.location.origin)
 
-  //=include components/search.js
+  require('./components/search.js')(socket)
 
   // ====================================
   // Pages logic
   // ====================================
 
-  //=include pages/view.js
-  //=include pages/create.js
-  //=include pages/edit.js
-  //=include pages/source.js
-  //=include pages/admin.js
+  require('./pages/view.js')(alerts)
+  // require('./pages/create.js')
+  require('./pages/edit.js')(alerts, socket)
+  require('./pages/source.js')(alerts)
+  require('./pages/admin.js')(alerts)
 })
-
-//=include helpers/form.js
-//=include helpers/pages.js
-
-//=include components/alerts.js
-
-/* eslint-enable spaced-comment */

+ 56 - 46
client/js/components/editor-codeblock.js

@@ -1,6 +1,12 @@
-/* global $, Vue, ace, mde, _ */
+'use strict'
+
+import $ from 'jquery'
+import Vue from 'vue'
+import _ from 'lodash'
+import * as ace from 'brace'
+import 'brace/theme/tomorrow_night'
+import 'brace/mode/markdown'
 
-let modelist = ace.require('ace/ext/modelist')
 let codeEditor = null
 
 // ACE - Mode Loader
@@ -24,52 +30,56 @@ let loadAceMode = (m) => {
 
 // Vue Code Block instance
 
-let vueCodeBlock = new Vue({
-  el: '#modal-editor-codeblock',
-  data: {
-    modes: modelist.modesByName,
-    modeSelected: 'text',
-    initContent: ''
-  },
-  watch: {
-    modeSelected: (val, oldVal) => {
-      loadAceMode(val).done(() => {
-        ace.require('ace/mode/' + val)
-        codeEditor.getSession().setMode('ace/mode/' + val)
-      })
-    }
-  },
-  methods: {
-    open: (ev) => {
-      $('#modal-editor-codeblock').addClass('is-active')
+module.exports = (mde, mdeModalOpenState) => {
+  let modelist = {} // ace.require('ace/ext/modelist')
+  let vueCodeBlock = new Vue({
+    el: '#modal-editor-codeblock',
+    data: {
+      modes: modelist.modesByName,
+      modeSelected: 'text',
+      initContent: ''
+    },
+    watch: {
+      modeSelected: (val, oldVal) => {
+        loadAceMode(val).done(() => {
+          ace.require('ace/mode/' + val)
+          codeEditor.getSession().setMode('ace/mode/' + val)
+        })
+      }
+    },
+    methods: {
+      open: (ev) => {
+        $('#modal-editor-codeblock').addClass('is-active')
 
-      _.delay(() => {
-        codeEditor = ace.edit('codeblock-editor')
-        codeEditor.setTheme('ace/theme/tomorrow_night')
-        codeEditor.getSession().setMode('ace/mode/' + vueCodeBlock.modeSelected)
-        codeEditor.setOption('fontSize', '14px')
-        codeEditor.setOption('hScrollBarAlwaysVisible', false)
-        codeEditor.setOption('wrap', true)
+        _.delay(() => {
+          codeEditor = ace.edit('codeblock-editor')
+          codeEditor.setTheme('ace/theme/tomorrow_night')
+          codeEditor.getSession().setMode('ace/mode/' + vueCodeBlock.modeSelected)
+          codeEditor.setOption('fontSize', '14px')
+          codeEditor.setOption('hScrollBarAlwaysVisible', false)
+          codeEditor.setOption('wrap', true)
 
-        codeEditor.setValue(vueCodeBlock.initContent)
+          codeEditor.setValue(vueCodeBlock.initContent)
 
-        codeEditor.focus()
-        codeEditor.renderer.updateFull()
-      }, 300)
-    },
-    cancel: (ev) => {
-      mdeModalOpenState = false  // eslint-disable-line no-undef
-      $('#modal-editor-codeblock').removeClass('is-active')
-      vueCodeBlock.initContent = ''
-    },
-    insertCode: (ev) => {
-      if (mde.codemirror.doc.somethingSelected()) {
-        mde.codemirror.execCommand('singleSelection')
-      }
-      let codeBlockText = '\n```' + vueCodeBlock.modeSelected + '\n' + codeEditor.getValue() + '\n```\n'
+          codeEditor.focus()
+          codeEditor.renderer.updateFull()
+        }, 300)
+      },
+      cancel: (ev) => {
+        mdeModalOpenState = false  // eslint-disable-line no-undef
+        $('#modal-editor-codeblock').removeClass('is-active')
+        vueCodeBlock.initContent = ''
+      },
+      insertCode: (ev) => {
+        if (mde.codemirror.doc.somethingSelected()) {
+          mde.codemirror.execCommand('singleSelection')
+        }
+        let codeBlockText = '\n```' + vueCodeBlock.modeSelected + '\n' + codeEditor.getValue() + '\n```\n'
 
-      mde.codemirror.doc.replaceSelection(codeBlockText)
-      vueCodeBlock.cancel()
+        mde.codemirror.doc.replaceSelection(codeBlockText)
+        vueCodeBlock.cancel()
+      }
     }
-  }
-})
+  })
+  return vueCodeBlock
+}

+ 310 - 301
client/js/components/editor-file.js

@@ -1,352 +1,361 @@
-/* global $, Vue, _, alerts, mde, socket */
-
-let vueFile = new Vue({
-  el: '#modal-editor-file',
-  data: {
-    isLoading: false,
-    isLoadingText: '',
-    newFolderName: '',
-    newFolderShow: false,
-    newFolderError: false,
-    folders: [],
-    currentFolder: '',
-    currentFile: '',
-    files: [],
-    uploadSucceeded: false,
-    postUploadChecks: 0,
-    renameFileShow: false,
-    renameFileId: '',
-    renameFileFilename: '',
-    deleteFileShow: false,
-    deleteFileId: '',
-    deleteFileFilename: ''
-  },
-  methods: {
-
-    open: () => {
-      mdeModalOpenState = true // eslint-disable-line no-undef
-      $('#modal-editor-file').addClass('is-active')
-      vueFile.refreshFolders()
-    },
-    cancel: (ev) => {
-      mdeModalOpenState = false // eslint-disable-line no-undef
-      $('#modal-editor-file').removeClass('is-active')
-    },
+'use strict'
 
-    // -------------------------------------------
-    // INSERT LINK TO FILE
-    // -------------------------------------------
+import $ from 'jquery'
+import Vue from 'vue'
+import _ from 'lodash'
+import 'jquery-contextmenu'
+import 'jquery-simple-upload'
 
-    selectFile: (fileId) => {
-      vueFile.currentFile = fileId
+module.exports = (alerts, mde, mdeModalOpenState, socket) => {
+  let vueFile = new Vue({
+    el: '#modal-editor-file',
+    data: {
+      isLoading: false,
+      isLoadingText: '',
+      newFolderName: '',
+      newFolderShow: false,
+      newFolderError: false,
+      folders: [],
+      currentFolder: '',
+      currentFile: '',
+      files: [],
+      uploadSucceeded: false,
+      postUploadChecks: 0,
+      renameFileShow: false,
+      renameFileId: '',
+      renameFileFilename: '',
+      deleteFileShow: false,
+      deleteFileId: '',
+      deleteFileFilename: ''
     },
-    insertFileLink: (ev) => {
-      if (mde.codemirror.doc.somethingSelected()) {
-        mde.codemirror.execCommand('singleSelection')
-      }
+    methods: {
+
+      open: () => {
+        mdeModalOpenState = true // eslint-disable-line no-undef
+        $('#modal-editor-file').addClass('is-active')
+        vueFile.refreshFolders()
+      },
+      cancel: (ev) => {
+        mdeModalOpenState = false // eslint-disable-line no-undef
+        $('#modal-editor-file').removeClass('is-active')
+      },
+
+      // -------------------------------------------
+      // INSERT LINK TO FILE
+      // -------------------------------------------
+
+      selectFile: (fileId) => {
+        vueFile.currentFile = fileId
+      },
+      insertFileLink: (ev) => {
+        if (mde.codemirror.doc.somethingSelected()) {
+          mde.codemirror.execCommand('singleSelection')
+        }
 
-      let selFile = _.find(vueFile.files, ['_id', vueFile.currentFile])
-      selFile.normalizedPath = (selFile.folder === 'f:') ? selFile.filename : selFile.folder.slice(2) + '/' + selFile.filename
-      selFile.titleGuess = _.startCase(selFile.basename)
+        let selFile = _.find(vueFile.files, ['_id', vueFile.currentFile])
+        selFile.normalizedPath = (selFile.folder === 'f:') ? selFile.filename : selFile.folder.slice(2) + '/' + selFile.filename
+        selFile.titleGuess = _.startCase(selFile.basename)
+
+        let fileText = '[' + selFile.titleGuess + '](/uploads/' + selFile.normalizedPath + ' "' + selFile.titleGuess + '")'
+
+        mde.codemirror.doc.replaceSelection(fileText)
+        vueFile.cancel()
+      },
+
+      // -------------------------------------------
+      // NEW FOLDER
+      // -------------------------------------------
+
+      newFolder: (ev) => {
+        vueFile.newFolderName = ''
+        vueFile.newFolderError = false
+        vueFile.newFolderShow = true
+        _.delay(() => { $('#txt-editor-file-newfoldername').focus() }, 400)
+      },
+      newFolderDiscard: (ev) => {
+        vueFile.newFolderShow = false
+      },
+      newFolderCreate: (ev) => {
+        let regFolderName = new RegExp('^[a-z0-9][a-z0-9-]*[a-z0-9]$')
+        vueFile.newFolderName = _.kebabCase(_.trim(vueFile.newFolderName))
+
+        if (_.isEmpty(vueFile.newFolderName) || !regFolderName.test(vueFile.newFolderName)) {
+          vueFile.newFolderError = true
+          return
+        }
 
-      let fileText = '[' + selFile.titleGuess + '](/uploads/' + selFile.normalizedPath + ' "' + selFile.titleGuess + '")'
+        vueFile.newFolderDiscard()
+        vueFile.isLoadingText = 'Creating new folder...'
+        vueFile.isLoading = true
 
-      mde.codemirror.doc.replaceSelection(fileText)
-      vueFile.cancel()
-    },
+        Vue.nextTick(() => {
+          socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => {
+            vueFile.folders = data
+            vueFile.currentFolder = vueFile.newFolderName
+            vueFile.files = []
+            vueFile.isLoading = false
+          })
+        })
+      },
+
+      // -------------------------------------------
+      // RENAME FILE
+      // -------------------------------------------
+
+      renameFile: () => {
+        let c = _.find(vueFile.files, [ '_id', vueFile.renameFileId ])
+        vueFile.renameFileFilename = c.basename || ''
+        vueFile.renameFileShow = true
+        _.delay(() => {
+          $('#txt-editor-renamefile').focus()
+          _.defer(() => { $('#txt-editor-file-rename').select() })
+        }, 400)
+      },
+      renameFileDiscard: () => {
+        vueFile.renameFileShow = false
+      },
+      renameFileGo: () => {
+        vueFile.renameFileDiscard()
+        vueFile.isLoadingText = 'Renaming file...'
+        vueFile.isLoading = true
 
-    // -------------------------------------------
-    // NEW FOLDER
-    // -------------------------------------------
+        Vue.nextTick(() => {
+          socket.emit('uploadsRenameFile', { uid: vueFile.renameFileId, folder: vueFile.currentFolder, filename: vueFile.renameFileFilename }, (data) => {
+            if (data.ok) {
+              vueFile.waitChangeComplete(vueFile.files.length, false)
+            } else {
+              vueFile.isLoading = false
+              alerts.pushError('Rename error', data.msg)
+            }
+          })
+        })
+      },
 
-    newFolder: (ev) => {
-      vueFile.newFolderName = ''
-      vueFile.newFolderError = false
-      vueFile.newFolderShow = true
-      _.delay(() => { $('#txt-editor-file-newfoldername').focus() }, 400)
-    },
-    newFolderDiscard: (ev) => {
-      vueFile.newFolderShow = false
-    },
-    newFolderCreate: (ev) => {
-      let regFolderName = new RegExp('^[a-z0-9][a-z0-9-]*[a-z0-9]$')
-      vueFile.newFolderName = _.kebabCase(_.trim(vueFile.newFolderName))
+      // -------------------------------------------
+      // MOVE FILE
+      // -------------------------------------------
 
-      if (_.isEmpty(vueFile.newFolderName) || !regFolderName.test(vueFile.newFolderName)) {
-        vueFile.newFolderError = true
-        return
-      }
+      moveFile: (uid, fld) => {
+        vueFile.isLoadingText = 'Moving file...'
+        vueFile.isLoading = true
+        Vue.nextTick(() => {
+          socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
+            if (data.ok) {
+              vueFile.loadFiles()
+            } else {
+              vueFile.isLoading = false
+              alerts.pushError('Rename error', data.msg)
+            }
+          })
+        })
+      },
 
-      vueFile.newFolderDiscard()
-      vueFile.isLoadingText = 'Creating new folder...'
-      vueFile.isLoading = true
+      // -------------------------------------------
+      // DELETE FILE
+      // -------------------------------------------
 
-      Vue.nextTick(() => {
-        socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => {
-          vueFile.folders = data
-          vueFile.currentFolder = vueFile.newFolderName
-          vueFile.files = []
-          vueFile.isLoading = false
+      deleteFileWarn: (show) => {
+        if (show) {
+          let c = _.find(vueFile.files, [ '_id', vueFile.deleteFileId ])
+          vueFile.deleteFileFilename = c.filename || 'this file'
+        }
+        vueFile.deleteFileShow = show
+      },
+      deleteFileGo: () => {
+        vueFile.deleteFileWarn(false)
+        vueFile.isLoadingText = 'Deleting file...'
+        vueFile.isLoading = true
+        Vue.nextTick(() => {
+          socket.emit('uploadsDeleteFile', { uid: vueFile.deleteFileId }, (data) => {
+            vueFile.loadFiles()
+          })
         })
-      })
-    },
+      },
 
-    // -------------------------------------------
-    // RENAME FILE
-    // -------------------------------------------
-
-    renameFile: () => {
-      let c = _.find(vueFile.files, [ '_id', vueFile.renameFileId ])
-      vueFile.renameFileFilename = c.basename || ''
-      vueFile.renameFileShow = true
-      _.delay(() => {
-        $('#txt-editor-renamefile').focus()
-        _.defer(() => { $('#txt-editor-file-rename').select() })
-      }, 400)
-    },
-    renameFileDiscard: () => {
-      vueFile.renameFileShow = false
-    },
-    renameFileGo: () => {
-      vueFile.renameFileDiscard()
-      vueFile.isLoadingText = 'Renaming file...'
-      vueFile.isLoading = true
-
-      Vue.nextTick(() => {
-        socket.emit('uploadsRenameFile', { uid: vueFile.renameFileId, folder: vueFile.currentFolder, filename: vueFile.renameFileFilename }, (data) => {
-          if (data.ok) {
-            vueFile.waitChangeComplete(vueFile.files.length, false)
-          } else {
-            vueFile.isLoading = false
-            alerts.pushError('Rename error', data.msg)
-          }
-        })
-      })
-    },
+      // -------------------------------------------
+      // LOAD FROM REMOTE
+      // -------------------------------------------
 
-    // -------------------------------------------
-    // MOVE FILE
-    // -------------------------------------------
+      selectFolder: (fldName) => {
+        vueFile.currentFolder = fldName
+        vueFile.loadFiles()
+      },
 
-    moveFile: (uid, fld) => {
-      vueFile.isLoadingText = 'Moving file...'
-      vueFile.isLoading = true
-      Vue.nextTick(() => {
-        socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
-          if (data.ok) {
+      refreshFolders: () => {
+        vueFile.isLoadingText = 'Fetching folders list...'
+        vueFile.isLoading = true
+        vueFile.currentFolder = ''
+        vueFile.currentImage = ''
+        Vue.nextTick(() => {
+          socket.emit('uploadsGetFolders', { }, (data) => {
+            vueFile.folders = data
             vueFile.loadFiles()
-          } else {
-            vueFile.isLoading = false
-            alerts.pushError('Rename error', data.msg)
-          }
+          })
         })
-      })
-    },
-
-    // -------------------------------------------
-    // DELETE FILE
-    // -------------------------------------------
+      },
 
-    deleteFileWarn: (show) => {
-      if (show) {
-        let c = _.find(vueFile.files, [ '_id', vueFile.deleteFileId ])
-        vueFile.deleteFileFilename = c.filename || 'this file'
-      }
-      vueFile.deleteFileShow = show
-    },
-    deleteFileGo: () => {
-      vueFile.deleteFileWarn(false)
-      vueFile.isLoadingText = 'Deleting file...'
-      vueFile.isLoading = true
-      Vue.nextTick(() => {
-        socket.emit('uploadsDeleteFile', { uid: vueFile.deleteFileId }, (data) => {
-          vueFile.loadFiles()
+      loadFiles: (silent) => {
+        if (!silent) {
+          vueFile.isLoadingText = 'Fetching files...'
+          vueFile.isLoading = true
+        }
+        return new Promise((resolve, reject) => {
+          Vue.nextTick(() => {
+            socket.emit('uploadsGetFiles', { folder: vueFile.currentFolder }, (data) => {
+              vueFile.files = data
+              if (!silent) {
+                vueFile.isLoading = false
+              }
+              vueFile.attachContextMenus()
+              resolve(true)
+            })
+          })
         })
-      })
-    },
+      },
 
-    // -------------------------------------------
-    // LOAD FROM REMOTE
-    // -------------------------------------------
+      waitChangeComplete: (oldAmount, expectChange) => {
+        expectChange = (_.isBoolean(expectChange)) ? expectChange : true
 
-    selectFolder: (fldName) => {
-      vueFile.currentFolder = fldName
-      vueFile.loadFiles()
-    },
+        vueFile.postUploadChecks++
+        vueFile.isLoadingText = 'Processing...'
 
-    refreshFolders: () => {
-      vueFile.isLoadingText = 'Fetching folders list...'
-      vueFile.isLoading = true
-      vueFile.currentFolder = ''
-      vueFile.currentImage = ''
-      Vue.nextTick(() => {
-        socket.emit('uploadsGetFolders', { }, (data) => {
-          vueFile.folders = data
-          vueFile.loadFiles()
-        })
-      })
-    },
-
-    loadFiles: (silent) => {
-      if (!silent) {
-        vueFile.isLoadingText = 'Fetching files...'
-        vueFile.isLoading = true
-      }
-      return new Promise((resolve, reject) => {
         Vue.nextTick(() => {
-          socket.emit('uploadsGetFiles', { folder: vueFile.currentFolder }, (data) => {
-            vueFile.files = data
-            if (!silent) {
+          vueFile.loadFiles(true).then(() => {
+            if ((vueFile.files.length !== oldAmount) === expectChange) {
+              vueFile.postUploadChecks = 0
+              vueFile.isLoading = false
+            } else if (vueFile.postUploadChecks > 5) {
+              vueFile.postUploadChecks = 0
               vueFile.isLoading = false
+              alerts.pushError('Unable to fetch updated listing', 'Try again later')
+            } else {
+              _.delay(() => {
+                vueFile.waitChangeComplete(oldAmount, expectChange)
+              }, 1500)
             }
-            vueFile.attachContextMenus()
-            resolve(true)
           })
         })
-      })
-    },
+      },
 
-    waitChangeComplete: (oldAmount, expectChange) => {
-      expectChange = (_.isBoolean(expectChange)) ? expectChange : true
+      // -------------------------------------------
+      // IMAGE CONTEXT MENU
+      // -------------------------------------------
 
-      vueFile.postUploadChecks++
-      vueFile.isLoadingText = 'Processing...'
-
-      Vue.nextTick(() => {
-        vueFile.loadFiles(true).then(() => {
-          if ((vueFile.files.length !== oldAmount) === expectChange) {
-            vueFile.postUploadChecks = 0
-            vueFile.isLoading = false
-          } else if (vueFile.postUploadChecks > 5) {
-            vueFile.postUploadChecks = 0
-            vueFile.isLoading = false
-            alerts.pushError('Unable to fetch updated listing', 'Try again later')
-          } else {
-            _.delay(() => {
-              vueFile.waitChangeComplete(oldAmount, expectChange)
-            }, 1500)
+      attachContextMenus: () => {
+        let moveFolders = _.map(vueFile.folders, (f) => {
+          return {
+            name: (f !== '') ? f : '/ (root)',
+            icon: 'fa-folder',
+            callback: (key, opt) => {
+              let moveFileId = _.toString($(opt.$trigger).data('uid'))
+              let moveFileDestFolder = _.nth(vueFile.folders, key)
+              vueFile.moveFile(moveFileId, moveFileDestFolder)
+            }
           }
         })
-      })
-    },
 
-    // -------------------------------------------
-    // IMAGE CONTEXT MENU
-    // -------------------------------------------
-
-    attachContextMenus: () => {
-      let moveFolders = _.map(vueFile.folders, (f) => {
-        return {
-          name: (f !== '') ? f : '/ (root)',
-          icon: 'fa-folder',
-          callback: (key, opt) => {
-            let moveFileId = _.toString($(opt.$trigger).data('uid'))
-            let moveFileDestFolder = _.nth(vueFile.folders, key)
-            vueFile.moveFile(moveFileId, moveFileDestFolder)
-          }
-        }
-      })
-
-      $.contextMenu('destroy', '.editor-modal-file-choices > figure')
-      $.contextMenu({
-        selector: '.editor-modal-file-choices > figure',
-        appendTo: '.editor-modal-file-choices',
-        position: (opt, x, y) => {
-          $(opt.$trigger).addClass('is-contextopen')
-          let trigPos = $(opt.$trigger).position()
-          let trigDim = { w: $(opt.$trigger).width() / 5, h: $(opt.$trigger).height() / 2 }
-          opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w })
-        },
-        events: {
-          hide: (opt) => {
-            $(opt.$trigger).removeClass('is-contextopen')
-          }
-        },
-        items: {
-          rename: {
-            name: 'Rename',
-            icon: 'fa-edit',
-            callback: (key, opt) => {
-              vueFile.renameFileId = _.toString(opt.$trigger[0].dataset.uid)
-              vueFile.renameFile()
-            }
+        $.contextMenu('destroy', '.editor-modal-file-choices > figure')
+        $.contextMenu({
+          selector: '.editor-modal-file-choices > figure',
+          appendTo: '.editor-modal-file-choices',
+          position: (opt, x, y) => {
+            $(opt.$trigger).addClass('is-contextopen')
+            let trigPos = $(opt.$trigger).position()
+            let trigDim = { w: $(opt.$trigger).width() / 5, h: $(opt.$trigger).height() / 2 }
+            opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w })
           },
-          move: {
-            name: 'Move to...',
-            icon: 'fa-folder-open-o',
-            items: moveFolders
+          events: {
+            hide: (opt) => {
+              $(opt.$trigger).removeClass('is-contextopen')
+            }
           },
-          delete: {
-            name: 'Delete',
-            icon: 'fa-trash',
-            callback: (key, opt) => {
-              vueFile.deleteFileId = _.toString(opt.$trigger[0].dataset.uid)
-              vueFile.deleteFileWarn(true)
+          items: {
+            rename: {
+              name: 'Rename',
+              icon: 'fa-edit',
+              callback: (key, opt) => {
+                vueFile.renameFileId = _.toString(opt.$trigger[0].dataset.uid)
+                vueFile.renameFile()
+              }
+            },
+            move: {
+              name: 'Move to...',
+              icon: 'fa-folder-open-o',
+              items: moveFolders
+            },
+            delete: {
+              name: 'Delete',
+              icon: 'fa-trash',
+              callback: (key, opt) => {
+                vueFile.deleteFileId = _.toString(opt.$trigger[0].dataset.uid)
+                vueFile.deleteFileWarn(true)
+              }
             }
           }
-        }
-      })
-    }
-
-  }
-})
+        })
+      }
 
-$('#btn-editor-file-upload input').on('change', (ev) => {
-  let curFileAmount = vueFile.files.length
+    }
+  })
 
-  $(ev.currentTarget).simpleUpload('/uploads/file', {
+  $('#btn-editor-file-upload input').on('change', (ev) => {
+    let curFileAmount = vueFile.files.length
 
-    name: 'binfile',
-    data: {
-      folder: vueFile.currentFolder
-    },
-    limit: 20,
-    expect: 'json',
-    maxFileSize: 0,
-
-    init: (totalUploads) => {
-      vueFile.uploadSucceeded = false
-      vueFile.isLoadingText = 'Preparing to upload...'
-      vueFile.isLoading = true
-    },
+    $(ev.currentTarget).simpleUpload('/uploads/file', {
 
-    progress: (progress) => {
-      vueFile.isLoadingText = 'Uploading...' + Math.round(progress) + '%'
-    },
+      name: 'binfile',
+      data: {
+        folder: vueFile.currentFolder
+      },
+      limit: 20,
+      expect: 'json',
+      maxFileSize: 0,
 
-    success: (data) => {
-      if (data.ok) {
-        let failedUpls = _.filter(data.results, ['ok', false])
-        if (failedUpls.length) {
-          _.forEach(failedUpls, (u) => {
-            alerts.pushError('Upload error', u.msg)
-          })
-          if (failedUpls.length < data.results.length) {
-            alerts.push({
-              title: 'Some uploads succeeded',
-              message: 'Files that are not mentionned in the errors above were uploaded successfully.'
+      init: (totalUploads) => {
+        vueFile.uploadSucceeded = false
+        vueFile.isLoadingText = 'Preparing to upload...'
+        vueFile.isLoading = true
+      },
+
+      progress: (progress) => {
+        vueFile.isLoadingText = 'Uploading...' + Math.round(progress) + '%'
+      },
+
+      success: (data) => {
+        if (data.ok) {
+          let failedUpls = _.filter(data.results, ['ok', false])
+          if (failedUpls.length) {
+            _.forEach(failedUpls, (u) => {
+              alerts.pushError('Upload error', u.msg)
             })
+            if (failedUpls.length < data.results.length) {
+              alerts.push({
+                title: 'Some uploads succeeded',
+                message: 'Files that are not mentionned in the errors above were uploaded successfully.'
+              })
+              vueFile.uploadSucceeded = true
+            }
+          } else {
             vueFile.uploadSucceeded = true
           }
         } else {
-          vueFile.uploadSucceeded = true
+          alerts.pushError('Upload error', data.msg)
         }
-      } else {
-        alerts.pushError('Upload error', data.msg)
-      }
-    },
+      },
 
-    error: (error) => {
-      alerts.pushError('Upload error', error.message)
-    },
+      error: (error) => {
+        alerts.pushError('Upload error', error.message)
+      },
 
-    finish: () => {
-      if (vueFile.uploadSucceeded) {
-        vueFile.waitChangeComplete(curFileAmount, true)
-      } else {
-        vueFile.isLoading = false
+      finish: () => {
+        if (vueFile.uploadSucceeded) {
+          vueFile.waitChangeComplete(curFileAmount, true)
+        } else {
+          vueFile.isLoading = false
+        }
       }
-    }
 
+    })
   })
-})
+  return vueFile
+}

+ 353 - 344
client/js/components/editor-image.js

@@ -1,397 +1,406 @@
-/* global $, Vue, mde, _, alerts, socket */
-
-let vueImage = new Vue({
-  el: '#modal-editor-image',
-  data: {
-    isLoading: false,
-    isLoadingText: '',
-    newFolderName: '',
-    newFolderShow: false,
-    newFolderError: false,
-    fetchFromUrlURL: '',
-    fetchFromUrlShow: false,
-    folders: [],
-    currentFolder: '',
-    currentImage: '',
-    currentAlign: 'left',
-    images: [],
-    uploadSucceeded: false,
-    postUploadChecks: 0,
-    renameImageShow: false,
-    renameImageId: '',
-    renameImageFilename: '',
-    deleteImageShow: false,
-    deleteImageId: '',
-    deleteImageFilename: ''
-  },
-  methods: {
-
-    open: () => {
-      mdeModalOpenState = true // eslint-disable-line no-undef
-      $('#modal-editor-image').addClass('is-active')
-      vueImage.refreshFolders()
-    },
-    cancel: (ev) => {
-      mdeModalOpenState = false // eslint-disable-line no-undef
-      $('#modal-editor-image').removeClass('is-active')
-    },
-
-    // -------------------------------------------
-    // INSERT IMAGE
-    // -------------------------------------------
-
-    selectImage: (imageId) => {
-      vueImage.currentImage = imageId
-    },
-    insertImage: (ev) => {
-      if (mde.codemirror.doc.somethingSelected()) {
-        mde.codemirror.execCommand('singleSelection')
-      }
+'use strict'
 
-      let selImage = _.find(vueImage.images, ['_id', vueImage.currentImage])
-      selImage.normalizedPath = (selImage.folder === 'f:') ? selImage.filename : selImage.folder.slice(2) + '/' + selImage.filename
-      selImage.titleGuess = _.startCase(selImage.basename)
-
-      let imageText = '![' + selImage.titleGuess + '](/uploads/' + selImage.normalizedPath + ' "' + selImage.titleGuess + '")'
-      switch (vueImage.currentAlign) {
-        case 'center':
-          imageText += '{.align-center}'
-          break
-        case 'right':
-          imageText += '{.align-right}'
-          break
-        case 'logo':
-          imageText += '{.pagelogo}'
-          break
-      }
+import $ from 'jquery'
+import Vue from 'vue'
+import _ from 'lodash'
+import 'jquery-contextmenu'
+import 'jquery-simple-upload'
 
-      mde.codemirror.doc.replaceSelection(imageText)
-      vueImage.cancel()
+module.exports = (alerts, mde, mdeModalOpenState, socket) => {
+  let vueImage = new Vue({
+    el: '#modal-editor-image',
+    data: {
+      isLoading: false,
+      isLoadingText: '',
+      newFolderName: '',
+      newFolderShow: false,
+      newFolderError: false,
+      fetchFromUrlURL: '',
+      fetchFromUrlShow: false,
+      folders: [],
+      currentFolder: '',
+      currentImage: '',
+      currentAlign: 'left',
+      images: [],
+      uploadSucceeded: false,
+      postUploadChecks: 0,
+      renameImageShow: false,
+      renameImageId: '',
+      renameImageFilename: '',
+      deleteImageShow: false,
+      deleteImageId: '',
+      deleteImageFilename: ''
     },
+    methods: {
+
+      open: () => {
+        mdeModalOpenState = true
+        $('#modal-editor-image').addClass('is-active')
+        vueImage.refreshFolders()
+      },
+      cancel: (ev) => {
+        mdeModalOpenState = false
+        $('#modal-editor-image').removeClass('is-active')
+      },
+
+      // -------------------------------------------
+      // INSERT IMAGE
+      // -------------------------------------------
+
+      selectImage: (imageId) => {
+        vueImage.currentImage = imageId
+      },
+      insertImage: (ev) => {
+        if (mde.codemirror.doc.somethingSelected()) {
+          mde.codemirror.execCommand('singleSelection')
+        }
 
-    // -------------------------------------------
-    // NEW FOLDER
-    // -------------------------------------------
-
-    newFolder: (ev) => {
-      vueImage.newFolderName = ''
-      vueImage.newFolderError = false
-      vueImage.newFolderShow = true
-      _.delay(() => { $('#txt-editor-image-newfoldername').focus() }, 400)
-    },
-    newFolderDiscard: (ev) => {
-      vueImage.newFolderShow = false
-    },
-    newFolderCreate: (ev) => {
-      let regFolderName = new RegExp('^[a-z0-9][a-z0-9-]*[a-z0-9]$')
-      vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName))
+        let selImage = _.find(vueImage.images, ['_id', vueImage.currentImage])
+        selImage.normalizedPath = (selImage.folder === 'f:') ? selImage.filename : selImage.folder.slice(2) + '/' + selImage.filename
+        selImage.titleGuess = _.startCase(selImage.basename)
+
+        let imageText = '![' + selImage.titleGuess + '](/uploads/' + selImage.normalizedPath + ' "' + selImage.titleGuess + '")'
+        switch (vueImage.currentAlign) {
+          case 'center':
+            imageText += '{.align-center}'
+            break
+          case 'right':
+            imageText += '{.align-right}'
+            break
+          case 'logo':
+            imageText += '{.pagelogo}'
+            break
+        }
 
-      if (_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) {
-        vueImage.newFolderError = true
-        return
-      }
+        mde.codemirror.doc.replaceSelection(imageText)
+        vueImage.cancel()
+      },
+
+      // -------------------------------------------
+      // NEW FOLDER
+      // -------------------------------------------
+
+      newFolder: (ev) => {
+        vueImage.newFolderName = ''
+        vueImage.newFolderError = false
+        vueImage.newFolderShow = true
+        _.delay(() => { $('#txt-editor-image-newfoldername').focus() }, 400)
+      },
+      newFolderDiscard: (ev) => {
+        vueImage.newFolderShow = false
+      },
+      newFolderCreate: (ev) => {
+        let regFolderName = new RegExp('^[a-z0-9][a-z0-9-]*[a-z0-9]$')
+        vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName))
+
+        if (_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) {
+          vueImage.newFolderError = true
+          return
+        }
 
-      vueImage.newFolderDiscard()
-      vueImage.isLoadingText = 'Creating new folder...'
-      vueImage.isLoading = true
+        vueImage.newFolderDiscard()
+        vueImage.isLoadingText = 'Creating new folder...'
+        vueImage.isLoading = true
 
-      Vue.nextTick(() => {
-        socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => {
-          vueImage.folders = data
-          vueImage.currentFolder = vueImage.newFolderName
-          vueImage.images = []
-          vueImage.isLoading = false
+        Vue.nextTick(() => {
+          socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => {
+            vueImage.folders = data
+            vueImage.currentFolder = vueImage.newFolderName
+            vueImage.images = []
+            vueImage.isLoading = false
+          })
         })
-      })
-    },
+      },
+
+      // -------------------------------------------
+      // FETCH FROM URL
+      // -------------------------------------------
+
+      fetchFromUrl: (ev) => {
+        vueImage.fetchFromUrlURL = ''
+        vueImage.fetchFromUrlShow = true
+        _.delay(() => { $('#txt-editor-image-fetchurl').focus() }, 400)
+      },
+      fetchFromUrlDiscard: (ev) => {
+        vueImage.fetchFromUrlShow = false
+      },
+      fetchFromUrlGo: (ev) => {
+        vueImage.fetchFromUrlDiscard()
+        vueImage.isLoadingText = 'Fetching image...'
+        vueImage.isLoading = true
 
-    // -------------------------------------------
-    // FETCH FROM URL
-    // -------------------------------------------
+        Vue.nextTick(() => {
+          socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => {
+            if (data.ok) {
+              vueImage.waitChangeComplete(vueImage.images.length, true)
+            } else {
+              vueImage.isLoading = false
+              alerts.pushError('Upload error', data.msg)
+            }
+          })
+        })
+      },
+
+      // -------------------------------------------
+      // RENAME IMAGE
+      // -------------------------------------------
+
+      renameImage: () => {
+        let c = _.find(vueImage.images, [ '_id', vueImage.renameImageId ])
+        vueImage.renameImageFilename = c.basename || ''
+        vueImage.renameImageShow = true
+        _.delay(() => {
+          $('#txt-editor-image-rename').focus()
+          _.defer(() => { $('#txt-editor-image-rename').select() })
+        }, 400)
+      },
+      renameImageDiscard: () => {
+        vueImage.renameImageShow = false
+      },
+      renameImageGo: () => {
+        vueImage.renameImageDiscard()
+        vueImage.isLoadingText = 'Renaming image...'
+        vueImage.isLoading = true
 
-    fetchFromUrl: (ev) => {
-      vueImage.fetchFromUrlURL = ''
-      vueImage.fetchFromUrlShow = true
-      _.delay(() => { $('#txt-editor-image-fetchurl').focus() }, 400)
-    },
-    fetchFromUrlDiscard: (ev) => {
-      vueImage.fetchFromUrlShow = false
-    },
-    fetchFromUrlGo: (ev) => {
-      vueImage.fetchFromUrlDiscard()
-      vueImage.isLoadingText = 'Fetching image...'
-      vueImage.isLoading = true
-
-      Vue.nextTick(() => {
-        socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => {
-          if (data.ok) {
-            vueImage.waitChangeComplete(vueImage.images.length, true)
-          } else {
-            vueImage.isLoading = false
-            alerts.pushError('Upload error', data.msg)
-          }
+        Vue.nextTick(() => {
+          socket.emit('uploadsRenameFile', { uid: vueImage.renameImageId, folder: vueImage.currentFolder, filename: vueImage.renameImageFilename }, (data) => {
+            if (data.ok) {
+              vueImage.waitChangeComplete(vueImage.images.length, false)
+            } else {
+              vueImage.isLoading = false
+              alerts.pushError('Rename error', data.msg)
+            }
+          })
         })
-      })
-    },
+      },
 
-    // -------------------------------------------
-    // RENAME IMAGE
-    // -------------------------------------------
-
-    renameImage: () => {
-      let c = _.find(vueImage.images, [ '_id', vueImage.renameImageId ])
-      vueImage.renameImageFilename = c.basename || ''
-      vueImage.renameImageShow = true
-      _.delay(() => {
-        $('#txt-editor-image-rename').focus()
-        _.defer(() => { $('#txt-editor-image-rename').select() })
-      }, 400)
-    },
-    renameImageDiscard: () => {
-      vueImage.renameImageShow = false
-    },
-    renameImageGo: () => {
-      vueImage.renameImageDiscard()
-      vueImage.isLoadingText = 'Renaming image...'
-      vueImage.isLoading = true
-
-      Vue.nextTick(() => {
-        socket.emit('uploadsRenameFile', { uid: vueImage.renameImageId, folder: vueImage.currentFolder, filename: vueImage.renameImageFilename }, (data) => {
-          if (data.ok) {
-            vueImage.waitChangeComplete(vueImage.images.length, false)
-          } else {
-            vueImage.isLoading = false
-            alerts.pushError('Rename error', data.msg)
-          }
+      // -------------------------------------------
+      // MOVE IMAGE
+      // -------------------------------------------
+
+      moveImage: (uid, fld) => {
+        vueImage.isLoadingText = 'Moving image...'
+        vueImage.isLoading = true
+        Vue.nextTick(() => {
+          socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
+            if (data.ok) {
+              vueImage.loadImages()
+            } else {
+              vueImage.isLoading = false
+              alerts.pushError('Rename error', data.msg)
+            }
+          })
         })
-      })
-    },
+      },
 
-    // -------------------------------------------
-    // MOVE IMAGE
-    // -------------------------------------------
+      // -------------------------------------------
+      // DELETE IMAGE
+      // -------------------------------------------
 
-    moveImage: (uid, fld) => {
-      vueImage.isLoadingText = 'Moving image...'
-      vueImage.isLoading = true
-      Vue.nextTick(() => {
-        socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
-          if (data.ok) {
+      deleteImageWarn: (show) => {
+        if (show) {
+          let c = _.find(vueImage.images, [ '_id', vueImage.deleteImageId ])
+          vueImage.deleteImageFilename = c.filename || 'this image'
+        }
+        vueImage.deleteImageShow = show
+      },
+      deleteImageGo: () => {
+        vueImage.deleteImageWarn(false)
+        vueImage.isLoadingText = 'Deleting image...'
+        vueImage.isLoading = true
+        Vue.nextTick(() => {
+          socket.emit('uploadsDeleteFile', { uid: vueImage.deleteImageId }, (data) => {
             vueImage.loadImages()
-          } else {
-            vueImage.isLoading = false
-            alerts.pushError('Rename error', data.msg)
-          }
+          })
         })
-      })
-    },
+      },
 
-    // -------------------------------------------
-    // DELETE IMAGE
-    // -------------------------------------------
+      // -------------------------------------------
+      // LOAD FROM REMOTE
+      // -------------------------------------------
 
-    deleteImageWarn: (show) => {
-      if (show) {
-        let c = _.find(vueImage.images, [ '_id', vueImage.deleteImageId ])
-        vueImage.deleteImageFilename = c.filename || 'this image'
-      }
-      vueImage.deleteImageShow = show
-    },
-    deleteImageGo: () => {
-      vueImage.deleteImageWarn(false)
-      vueImage.isLoadingText = 'Deleting image...'
-      vueImage.isLoading = true
-      Vue.nextTick(() => {
-        socket.emit('uploadsDeleteFile', { uid: vueImage.deleteImageId }, (data) => {
-          vueImage.loadImages()
+      selectFolder: (fldName) => {
+        vueImage.currentFolder = fldName
+        vueImage.loadImages()
+      },
+
+      refreshFolders: () => {
+        vueImage.isLoadingText = 'Fetching folders list...'
+        vueImage.isLoading = true
+        vueImage.currentFolder = ''
+        vueImage.currentImage = ''
+        Vue.nextTick(() => {
+          socket.emit('uploadsGetFolders', { }, (data) => {
+            vueImage.folders = data
+            vueImage.loadImages()
+          })
         })
-      })
-    },
+      },
 
-    // -------------------------------------------
-    // LOAD FROM REMOTE
-    // -------------------------------------------
+      loadImages: (silent) => {
+        if (!silent) {
+          vueImage.isLoadingText = 'Fetching images...'
+          vueImage.isLoading = true
+        }
+        return new Promise((resolve, reject) => {
+          Vue.nextTick(() => {
+            socket.emit('uploadsGetImages', { folder: vueImage.currentFolder }, (data) => {
+              vueImage.images = data
+              if (!silent) {
+                vueImage.isLoading = false
+              }
+              vueImage.attachContextMenus()
+              resolve(true)
+            })
+          })
+        })
+      },
 
-    selectFolder: (fldName) => {
-      vueImage.currentFolder = fldName
-      vueImage.loadImages()
-    },
+      waitChangeComplete: (oldAmount, expectChange) => {
+        expectChange = (_.isBoolean(expectChange)) ? expectChange : true
 
-    refreshFolders: () => {
-      vueImage.isLoadingText = 'Fetching folders list...'
-      vueImage.isLoading = true
-      vueImage.currentFolder = ''
-      vueImage.currentImage = ''
-      Vue.nextTick(() => {
-        socket.emit('uploadsGetFolders', { }, (data) => {
-          vueImage.folders = data
-          vueImage.loadImages()
-        })
-      })
-    },
+        vueImage.postUploadChecks++
+        vueImage.isLoadingText = 'Processing...'
 
-    loadImages: (silent) => {
-      if (!silent) {
-        vueImage.isLoadingText = 'Fetching images...'
-        vueImage.isLoading = true
-      }
-      return new Promise((resolve, reject) => {
         Vue.nextTick(() => {
-          socket.emit('uploadsGetImages', { folder: vueImage.currentFolder }, (data) => {
-            vueImage.images = data
-            if (!silent) {
+          vueImage.loadImages(true).then(() => {
+            if ((vueImage.images.length !== oldAmount) === expectChange) {
+              vueImage.postUploadChecks = 0
               vueImage.isLoading = false
+            } else if (vueImage.postUploadChecks > 5) {
+              vueImage.postUploadChecks = 0
+              vueImage.isLoading = false
+              alerts.pushError('Unable to fetch updated listing', 'Try again later')
+            } else {
+              _.delay(() => {
+                vueImage.waitChangeComplete(oldAmount, expectChange)
+              }, 1500)
             }
-            vueImage.attachContextMenus()
-            resolve(true)
           })
         })
-      })
-    },
-
-    waitChangeComplete: (oldAmount, expectChange) => {
-      expectChange = (_.isBoolean(expectChange)) ? expectChange : true
+      },
 
-      vueImage.postUploadChecks++
-      vueImage.isLoadingText = 'Processing...'
+      // -------------------------------------------
+      // IMAGE CONTEXT MENU
+      // -------------------------------------------
 
-      Vue.nextTick(() => {
-        vueImage.loadImages(true).then(() => {
-          if ((vueImage.images.length !== oldAmount) === expectChange) {
-            vueImage.postUploadChecks = 0
-            vueImage.isLoading = false
-          } else if (vueImage.postUploadChecks > 5) {
-            vueImage.postUploadChecks = 0
-            vueImage.isLoading = false
-            alerts.pushError('Unable to fetch updated listing', 'Try again later')
-          } else {
-            _.delay(() => {
-              vueImage.waitChangeComplete(oldAmount, expectChange)
-            }, 1500)
+      attachContextMenus: () => {
+        let moveFolders = _.map(vueImage.folders, (f) => {
+          return {
+            name: (f !== '') ? f : '/ (root)',
+            icon: 'fa-folder',
+            callback: (key, opt) => {
+              let moveImageId = _.toString($(opt.$trigger).data('uid'))
+              let moveImageDestFolder = _.nth(vueImage.folders, key)
+              vueImage.moveImage(moveImageId, moveImageDestFolder)
+            }
           }
         })
-      })
-    },
 
-    // -------------------------------------------
-    // IMAGE CONTEXT MENU
-    // -------------------------------------------
-
-    attachContextMenus: () => {
-      let moveFolders = _.map(vueImage.folders, (f) => {
-        return {
-          name: (f !== '') ? f : '/ (root)',
-          icon: 'fa-folder',
-          callback: (key, opt) => {
-            let moveImageId = _.toString($(opt.$trigger).data('uid'))
-            let moveImageDestFolder = _.nth(vueImage.folders, key)
-            vueImage.moveImage(moveImageId, moveImageDestFolder)
-          }
-        }
-      })
-
-      $.contextMenu('destroy', '.editor-modal-image-choices > figure')
-      $.contextMenu({
-        selector: '.editor-modal-image-choices > figure',
-        appendTo: '.editor-modal-image-choices',
-        position: (opt, x, y) => {
-          $(opt.$trigger).addClass('is-contextopen')
-          let trigPos = $(opt.$trigger).position()
-          let trigDim = { w: $(opt.$trigger).width() / 2, h: $(opt.$trigger).height() / 2 }
-          opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w })
-        },
-        events: {
-          hide: (opt) => {
-            $(opt.$trigger).removeClass('is-contextopen')
-          }
-        },
-        items: {
-          rename: {
-            name: 'Rename',
-            icon: 'fa-edit',
-            callback: (key, opt) => {
-              vueImage.renameImageId = _.toString(opt.$trigger[0].dataset.uid)
-              vueImage.renameImage()
-            }
+        $.contextMenu('destroy', '.editor-modal-image-choices > figure')
+        $.contextMenu({
+          selector: '.editor-modal-image-choices > figure',
+          appendTo: '.editor-modal-image-choices',
+          position: (opt, x, y) => {
+            $(opt.$trigger).addClass('is-contextopen')
+            let trigPos = $(opt.$trigger).position()
+            let trigDim = { w: $(opt.$trigger).width() / 2, h: $(opt.$trigger).height() / 2 }
+            opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w })
           },
-          move: {
-            name: 'Move to...',
-            icon: 'fa-folder-open-o',
-            items: moveFolders
+          events: {
+            hide: (opt) => {
+              $(opt.$trigger).removeClass('is-contextopen')
+            }
           },
-          delete: {
-            name: 'Delete',
-            icon: 'fa-trash',
-            callback: (key, opt) => {
-              vueImage.deleteImageId = _.toString(opt.$trigger[0].dataset.uid)
-              vueImage.deleteImageWarn(true)
+          items: {
+            rename: {
+              name: 'Rename',
+              icon: 'fa-edit',
+              callback: (key, opt) => {
+                vueImage.renameImageId = _.toString(opt.$trigger[0].dataset.uid)
+                vueImage.renameImage()
+              }
+            },
+            move: {
+              name: 'Move to...',
+              icon: 'fa-folder-open-o',
+              items: moveFolders
+            },
+            delete: {
+              name: 'Delete',
+              icon: 'fa-trash',
+              callback: (key, opt) => {
+                vueImage.deleteImageId = _.toString(opt.$trigger[0].dataset.uid)
+                vueImage.deleteImageWarn(true)
+              }
             }
           }
-        }
-      })
-    }
-
-  }
-})
+        })
+      }
 
-$('#btn-editor-image-upload input').on('change', (ev) => {
-  let curImageAmount = vueImage.images.length
+    }
+  })
 
-  $(ev.currentTarget).simpleUpload('/uploads/img', {
+  $('#btn-editor-image-upload input').on('change', (ev) => {
+    let curImageAmount = vueImage.images.length
 
-    name: 'imgfile',
-    data: {
-      folder: vueImage.currentFolder
-    },
-    limit: 20,
-    expect: 'json',
-    allowedExts: ['jpg', 'jpeg', 'gif', 'png', 'webp'],
-    allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
-    maxFileSize: 3145728, // max 3 MB
-
-    init: (totalUploads) => {
-      vueImage.uploadSucceeded = false
-      vueImage.isLoadingText = 'Preparing to upload...'
-      vueImage.isLoading = true
-    },
+    $(ev.currentTarget).simpleUpload('/uploads/img', {
 
-    progress: (progress) => {
-      vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%'
-    },
+      name: 'imgfile',
+      data: {
+        folder: vueImage.currentFolder
+      },
+      limit: 20,
+      expect: 'json',
+      allowedExts: ['jpg', 'jpeg', 'gif', 'png', 'webp'],
+      allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
+      maxFileSize: 3145728, // max 3 MB
 
-    success: (data) => {
-      if (data.ok) {
-        let failedUpls = _.filter(data.results, ['ok', false])
-        if (failedUpls.length) {
-          _.forEach(failedUpls, (u) => {
-            alerts.pushError('Upload error', u.msg)
-          })
-          if (failedUpls.length < data.results.length) {
-            alerts.push({
-              title: 'Some uploads succeeded',
-              message: 'Files that are not mentionned in the errors above were uploaded successfully.'
+      init: (totalUploads) => {
+        vueImage.uploadSucceeded = false
+        vueImage.isLoadingText = 'Preparing to upload...'
+        vueImage.isLoading = true
+      },
+
+      progress: (progress) => {
+        vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%'
+      },
+
+      success: (data) => {
+        if (data.ok) {
+          let failedUpls = _.filter(data.results, ['ok', false])
+          if (failedUpls.length) {
+            _.forEach(failedUpls, (u) => {
+              alerts.pushError('Upload error', u.msg)
             })
+            if (failedUpls.length < data.results.length) {
+              alerts.push({
+                title: 'Some uploads succeeded',
+                message: 'Files that are not mentionned in the errors above were uploaded successfully.'
+              })
+              vueImage.uploadSucceeded = true
+            }
+          } else {
             vueImage.uploadSucceeded = true
           }
         } else {
-          vueImage.uploadSucceeded = true
+          alerts.pushError('Upload error', data.msg)
         }
-      } else {
-        alerts.pushError('Upload error', data.msg)
-      }
-    },
+      },
 
-    error: (error) => {
-      alerts.pushError(error.message, this.upload.file.name)
-    },
+      error: (error) => {
+        alerts.pushError(error.message, this.upload.file.name)
+      },
 
-    finish: () => {
-      if (vueImage.uploadSucceeded) {
-        vueImage.waitChangeComplete(curImageAmount, true)
-      } else {
-        vueImage.isLoading = false
+      finish: () => {
+        if (vueImage.uploadSucceeded) {
+          vueImage.waitChangeComplete(curImageAmount, true)
+        } else {
+          vueImage.isLoading = false
+        }
       }
-    }
 
+    })
   })
-})
+  return vueImage
+}

+ 45 - 38
client/js/components/editor-video.js

@@ -1,4 +1,8 @@
-/* global $, Vue, mde, _ */
+'use strict'
+
+import $ from 'jquery'
+import Vue from 'vue'
+import _ from 'lodash'
 
 const videoRules = {
   'youtube': new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/, 'i'),
@@ -6,43 +10,46 @@ const videoRules = {
   'dailymotion': new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i')
 }
 
-// Vue Video instance
-
-let vueVideo = new Vue({
-  el: '#modal-editor-video',
-  data: {
-    link: ''
-  },
-  methods: {
-    open: (ev) => {
-      $('#modal-editor-video').addClass('is-active')
-      $('#modal-editor-video input').focus()
-    },
-    cancel: (ev) => {
-      mdeModalOpenState = false // eslint-disable-line no-undef
-      $('#modal-editor-video').removeClass('is-active')
-      vueVideo.link = ''
-    },
-    insertVideo: (ev) => {
-      if (mde.codemirror.doc.somethingSelected()) {
-        mde.codemirror.execCommand('singleSelection')
-      }
-
-      // Guess video type
+module.exports = (mde, mdeModalOpenState) => {
+  // Vue Video instance
 
-      let videoType = _.findKey(videoRules, (vr) => {
-        return vr.test(vueVideo.link)
-      })
-      if (_.isNil(videoType)) {
-        videoType = 'video'
+  let vueVideo = new Vue({
+    el: '#modal-editor-video',
+    data: {
+      link: ''
+    },
+    methods: {
+      open: (ev) => {
+        $('#modal-editor-video').addClass('is-active')
+        $('#modal-editor-video input').focus()
+      },
+      cancel: (ev) => {
+        mdeModalOpenState = false // eslint-disable-line no-undef
+        $('#modal-editor-video').removeClass('is-active')
+        vueVideo.link = ''
+      },
+      insertVideo: (ev) => {
+        if (mde.codemirror.doc.somethingSelected()) {
+          mde.codemirror.execCommand('singleSelection')
+        }
+
+        // Guess video type
+
+        let videoType = _.findKey(videoRules, (vr) => {
+          return vr.test(vueVideo.link)
+        })
+        if (_.isNil(videoType)) {
+          videoType = 'video'
+        }
+
+        // Insert video tag
+
+        let videoText = '[video](' + vueVideo.link + '){.' + videoType + '}\n'
+
+        mde.codemirror.doc.replaceSelection(videoText)
+        vueVideo.cancel()
       }
-
-      // Insert video tag
-
-      let videoText = '[video](' + vueVideo.link + '){.' + videoType + '}\n'
-
-      mde.codemirror.doc.replaceSelection(videoText)
-      vueVideo.cancel()
     }
-  }
-})
+  })
+  return vueVideo
+}

+ 203 - 200
client/js/components/editor.js

@@ -1,220 +1,223 @@
 'use strict'
 
-/* global $, Vue, _, filesize, SimpleMDE, alerts, vueImage, vueFile, vueVideo, vueCodeBlock */
+import $ from 'jquery'
+import Vue from 'vue'
+import _ from 'lodash'
+import filesize from 'filesize.js'
+import SimpleMDE from 'simplemde'
 
 // ====================================
 // Markdown Editor
 // ====================================
 
-if ($('#mk-editor').length === 1) {
-  let mdeModalOpenState = false
-  let mdeCurrentEditor = null // eslint-disable-line no-unused-vars
+module.exports = (alerts, pageEntryPath, socket) => {
+  if ($('#mk-editor').length === 1) {
+    Vue.filter('filesize', (v) => {
+      return _.toUpper(filesize(v))
+    })
 
-  Vue.filter('filesize', (v) => {
-    return _.toUpper(filesize(v))
-  })
+    let mde
+    let mdeModalOpenState = false
+    let vueImage = require('./editor-image.js')(alerts, mde, mdeModalOpenState, socket)
+    let vueFile = require('./editor-file.js')(alerts, mde, mdeModalOpenState, socket)
+    let vueVideo = require('./editor-video.js')(mde, mdeModalOpenState)
+    let vueCodeBlock = require('./editor-codeblock.js')(mde, mdeModalOpenState)
 
-  /* eslint-disable spaced-comment */
-  //=include editor-image.js
-  //=include editor-file.js
-  //=include editor-video.js
-  //=include editor-codeblock.js
-  /* eslint-enable spaced-comment */
+    mde = new SimpleMDE({
+      autofocus: true,
+      autoDownloadFontAwesome: false,
+      element: $('#mk-editor').get(0),
+      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!')
+          },
+          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 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)
+          },
+          className: 'icon-terminal',
+          title: 'Inline Code'
+        },
+        {
+          name: 'code-block',
+          action: (editor) => {
+            if (!mdeModalOpenState) {
+              mdeModalOpenState = true
 
-  var mde = new SimpleMDE({
-    autofocus: true,
-    autoDownloadFontAwesome: false,
-    element: $('#mk-editor').get(0),
-    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!')
-        },
-        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 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)
-        },
-        className: 'icon-terminal',
-        title: 'Inline Code'
-      },
-      {
-        name: 'code-block',
-        action: (editor) => {
-          if (!mdeModalOpenState) {
-            mdeModalOpenState = true
+              if (mde.codemirror.doc.somethingSelected()) {
+                vueCodeBlock.initContent = mde.codemirror.doc.getSelection()
+              }
 
-            if (mde.codemirror.doc.somethingSelected()) {
-              vueCodeBlock.initContent = mde.codemirror.doc.getSelection()
+              vueCodeBlock.open()
             }
-
-            vueCodeBlock.open()
-          }
-        },
-        className: 'icon-code',
-        title: 'Code Block'
-      },
-      '|',
-      {
-        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'
+          },
+          className: 'icon-code',
+          title: 'Code Block'
+        },
+        '|',
+        {
+          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
+
+    let saveCurrentDocument = (ev) => {
+      $.ajax(window.location.href, {
+        data: {
+          markdown: mde.value()
+        },
+        dataType: 'json',
+        method: 'PUT'
+      }).then((rData, rStatus, rXHR) => {
+        if (rData.ok) {
+          window.location.assign('/' + pageEntryPath) // eslint-disable-line no-undef
+        } else {
+          alerts.pushError('Something went wrong', rData.error)
+        }
+      }, (rXHR, rStatus, err) => {
+        alerts.pushError('Something went wrong', 'Save operation failed.')
+      })
     }
-  })
 
-  // -> Save
+    $('.btn-edit-save, .btn-create-save').on('click', (ev) => {
+      saveCurrentDocument(ev)
+    })
 
-  let saveCurrentDocument = (ev) => {
-    $.ajax(window.location.href, {
-      data: {
-        markdown: mde.value()
-      },
-      dataType: 'json',
-      method: 'PUT'
-    }).then((rData, rStatus, rXHR) => {
-      if (rData.ok) {
-        window.location.assign('/' + pageEntryPath) // eslint-disable-line no-undef
-      } else {
-        alerts.pushError('Something went wrong', rData.error)
+    $(window).bind('keydown', (ev) => {
+      if (ev.ctrlKey || ev.metaKey) {
+        switch (String.fromCharCode(ev.which).toLowerCase()) {
+          case 's':
+            ev.preventDefault()
+            saveCurrentDocument(ev)
+            break
+        }
       }
-    }, (rXHR, rStatus, err) => {
-      alerts.pushError('Something went wrong', 'Save operation failed.')
     })
   }
-
-  $('.btn-edit-save, .btn-create-save').on('click', (ev) => {
-    saveCurrentDocument(ev)
-  })
-
-  $(window).bind('keydown', (ev) => {
-    if (ev.ctrlKey || ev.metaKey) {
-      switch (String.fromCharCode(ev.which).toLowerCase()) {
-        case 's':
-          ev.preventDefault()
-          saveCurrentDocument(ev)
-          break
-      }
-    }
-  })
 }

+ 74 - 70
client/js/components/search.js

@@ -1,83 +1,87 @@
 'use strict'
 
-/* global $, Vue, _, socket */
+import $ from 'jquery'
+import _ from 'lodash'
+import Vue from 'vue'
 
-if ($('#search-input').length) {
-  $('#search-input').focus()
+module.exports = (socket) => {
+  if ($('#search-input').length) {
+    $('#search-input').focus()
 
-  $('.searchresults').css('display', 'block')
+    $('.searchresults').css('display', 'block')
 
-  var vueHeader = new Vue({
-    el: '#header-container',
-    data: {
-      searchq: '',
-      searchres: [],
-      searchsuggest: [],
-      searchload: 0,
-      searchactive: false,
-      searchmoveidx: 0,
-      searchmovekey: '',
-      searchmovearr: []
-    },
-    watch: {
-      searchq: (val, oldVal) => {
-        vueHeader.searchmoveidx = 0
-        if (val.length >= 3) {
-          vueHeader.searchactive = true
-          vueHeader.searchload++
-          socket.emit('search', { terms: val }, (data) => {
-            vueHeader.searchres = data.match
-            vueHeader.searchsuggest = data.suggest
-            vueHeader.searchmovearr = _.concat([], vueHeader.searchres, vueHeader.searchsuggest)
-            if (vueHeader.searchload > 0) { vueHeader.searchload-- }
-          })
-        } else {
-          vueHeader.searchactive = false
-          vueHeader.searchres = []
-          vueHeader.searchsuggest = []
-          vueHeader.searchmovearr = []
-          vueHeader.searchload = 0
-        }
+    var vueHeader = new Vue({
+      el: '#header-container',
+      data: {
+        searchq: '',
+        searchres: [],
+        searchsuggest: [],
+        searchload: 0,
+        searchactive: false,
+        searchmoveidx: 0,
+        searchmovekey: '',
+        searchmovearr: []
       },
-      searchmoveidx: (val, oldVal) => {
-        if (val > 0) {
-          vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1])
-            ? 'res.' + vueHeader.searchmovearr[val - 1].entryPath
-            : 'sug.' + vueHeader.searchmovearr[val - 1]
-        } else {
-          vueHeader.searchmovekey = ''
+      watch: {
+        searchq: (val, oldVal) => {
+          vueHeader.searchmoveidx = 0
+          if (val.length >= 3) {
+            vueHeader.searchactive = true
+            vueHeader.searchload++
+            socket.emit('search', { terms: val }, (data) => {
+              vueHeader.searchres = data.match
+              vueHeader.searchsuggest = data.suggest
+              vueHeader.searchmovearr = _.concat([], vueHeader.searchres, vueHeader.searchsuggest)
+              if (vueHeader.searchload > 0) { vueHeader.searchload-- }
+            })
+          } else {
+            vueHeader.searchactive = false
+            vueHeader.searchres = []
+            vueHeader.searchsuggest = []
+            vueHeader.searchmovearr = []
+            vueHeader.searchload = 0
+          }
+        },
+        searchmoveidx: (val, oldVal) => {
+          if (val > 0) {
+            vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1])
+              ? 'res.' + vueHeader.searchmovearr[val - 1].entryPath
+              : 'sug.' + vueHeader.searchmovearr[val - 1]
+          } else {
+            vueHeader.searchmovekey = ''
+          }
         }
-      }
-    },
-    methods: {
-      useSuggestion: (sug) => {
-        vueHeader.searchq = sug
       },
-      closeSearch: () => {
-        vueHeader.searchq = ''
-      },
-      moveSelectSearch: () => {
-        if (vueHeader.searchmoveidx < 1) { return }
-        let i = vueHeader.searchmoveidx - 1
+      methods: {
+        useSuggestion: (sug) => {
+          vueHeader.searchq = sug
+        },
+        closeSearch: () => {
+          vueHeader.searchq = ''
+        },
+        moveSelectSearch: () => {
+          if (vueHeader.searchmoveidx < 1) { return }
+          let i = vueHeader.searchmoveidx - 1
 
-        if (vueHeader.searchmovearr[i]) {
-          window.location.assign('/' + vueHeader.searchmovearr[i].entryPath)
-        } else {
-          vueHeader.searchq = vueHeader.searchmovearr[i]
-        }
-      },
-      moveDownSearch: () => {
-        if (vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
-          vueHeader.searchmoveidx++
-        }
-      },
-      moveUpSearch: () => {
-        if (vueHeader.searchmoveidx > 0) {
-          vueHeader.searchmoveidx--
+          if (vueHeader.searchmovearr[i]) {
+            window.location.assign('/' + vueHeader.searchmovearr[i].entryPath)
+          } else {
+            vueHeader.searchq = vueHeader.searchmovearr[i]
+          }
+        },
+        moveDownSearch: () => {
+          if (vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
+            vueHeader.searchmoveidx++
+          }
+        },
+        moveUpSearch: () => {
+          if (vueHeader.searchmoveidx > 0) {
+            vueHeader.searchmoveidx--
+          }
         }
       }
-    }
-  })
+    })
 
-  $('main').on('click', vueHeader.closeSearch)
+    $('main').on('click', vueHeader.closeSearch)
+  }
 }

+ 22 - 16
client/js/helpers/form.js

@@ -1,19 +1,25 @@
-/* eslint-disable no-unused-vars */
+'use strict'
 
-function setInputSelection (input, startPos, endPos) {
-  input.focus()
-  if (typeof input.selectionStart !== 'undefined') {
-    input.selectionStart = startPos
-    input.selectionEnd = endPos
-  } else if (document.selection && document.selection.createRange) {
-        // IE branch
-    input.select()
-    var range = document.selection.createRange()
-    range.collapse(true)
-    range.moveEnd('character', endPos)
-    range.moveStart('character', startPos)
-    range.select()
+module.exports = {
+  /**
+   * Set Input Selection
+   * @param {DOMElement} input The input element
+   * @param {number} startPos The starting position
+   * @param {nunber} endPos The ending position
+   */
+  setInputSelection: (input, startPos, endPos) => {
+    input.focus()
+    if (typeof input.selectionStart !== 'undefined') {
+      input.selectionStart = startPos
+      input.selectionEnd = endPos
+    } else if (document.selection && document.selection.createRange) {
+      // IE branch
+      input.select()
+      var range = document.selection.createRange()
+      range.collapse(true)
+      range.moveEnd('character', endPos)
+      range.moveStart('character', startPos)
+      range.select()
+    }
   }
 }
-
-/* eslint-enable no-unused-vars */

+ 16 - 10
client/js/helpers/pages.js

@@ -1,13 +1,19 @@
-/* global _ */
-/* eslint-disable no-unused-vars */
+'use strict'
 
-function makeSafePath (rawPath) {
-  let rawParts = _.split(_.trim(rawPath), '/')
-  rawParts = _.map(rawParts, (r) => {
-    return _.kebabCase(_.deburr(_.trim(r)))
-  })
+import _ from 'lodash'
 
-  return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r) }), '/')
-}
+module.exports = {
+  /**
+   * Convert raw path to safe path
+   * @param {string} rawPath Raw path
+   * @returns {string} Safe path
+   */
+  makeSafePath: (rawPath) => {
+    let rawParts = _.split(_.trim(rawPath), '/')
+    rawParts = _.map(rawParts, (r) => {
+      return _.kebabCase(_.deburr(_.trim(r)))
+    })
 
-/* eslint-enable no-unused-vars */
+    return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r) }), '/')
+  }
+}

+ 50 - 45
client/js/modals/admin-users-create.js

@@ -1,51 +1,56 @@
-/* global $, Vue, alerts */
+'use strict'
+
+import $ from 'jquery'
+import Vue from 'vue'
 
 // Vue Create User instance
 
-let vueCreateUser = new Vue({
-  el: '#modal-admin-users-create',
-  data: {
-    email: '',
-    provider: 'local',
-    password: '',
-    name: '',
-    loading: false
-  },
-  methods: {
-    open: (ev) => {
-      $('#modal-admin-users-create').addClass('is-active')
-      $('#modal-admin-users-create input').first().focus()
-    },
-    cancel: (ev) => {
-      $('#modal-admin-users-create').removeClass('is-active')
-      vueCreateUser.email = ''
-      vueCreateUser.provider = 'local'
+module.exports = (alerts) => {
+  let vueCreateUser = new Vue({
+    el: '#modal-admin-users-create',
+    data: {
+      email: '',
+      provider: 'local',
+      password: '',
+      name: '',
+      loading: false
     },
-    create: (ev) => {
-      vueCreateUser.loading = true
-      $.ajax('/admin/users/create', {
-        data: {
-          email: vueCreateUser.email,
-          provider: vueCreateUser.provider,
-          password: vueCreateUser.password,
-          name: vueCreateUser.name
-        },
-        dataType: 'json',
-        method: 'POST'
-      }).then((rData, rStatus, rXHR) => {
-        vueCreateUser.loading = false
-        if (rData.ok) {
-          vueCreateUser.cancel()
-          window.location.reload(true)
-        } else {
-          alerts.pushError('Something went wrong', rData.msg)
-        }
-      }, (rXHR, rStatus, err) => {
-        vueCreateUser.loading = false
-        alerts.pushError('Error', rXHR.responseJSON.msg)
-      })
+    methods: {
+      open: (ev) => {
+        $('#modal-admin-users-create').addClass('is-active')
+        $('#modal-admin-users-create input').first().focus()
+      },
+      cancel: (ev) => {
+        $('#modal-admin-users-create').removeClass('is-active')
+        vueCreateUser.email = ''
+        vueCreateUser.provider = 'local'
+      },
+      create: (ev) => {
+        vueCreateUser.loading = true
+        $.ajax('/admin/users/create', {
+          data: {
+            email: vueCreateUser.email,
+            provider: vueCreateUser.provider,
+            password: vueCreateUser.password,
+            name: vueCreateUser.name
+          },
+          dataType: 'json',
+          method: 'POST'
+        }).then((rData, rStatus, rXHR) => {
+          vueCreateUser.loading = false
+          if (rData.ok) {
+            vueCreateUser.cancel()
+            window.location.reload(true)
+          } else {
+            alerts.pushError('Something went wrong', rData.msg)
+          }
+        }, (rXHR, rStatus, err) => {
+          vueCreateUser.loading = false
+          alerts.pushError('Error', rXHR.responseJSON.msg)
+        })
+      }
     }
-  }
-})
+  })
 
-$('.btn-create-prompt').on('click', vueCreateUser.open)
+  $('.btn-create-prompt').on('click', vueCreateUser.open)
+}

+ 37 - 28
client/js/modals/admin-users-delete.js

@@ -1,34 +1,43 @@
-/* global $, Vue, usrData, alerts */
+'use strict'
+
+/* global usrData */
+
+'use strict'
+
+import $ from 'jquery'
+import Vue from 'vue'
 
 // Vue Delete User instance
 
-let vueDeleteUser = new Vue({
-  el: '#modal-admin-users-delete',
-  data: {
-    loading: false
-  },
-  methods: {
-    open: (ev) => {
-      $('#modal-admin-users-delete').addClass('is-active')
-    },
-    cancel: (ev) => {
-      $('#modal-admin-users-delete').removeClass('is-active')
+module.exports = (alerts) => {
+  let vueDeleteUser = new Vue({
+    el: '#modal-admin-users-delete',
+    data: {
+      loading: false
     },
-    deleteUser: (ev) => {
-      vueDeleteUser.loading = true
-      $.ajax('/admin/users/' + usrData._id, {
-        dataType: 'json',
-        method: 'DELETE'
-      }).then((rData, rStatus, rXHR) => {
-        vueDeleteUser.loading = false
-        vueDeleteUser.cancel()
-        window.location.assign('/admin/users')
-      }, (rXHR, rStatus, err) => {
-        vueDeleteUser.loading = false
-        alerts.pushError('Error', rXHR.responseJSON.msg)
-      })
+    methods: {
+      open: (ev) => {
+        $('#modal-admin-users-delete').addClass('is-active')
+      },
+      cancel: (ev) => {
+        $('#modal-admin-users-delete').removeClass('is-active')
+      },
+      deleteUser: (ev) => {
+        vueDeleteUser.loading = true
+        $.ajax('/admin/users/' + usrData._id, {
+          dataType: 'json',
+          method: 'DELETE'
+        }).then((rData, rStatus, rXHR) => {
+          vueDeleteUser.loading = false
+          vueDeleteUser.cancel()
+          window.location.assign('/admin/users')
+        }, (rXHR, rStatus, err) => {
+          vueDeleteUser.loading = false
+          alerts.pushError('Error', rXHR.responseJSON.msg)
+        })
+      }
     }
-  }
-})
+  })
 
-$('.btn-deluser-prompt').on('click', vueDeleteUser.open)
+  $('.btn-deluser-prompt').on('click', vueDeleteUser.open)
+}

+ 29 - 22
client/js/modals/create.js

@@ -1,28 +1,35 @@
-/* global $, _, currentBasePath */
+'use strict'
+
+import $ from 'jquery'
+import _ from 'lodash'
+import { setInputSelection } from '../helpers/form'
+import { makeSafePath } from '../helpers/pages'
 
 // -> Create New Document
 
-let suggestedCreatePath = currentBasePath + '/new-page'
+module.exports = (currentBasePath) => {
+  let suggestedCreatePath = currentBasePath + '/new-page'
 
-$('.btn-create-prompt').on('click', (ev) => {
-  $('#txt-create-prompt').val(suggestedCreatePath)
-  $('#modal-create-prompt').toggleClass('is-active')
-  setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length + 1, suggestedCreatePath.length) // eslint-disable-line no-undef
-  $('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden')
-})
+  $('.btn-create-prompt').on('click', (ev) => {
+    $('#txt-create-prompt').val(suggestedCreatePath)
+    $('#modal-create-prompt').toggleClass('is-active')
+    setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length + 1, suggestedCreatePath.length)
+    $('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden')
+  })
 
-$('#txt-create-prompt').on('keypress', (ev) => {
-  if (ev.which === 13) {
-    $('.btn-create-go').trigger('click')
-  }
-})
+  $('#txt-create-prompt').on('keypress', (ev) => {
+    if (ev.which === 13) {
+      $('.btn-create-go').trigger('click')
+    }
+  })
 
-$('.btn-create-go').on('click', (ev) => {
-  let newDocPath = makeSafePath($('#txt-create-prompt').val()) // eslint-disable-line no-undef
-  if (_.isEmpty(newDocPath)) {
-    $('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden')
-  } else {
-    $('#txt-create-prompt').parent().addClass('is-loading')
-    window.location.assign('/create/' + newDocPath)
-  }
-})
+  $('.btn-create-go').on('click', (ev) => {
+    let newDocPath = makeSafePath($('#txt-create-prompt').val())
+    if (_.isEmpty(newDocPath)) {
+      $('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden')
+    } else {
+      $('#txt-create-prompt').parent().addClass('is-loading')
+      window.location.assign('/create/' + newDocPath)
+    }
+  })
+}

+ 48 - 41
client/js/modals/move.js

@@ -1,47 +1,54 @@
-/* global $, _, alerts, currentBasePath */
+'use strict'
+
+import $ from 'jquery'
+import _ from 'lodash'
+import { makeSafePath } from '../helpers/form'
+import { setInputSelection } from '../helpers/pages'
 
 // -> Move Existing Document
 
-if (currentBasePath !== '') {
-  $('.btn-move-prompt').removeClass('is-hidden')
-}
+module.exports = (currentBasePath, alerts) => {
+  if (currentBasePath !== '') {
+    $('.btn-move-prompt').removeClass('is-hidden')
+  }
 
-let moveInitialDocument = _.lastIndexOf(currentBasePath, '/') + 1
+  let moveInitialDocument = _.lastIndexOf(currentBasePath, '/') + 1
 
-$('.btn-move-prompt').on('click', (ev) => {
-  $('#txt-move-prompt').val(currentBasePath)
-  $('#modal-move-prompt').toggleClass('is-active')
-  setInputSelection($('#txt-move-prompt').get(0), moveInitialDocument, currentBasePath.length) // eslint-disable-line no-undef
-  $('#txt-move-prompt').removeClass('is-danger').next().addClass('is-hidden')
-})
+  $('.btn-move-prompt').on('click', (ev) => {
+    $('#txt-move-prompt').val(currentBasePath)
+    $('#modal-move-prompt').toggleClass('is-active')
+    setInputSelection($('#txt-move-prompt').get(0), moveInitialDocument, currentBasePath.length)
+    $('#txt-move-prompt').removeClass('is-danger').next().addClass('is-hidden')
+  })
 
-$('#txt-move-prompt').on('keypress', (ev) => {
-  if (ev.which === 13) {
-    $('.btn-move-go').trigger('click')
-  }
-})
-
-$('.btn-move-go').on('click', (ev) => {
-  let newDocPath = makeSafePath($('#txt-move-prompt').val()) // eslint-disable-line no-undef
-  if (_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') {
-    $('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden')
-  } else {
-    $('#txt-move-prompt').parent().addClass('is-loading')
-
-    $.ajax(window.location.href, {
-      data: {
-        move: newDocPath
-      },
-      dataType: 'json',
-      method: 'PUT'
-    }).then((rData, rStatus, rXHR) => {
-      if (rData.ok) {
-        window.location.assign('/' + newDocPath)
-      } else {
-        alerts.pushError('Something went wrong', rData.error)
-      }
-    }, (rXHR, rStatus, err) => {
-      alerts.pushError('Something went wrong', 'Save operation failed.')
-    })
-  }
-})
+  $('#txt-move-prompt').on('keypress', (ev) => {
+    if (ev.which === 13) {
+      $('.btn-move-go').trigger('click')
+    }
+  })
+
+  $('.btn-move-go').on('click', (ev) => {
+    let newDocPath = makeSafePath($('#txt-move-prompt').val())
+    if (_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') {
+      $('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden')
+    } else {
+      $('#txt-move-prompt').parent().addClass('is-loading')
+
+      $.ajax(window.location.href, {
+        data: {
+          move: newDocPath
+        },
+        dataType: 'json',
+        method: 'PUT'
+      }).then((rData, rStatus, rXHR) => {
+        if (rData.ok) {
+          window.location.assign('/' + newDocPath)
+        } else {
+          alerts.pushError('Something went wrong', rData.error)
+        }
+      }, (rXHR, rStatus, err) => {
+        alerts.pushError('Something went wrong', 'Save operation failed.')
+      })
+    }
+  })
+}

+ 138 - 137
client/js/pages/admin.js

@@ -1,148 +1,149 @@
-/* global $, Vue, alerts, _, usrData, usrDataName */
+'use strict'
 
-if ($('#page-type-admin-profile').length) {
-  let vueProfile = new Vue({
-    el: '#page-type-admin-profile',
-    data: {
-      password: '********',
-      passwordVerify: '********',
-      name: ''
-    },
-    methods: {
-      saveUser: (ev) => {
-        if (vueProfile.password !== vueProfile.passwordVerify) {
-          alerts.pushError('Error', "Passwords don't match!")
-          return
-        }
-        $.post(window.location.href, {
-          password: vueProfile.password,
-          name: vueProfile.name
-        }).done((resp) => {
-          alerts.pushSuccess('Saved successfully', 'Changes have been applied.')
-        }).fail((jqXHR, txtStatus, resp) => {
-          alerts.pushError('Error', resp)
-        })
-      }
-    },
-    created: function () {
-      this.name = usrDataName
-    }
-  })
-} else if ($('#page-type-admin-users').length) {
+/* global usrData, usrDataName */
 
-  /* eslint-disable spaced-comment */
-  //=include ../modals/admin-users-create.js
-  /* eslint-enable spaced-comment */
+import $ from 'jquery'
+import _ from 'lodash'
+import Vue from 'vue'
 
-} else if ($('#page-type-admin-users-edit').length) {
-  let vueEditUser = new Vue({
-    el: '#page-type-admin-users-edit',
-    data: {
-      id: '',
-      email: '',
-      password: '********',
-      name: '',
-      rights: [],
-      roleoverride: 'none'
-    },
-    methods: {
-      addRightsRow: (ev) => {
-        vueEditUser.rights.push({
-          role: 'write',
-          path: '/',
-          exact: false,
-          deny: false
-        })
-      },
-      removeRightsRow: (idx) => {
-        _.pullAt(vueEditUser.rights, idx)
-        vueEditUser.$forceUpdate()
+module.exports = (alerts) => {
+  if ($('#page-type-admin-profile').length) {
+    let vueProfile = new Vue({
+      el: '#page-type-admin-profile',
+      data: {
+        password: '********',
+        passwordVerify: '********',
+        name: ''
       },
-      saveUser: (ev) => {
-        let formattedRights = _.cloneDeep(vueEditUser.rights)
-        switch (vueEditUser.roleoverride) {
-          case 'admin':
-            formattedRights.push({
-              role: 'admin',
-              path: '/',
-              exact: false,
-              deny: false
-            })
-            break
+      methods: {
+        saveUser: (ev) => {
+          if (vueProfile.password !== vueProfile.passwordVerify) {
+            alerts.pushError('Error', "Passwords don't match!")
+            return
+          }
+          $.post(window.location.href, {
+            password: vueProfile.password,
+            name: vueProfile.name
+          }).done((resp) => {
+            alerts.pushSuccess('Saved successfully', 'Changes have been applied.')
+          }).fail((jqXHR, txtStatus, resp) => {
+            alerts.pushError('Error', resp)
+          })
         }
-        $.post(window.location.href, {
-          password: vueEditUser.password,
-          name: vueEditUser.name,
-          rights: JSON.stringify(formattedRights)
-        }).done((resp) => {
-          alerts.pushSuccess('Saved successfully', 'Changes have been applied.')
-        }).fail((jqXHR, txtStatus, resp) => {
-          alerts.pushError('Error', resp)
-        })
-      }
-    },
-    created: function () {
-      this.id = usrData._id
-      this.email = usrData.email
-      this.name = usrData.name
-
-      if (_.find(usrData.rights, { role: 'admin' })) {
-        this.rights = _.reject(usrData.rights, ['role', 'admin'])
-        this.roleoverride = 'admin'
-      } else {
-        this.rights = usrData.rights
-      }
-    }
-  })
-
-  /* eslint-disable spaced-comment */
-  //=include ../modals/admin-users-delete.js
-  /* eslint-enable spaced-comment */
-} else if ($('#page-type-admin-settings').length) {
-  let vueSettings = new Vue({ // eslint-disable-line no-unused-vars
-    el: '#page-type-admin-settings',
-    data: {
-      upgradeModal: {
-        state: false,
-        step: 'confirm',
-        mode: 'upgrade',
-        error: 'Something went wrong.'
-      }
-    },
-    methods: {
-      upgrade: (ev) => {
-        vueSettings.upgradeModal.mode = 'upgrade'
-        vueSettings.upgradeModal.step = 'confirm'
-        vueSettings.upgradeModal.state = true
-      },
-      reinstall: (ev) => {
-        vueSettings.upgradeModal.mode = 're-install'
-        vueSettings.upgradeModal.step = 'confirm'
-        vueSettings.upgradeModal.state = true
       },
-      upgradeCancel: (ev) => {
-        vueSettings.upgradeModal.state = false
-      },
-      upgradeStart: (ev) => {
-        vueSettings.upgradeModal.step = 'running'
-        $.post('/admin/settings/install', {
-          mode: vueSettings.upgradeModal.mode
-        }).done((resp) => {
-          // todo
-        }).fail((jqXHR, txtStatus, resp) => {
-          vueSettings.upgradeModal.step = 'error'
-          vueSettings.upgradeModal.error = jqXHR.responseText
-        })
+      created: function () {
+        this.name = usrDataName
+      }
+    })
+  } else if ($('#page-type-admin-users').length) {
+    require('../modals/admin-users-create.js')(alerts)
+  } else if ($('#page-type-admin-users-edit').length) {
+    let vueEditUser = new Vue({
+      el: '#page-type-admin-users-edit',
+      data: {
+        id: '',
+        email: '',
+        password: '********',
+        name: '',
+        rights: [],
+        roleoverride: 'none'
       },
-      flushcache: (ev) => {
-        window.alert('Coming soon!')
+      methods: {
+        addRightsRow: (ev) => {
+          vueEditUser.rights.push({
+            role: 'write',
+            path: '/',
+            exact: false,
+            deny: false
+          })
+        },
+        removeRightsRow: (idx) => {
+          _.pullAt(vueEditUser.rights, idx)
+          vueEditUser.$forceUpdate()
+        },
+        saveUser: (ev) => {
+          let formattedRights = _.cloneDeep(vueEditUser.rights)
+          switch (vueEditUser.roleoverride) {
+            case 'admin':
+              formattedRights.push({
+                role: 'admin',
+                path: '/',
+                exact: false,
+                deny: false
+              })
+              break
+          }
+          $.post(window.location.href, {
+            password: vueEditUser.password,
+            name: vueEditUser.name,
+            rights: JSON.stringify(formattedRights)
+          }).done((resp) => {
+            alerts.pushSuccess('Saved successfully', 'Changes have been applied.')
+          }).fail((jqXHR, txtStatus, resp) => {
+            alerts.pushError('Error', resp)
+          })
+        }
       },
-      resetaccounts: (ev) => {
-        window.alert('Coming soon!')
+      created: function () {
+        this.id = usrData._id
+        this.email = usrData.email
+        this.name = usrData.name
+
+        if (_.find(usrData.rights, { role: 'admin' })) {
+          this.rights = _.reject(usrData.rights, ['role', 'admin'])
+          this.roleoverride = 'admin'
+        } else {
+          this.rights = usrData.rights
+        }
+      }
+    })
+    require('../modals/admin-users-delete.js')(alerts)
+  } else if ($('#page-type-admin-settings').length) {
+    let vueSettings = new Vue({ // eslint-disable-line no-unused-vars
+      el: '#page-type-admin-settings',
+      data: {
+        upgradeModal: {
+          state: false,
+          step: 'confirm',
+          mode: 'upgrade',
+          error: 'Something went wrong.'
+        }
       },
-      flushsessions: (ev) => {
-        window.alert('Coming soon!')
+      methods: {
+        upgrade: (ev) => {
+          vueSettings.upgradeModal.mode = 'upgrade'
+          vueSettings.upgradeModal.step = 'confirm'
+          vueSettings.upgradeModal.state = true
+        },
+        reinstall: (ev) => {
+          vueSettings.upgradeModal.mode = 're-install'
+          vueSettings.upgradeModal.step = 'confirm'
+          vueSettings.upgradeModal.state = true
+        },
+        upgradeCancel: (ev) => {
+          vueSettings.upgradeModal.state = false
+        },
+        upgradeStart: (ev) => {
+          vueSettings.upgradeModal.step = 'running'
+          $.post('/admin/settings/install', {
+            mode: vueSettings.upgradeModal.mode
+          }).done((resp) => {
+            // todo
+          }).fail((jqXHR, txtStatus, resp) => {
+            vueSettings.upgradeModal.step = 'error'
+            vueSettings.upgradeModal.error = jqXHR.responseText
+          })
+        },
+        flushcache: (ev) => {
+          window.alert('Coming soon!')
+        },
+        resetaccounts: (ev) => {
+          window.alert('Coming soon!')
+        },
+        flushsessions: (ev) => {
+          window.alert('Coming soon!')
+        }
       }
-    }
-  })
+    })
+  }
 }

+ 16 - 14
client/js/pages/edit.js

@@ -1,20 +1,22 @@
-/* global $ */
+'use strict'
 
-if ($('#page-type-edit').length) {
-  let pageEntryPath = $('#page-type-edit').data('entrypath') // eslint-disable-line no-unused-vars
-  // let pageCleanExit = false
+import $ from 'jquery'
 
-  // -> Discard
+module.exports = (alerts, socket) => {
+  if ($('#page-type-edit').length) {
+    let pageEntryPath = $('#page-type-edit').data('entrypath')
+    // let pageCleanExit = false
 
-  $('.btn-edit-discard').on('click', (ev) => {
-    $('#modal-edit-discard').toggleClass('is-active')
-  })
+    // -> Discard
 
-  // window.onbeforeunload = function () {
-  //   return (pageCleanExit) ? true : 'Unsaved modifications will be lost. Are you sure you want to navigate away from this page?'
-  // }
+    $('.btn-edit-discard').on('click', (ev) => {
+      $('#modal-edit-discard').toggleClass('is-active')
+    })
 
-  /* eslint-disable spaced-comment */
-  //=include ../components/editor.js
-  /* eslint-enable spaced-comment */
+    // window.onbeforeunload = function () {
+    //   return (pageCleanExit) ? true : 'Unsaved modifications will be lost. Are you sure you want to navigate away from this page?'
+    // }
+
+    require('../components/editor.js')(alerts, pageEntryPath, socket)
+  }
 }

+ 20 - 15
client/js/pages/source.js

@@ -1,19 +1,24 @@
-/* global $, ace */
+'use strict'
 
-if ($('#page-type-source').length) {
-  var scEditor = ace.edit('source-display')
-  scEditor.setTheme('ace/theme/tomorrow_night')
-  scEditor.getSession().setMode('ace/mode/markdown')
-  scEditor.setOption('fontSize', '14px')
-  scEditor.setOption('hScrollBarAlwaysVisible', false)
-  scEditor.setOption('wrap', true)
-  scEditor.setReadOnly(true)
-  scEditor.renderer.updateFull()
+import $ from 'jquery'
+import * as ace from 'brace'
+import 'brace/theme/tomorrow_night'
+import 'brace/mode/markdown'
 
-  let currentBasePath = ($('#page-type-source').data('entrypath') !== 'home') ? $('#page-type-source').data('entrypath') : '' // eslint-disable-line no-unused-vars
+module.exports = (alerts) => {
+  if ($('#page-type-source').length) {
+    var scEditor = ace.edit('source-display')
+    scEditor.setTheme('ace/theme/tomorrow_night')
+    scEditor.getSession().setMode('ace/mode/markdown')
+    scEditor.setOption('fontSize', '14px')
+    scEditor.setOption('hScrollBarAlwaysVisible', false)
+    scEditor.setOption('wrap', true)
+    scEditor.setReadOnly(true)
+    scEditor.renderer.updateFull()
 
-  /* eslint-disable spaced-comment */
-  //=include ../modals/create.js
-  //=include ../modals/move.js
-  /* eslint-enable spaced-comment */
+    let currentBasePath = ($('#page-type-source').data('entrypath') !== 'home') ? $('#page-type-source').data('entrypath') : ''
+
+    require('../modals/create.js')(currentBasePath)
+    require('../modals/move.js')(currentBasePath, alerts)
+  }
 }

+ 9 - 7
client/js/pages/view.js

@@ -1,10 +1,12 @@
-/* global $ */
+'use strict'
 
-if ($('#page-type-view').length) {
-  let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') : '' // eslint-disable-line no-unused-vars
+import $ from 'jquery'
 
-  /* eslint-disable spaced-comment */
-  //=include ../modals/create.js
-  //=include ../modals/move.js
-  /* eslint-enable spaced-comment */
+module.exports = (alerts) => {
+  if ($('#page-type-view').length) {
+    let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') : ''
+
+    require('../modals/create.js')(currentBasePath)
+    require('../modals/move.js')(currentBasePath, alerts)
+  }
 }

+ 2 - 0
client/scss/app.scss

@@ -19,6 +19,8 @@ $primary: 'indigo';
 
 @import './libs/twemoji-awesome';
 @import './libs/jquery-contextmenu';
+@import 'node_modules/highlight.js/styles/tomorrow';
+@import 'node_modules/simplemde/dist/simplemde.min';
 
 @import './components/_editor';
 

+ 24 - 11
fuse.js

@@ -27,6 +27,21 @@ const args = require('yargs')
     .alias('h', 'help')
     .argv
 
+// Define aliases
+
+const ALIASES = {
+  'ace': 'ace-builds/src-min-noconflict/ace.js',
+  'simplemde': 'simplemde/dist/simplemde.min.js',
+  'socket.io-client': 'socket.io-client/dist/socket.io.min.js',
+  'vue': 'vue/dist/vue.js'
+}
+const SHIMS = {
+  jquery: {
+    source: 'node_modules/jquery/dist/jquery.js',
+    exports: '$'
+  }
+}
+
 if (args.d) {
   // =============================================
   // DEVELOPER MODE
@@ -41,9 +56,8 @@ if (args.d) {
   const fuse = fsbx.FuseBox.init({
     homeDir: './client',
     outFile: './assets/js/bundle.min.js',
-    alias: {
-      vue: 'vue/dist/vue.js'
-    },
+    alias: ALIASES,
+    shim: SHIMS,
     plugins: [
       [ fsbx.SassPlugin({ includePaths: ['../core'] }), fsbx.CSSPlugin() ],
       fsbx.BabelPlugin({ comments: false, presets: ['es2015'] }),
@@ -55,7 +69,8 @@ if (args.d) {
 
   fuse.devServer('>index.js', {
     port: 4444,
-    httpServer: false
+    httpServer: false,
+    hmr: false
   })
 
   // Server
@@ -80,7 +95,7 @@ if (args.d) {
   }, 1000)
 } else if (args.c) {
   // =============================================
-  // DEVELOPER MODE
+  // CONFIGURE - DEVELOPER MODE
   // =============================================
 
   console.info(colors.bgWhite.black(' Starting Fuse in CONFIGURE DEVELOPER mode... '))
@@ -92,9 +107,8 @@ if (args.d) {
   const fuse = fsbx.FuseBox.init({
     homeDir: './client',
     outFile: './assets/js/configure.min.js',
-    alias: {
-      vue: 'vue/dist/vue.js'
-    },
+    alias: ALIASES,
+    shim: SHIMS,
     plugins: [
       [ fsbx.SassPlugin({ includePaths: ['../core'] }), fsbx.CSSPlugin() ],
       fsbx.BabelPlugin({ comments: false, presets: ['es2015'] }),
@@ -131,9 +145,8 @@ if (args.d) {
 
   const fuse = fsbx.FuseBox.init({
     homeDir: './client',
-    alias: {
-      vue: 'vue/dist/vue.js'
-    },
+    alias: ALIASES,
+    shim: SHIMS,
     plugins: [
       [ fsbx.SassPlugin({ outputStyle: 'compressed', includePaths: ['./node_modules/requarks-core'] }), fsbx.CSSPlugin() ],
       fsbx.BabelPlugin({

+ 13 - 13
package.json

@@ -43,13 +43,13 @@
   },
   "dependencies": {
     "auto-load": "^2.1.0",
-    "axios": "^0.15.3",
+    "axios": "^0.16.0",
     "bcryptjs-then": "^1.0.1",
     "bluebird": "^3.4.7",
     "body-parser": "^1.17.1",
     "bunyan": "^1.8.9",
     "cheerio": "^0.22.0",
-    "child-process-promise": "^2.2.0",
+    "child-process-promise": "^2.2.1",
     "chokidar": "^1.6.0",
     "commander": "^2.9.0",
     "compression": "^1.6.2",
@@ -61,7 +61,7 @@
     "express": "^4.15.2",
     "express-brute": "^1.0.0",
     "express-brute-mongoose": "0.0.7",
-    "express-session": "^1.15.1",
+    "express-session": "^1.15.2",
     "file-type": "^4.0.0",
     "filesize.js": "^1.0.2",
     "follow-redirects": "^1.2.3",
@@ -85,19 +85,19 @@
     "markdown-it-expand-tabs": "^1.0.11",
     "markdown-it-external-links": "0.0.6",
     "markdown-it-footnote": "^3.0.1",
-    "markdown-it-task-lists": "^1.4.1",
+    "markdown-it-task-lists": "^2.0.0",
     "memdown": "^1.2.4",
     "mime-types": "^2.1.15",
     "moment": "^2.18.1",
     "moment-timezone": "^0.5.11",
     "mongodb": "^2.2.25",
-    "mongoose": "^4.9.1",
+    "mongoose": "^4.9.2",
     "multer": "^1.2.1",
     "ora": "^1.2.0",
     "passport": "^0.3.2",
     "passport-local": "^1.0.0",
     "passport.socketio": "^3.7.0",
-    "pm2": "^2.4.2",
+    "pm2": "^2.4.3",
     "pug": "^2.0.0-beta11",
     "read-chunk": "^2.0.0",
     "remove-markdown": "^0.1.0",
@@ -119,33 +119,33 @@
     "winston": "^2.3.0"
   },
   "devDependencies": {
-    "ace-builds": "^1.2.6",
     "babel-cli": "^6.24.0",
     "babel-jest": "^19.0.0",
     "babel-preset-es2015": "^6.24.0",
+    "brace": "^0.10.0",
     "colors": "^1.1.2",
-    "eslint": "^3.18.0",
+    "eslint": "^3.19.0",
     "eslint-config-standard": "^7.1.0",
     "eslint-plugin-import": "^2.2.0",
-    "eslint-plugin-node": "^4.2.1",
+    "eslint-plugin-node": "^4.2.2",
     "eslint-plugin-promise": "^3.5.0",
     "eslint-plugin-standard": "^2.1.1",
-    "fuse-box": "^1.3.128",
+    "fuse-box": "^1.3.129",
     "jest": "^19.0.2",
     "jquery": "^3.2.1",
     "jquery-contextmenu": "^2.4.4",
     "jquery-simple-upload": "^1.0.0",
     "jquery-smooth-scroll": "^2.0.0",
-    "node-sass": "^4.5.1",
+    "node-sass": "^4.5.2",
     "nodemon": "^1.11.0",
     "pre-commit": "^1.2.2",
     "pug-lint": "^2.4.0",
-    "snyk": "^1.25.1",
+    "snyk": "^1.26.1",
     "standard": "^9.0.2",
     "sticky-js": "^1.1.9",
     "twemoji-awesome": "^1.0.4",
     "vee-validate": "^2.0.0-beta.25",
-    "vue": "^2.2.5"
+    "vue": "^2.2.6"
   },
   "standard": {
     "globals": [

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików