Fuse-box client scripts integration + deps update

This commit is contained in:
NGPixel 2017-04-01 17:07:01 -04:00
parent f6c519c5dc
commit fe0c4ce0c0
23 changed files with 32342 additions and 64195 deletions

47234
assets/js/bundle.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@ -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 let codeEditor = null
// ACE - Mode Loader // ACE - Mode Loader
@ -24,52 +30,56 @@ let loadAceMode = (m) => {
// Vue Code Block instance // Vue Code Block instance
let vueCodeBlock = new Vue({ module.exports = (mde, mdeModalOpenState) => {
el: '#modal-editor-codeblock', let modelist = {} // ace.require('ace/ext/modelist')
data: { let vueCodeBlock = new Vue({
modes: modelist.modesByName, el: '#modal-editor-codeblock',
modeSelected: 'text', data: {
initContent: '' modes: modelist.modesByName,
}, modeSelected: 'text',
watch: { initContent: ''
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)
codeEditor.setValue(vueCodeBlock.initContent)
codeEditor.focus()
codeEditor.renderer.updateFull()
}, 300)
}, },
cancel: (ev) => { watch: {
mdeModalOpenState = false // eslint-disable-line no-undef modeSelected: (val, oldVal) => {
$('#modal-editor-codeblock').removeClass('is-active') loadAceMode(val).done(() => {
vueCodeBlock.initContent = '' ace.require('ace/mode/' + val)
}, codeEditor.getSession().setMode('ace/mode/' + val)
insertCode: (ev) => { })
if (mde.codemirror.doc.somethingSelected()) {
mde.codemirror.execCommand('singleSelection')
} }
let codeBlockText = '\n```' + vueCodeBlock.modeSelected + '\n' + codeEditor.getValue() + '\n```\n' },
methods: {
open: (ev) => {
$('#modal-editor-codeblock').addClass('is-active')
mde.codemirror.doc.replaceSelection(codeBlockText) _.delay(() => {
vueCodeBlock.cancel() 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.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()
}
} }
} })
}) return vueCodeBlock
}

View File

@ -1,352 +1,361 @@
/* global $, Vue, _, alerts, mde, socket */ 'use strict'
let vueFile = new Vue({ import $ from 'jquery'
el: '#modal-editor-file', import Vue from 'vue'
data: { import _ from 'lodash'
isLoading: false, import 'jquery-contextmenu'
isLoadingText: '', import 'jquery-simple-upload'
newFolderName: '',
newFolderShow: false,
newFolderError: false,
folders: [],
currentFolder: '',
currentFile: '',
files: [],
uploadSucceeded: false,
postUploadChecks: 0,
renameFileShow: false,
renameFileId: '',
renameFileFilename: '',
deleteFileShow: false,
deleteFileId: '',
deleteFileFilename: ''
},
methods: {
open: () => { module.exports = (alerts, mde, mdeModalOpenState, socket) => {
mdeModalOpenState = true // eslint-disable-line no-undef let vueFile = new Vue({
$('#modal-editor-file').addClass('is-active') el: '#modal-editor-file',
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 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
}
vueFile.newFolderDiscard()
vueFile.isLoadingText = 'Creating new folder...'
vueFile.isLoading = true
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
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)
}
})
})
},
// -------------------------------------------
// MOVE FILE
// -------------------------------------------
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)
}
})
})
},
// -------------------------------------------
// 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()
})
})
},
// -------------------------------------------
// LOAD FROM REMOTE
// -------------------------------------------
selectFolder: (fldName) => {
vueFile.currentFolder = fldName
vueFile.loadFiles()
},
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.isLoading = false
}
vueFile.attachContextMenus()
resolve(true)
})
})
})
},
waitChangeComplete: (oldAmount, expectChange) => {
expectChange = (_.isBoolean(expectChange)) ? expectChange : true
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)
}
})
})
},
// -------------------------------------------
// 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()
}
},
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', {
name: 'binfile',
data: { data: {
folder: vueFile.currentFolder isLoading: false,
isLoadingText: '',
newFolderName: '',
newFolderShow: false,
newFolderError: false,
folders: [],
currentFolder: '',
currentFile: '',
files: [],
uploadSucceeded: false,
postUploadChecks: 0,
renameFileShow: false,
renameFileId: '',
renameFileFilename: '',
deleteFileShow: false,
deleteFileId: '',
deleteFileFilename: ''
}, },
limit: 20, methods: {
expect: 'json',
maxFileSize: 0,
init: (totalUploads) => { open: () => {
vueFile.uploadSucceeded = false mdeModalOpenState = true // eslint-disable-line no-undef
vueFile.isLoadingText = 'Preparing to upload...' $('#modal-editor-file').addClass('is-active')
vueFile.isLoading = true vueFile.refreshFolders()
}, },
cancel: (ev) => {
mdeModalOpenState = false // eslint-disable-line no-undef
$('#modal-editor-file').removeClass('is-active')
},
progress: (progress) => { // -------------------------------------------
vueFile.isLoadingText = 'Uploading...' + Math.round(progress) + '%' // INSERT LINK TO FILE
}, // -------------------------------------------
success: (data) => { selectFile: (fileId) => {
if (data.ok) { vueFile.currentFile = fileId
let failedUpls = _.filter(data.results, ['ok', false]) },
if (failedUpls.length) { insertFileLink: (ev) => {
_.forEach(failedUpls, (u) => { if (mde.codemirror.doc.somethingSelected()) {
alerts.pushError('Upload error', u.msg) 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 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
}
vueFile.newFolderDiscard()
vueFile.isLoadingText = 'Creating new folder...'
vueFile.isLoading = true
Vue.nextTick(() => {
socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => {
vueFile.folders = data
vueFile.currentFolder = vueFile.newFolderName
vueFile.files = []
vueFile.isLoading = false
}) })
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.' // -------------------------------------------
// 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)
}
})
})
},
// -------------------------------------------
// MOVE FILE
// -------------------------------------------
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)
}
})
})
},
// -------------------------------------------
// 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()
})
})
},
// -------------------------------------------
// LOAD FROM REMOTE
// -------------------------------------------
selectFolder: (fldName) => {
vueFile.currentFolder = fldName
vueFile.loadFiles()
},
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.isLoading = false
}
vueFile.attachContextMenus()
resolve(true)
}) })
})
})
},
waitChangeComplete: (oldAmount, expectChange) => {
expectChange = (_.isBoolean(expectChange)) ? expectChange : true
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)
}
})
})
},
// -------------------------------------------
// 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()
}
},
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', {
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
},
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 vueFile.uploadSucceeded = true
} }
} else { } else {
vueFile.uploadSucceeded = true alerts.pushError('Upload error', data.msg)
}
},
error: (error) => {
alerts.pushError('Upload error', error.message)
},
finish: () => {
if (vueFile.uploadSucceeded) {
vueFile.waitChangeComplete(curFileAmount, true)
} else {
vueFile.isLoading = false
} }
} else {
alerts.pushError('Upload error', data.msg)
} }
},
error: (error) => {
alerts.pushError('Upload error', error.message)
},
finish: () => {
if (vueFile.uploadSucceeded) {
vueFile.waitChangeComplete(curFileAmount, true)
} else {
vueFile.isLoading = false
}
}
})
}) })
}) return vueFile
}

View File

@ -1,397 +1,406 @@
/* global $, Vue, mde, _, alerts, socket */ 'use strict'
let vueImage = new Vue({ import $ from 'jquery'
el: '#modal-editor-image', import Vue from 'vue'
data: { import _ from 'lodash'
isLoading: false, import 'jquery-contextmenu'
isLoadingText: '', import 'jquery-simple-upload'
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: () => { module.exports = (alerts, mde, mdeModalOpenState, socket) => {
mdeModalOpenState = true // eslint-disable-line no-undef let vueImage = new Vue({
$('#modal-editor-image').addClass('is-active') el: '#modal-editor-image',
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')
}
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
}
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
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
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
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)
}
})
})
},
// -------------------------------------------
// DELETE IMAGE
// -------------------------------------------
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()
})
})
},
// -------------------------------------------
// LOAD FROM REMOTE
// -------------------------------------------
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()
})
})
},
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)
})
})
})
},
waitChangeComplete: (oldAmount, expectChange) => {
expectChange = (_.isBoolean(expectChange)) ? expectChange : true
vueImage.postUploadChecks++
vueImage.isLoadingText = 'Processing...'
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)
}
})
})
},
// -------------------------------------------
// 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()
}
},
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', {
name: 'imgfile',
data: { data: {
folder: vueImage.currentFolder 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: ''
}, },
limit: 20, methods: {
expect: 'json',
allowedExts: ['jpg', 'jpeg', 'gif', 'png', 'webp'],
allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
maxFileSize: 3145728, // max 3 MB
init: (totalUploads) => { open: () => {
vueImage.uploadSucceeded = false mdeModalOpenState = true
vueImage.isLoadingText = 'Preparing to upload...' $('#modal-editor-image').addClass('is-active')
vueImage.isLoading = true vueImage.refreshFolders()
}, },
cancel: (ev) => {
mdeModalOpenState = false
$('#modal-editor-image').removeClass('is-active')
},
progress: (progress) => { // -------------------------------------------
vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%' // INSERT IMAGE
}, // -------------------------------------------
success: (data) => { selectImage: (imageId) => {
if (data.ok) { vueImage.currentImage = imageId
let failedUpls = _.filter(data.results, ['ok', false]) },
if (failedUpls.length) { insertImage: (ev) => {
_.forEach(failedUpls, (u) => { if (mde.codemirror.doc.somethingSelected()) {
alerts.pushError('Upload error', u.msg) mde.codemirror.execCommand('singleSelection')
}
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
}
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
Vue.nextTick(() => {
socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => {
vueImage.folders = data
vueImage.currentFolder = vueImage.newFolderName
vueImage.images = []
vueImage.isLoading = false
}) })
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.' // -------------------------------------------
// 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
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
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)
}
})
})
},
// -------------------------------------------
// DELETE IMAGE
// -------------------------------------------
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()
})
})
},
// -------------------------------------------
// LOAD FROM REMOTE
// -------------------------------------------
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()
})
})
},
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)
}) })
})
})
},
waitChangeComplete: (oldAmount, expectChange) => {
expectChange = (_.isBoolean(expectChange)) ? expectChange : true
vueImage.postUploadChecks++
vueImage.isLoadingText = 'Processing...'
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)
}
})
})
},
// -------------------------------------------
// 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()
}
},
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', {
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
},
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 vueImage.uploadSucceeded = true
} }
} else { } else {
vueImage.uploadSucceeded = true alerts.pushError('Upload error', data.msg)
}
},
error: (error) => {
alerts.pushError(error.message, this.upload.file.name)
},
finish: () => {
if (vueImage.uploadSucceeded) {
vueImage.waitChangeComplete(curImageAmount, true)
} else {
vueImage.isLoading = false
} }
} else {
alerts.pushError('Upload error', data.msg)
} }
},
error: (error) => {
alerts.pushError(error.message, this.upload.file.name)
},
finish: () => {
if (vueImage.uploadSucceeded) {
vueImage.waitChangeComplete(curImageAmount, true)
} else {
vueImage.isLoading = false
}
}
})
}) })
}) return vueImage
}

View File

@ -1,4 +1,8 @@
/* global $, Vue, mde, _ */ 'use strict'
import $ from 'jquery'
import Vue from 'vue'
import _ from 'lodash'
const videoRules = { const videoRules = {
'youtube': new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/, 'i'), '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') '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 module.exports = (mde, mdeModalOpenState) => {
// Vue Video instance
let vueVideo = new Vue({ let vueVideo = new Vue({
el: '#modal-editor-video', el: '#modal-editor-video',
data: { data: {
link: '' link: ''
},
methods: {
open: (ev) => {
$('#modal-editor-video').addClass('is-active')
$('#modal-editor-video input').focus()
}, },
cancel: (ev) => { methods: {
mdeModalOpenState = false // eslint-disable-line no-undef open: (ev) => {
$('#modal-editor-video').removeClass('is-active') $('#modal-editor-video').addClass('is-active')
vueVideo.link = '' $('#modal-editor-video input').focus()
}, },
insertVideo: (ev) => { cancel: (ev) => {
if (mde.codemirror.doc.somethingSelected()) { mdeModalOpenState = false // eslint-disable-line no-undef
mde.codemirror.execCommand('singleSelection') $('#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()
} }
// 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()
} }
} })
}) return vueVideo
}

View File

@ -1,220 +1,223 @@
'use strict' '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 // Markdown Editor
// ==================================== // ====================================
if ($('#mk-editor').length === 1) { module.exports = (alerts, pageEntryPath, socket) => {
let mdeModalOpenState = false if ($('#mk-editor').length === 1) {
let mdeCurrentEditor = null // eslint-disable-line no-unused-vars Vue.filter('filesize', (v) => {
return _.toUpper(filesize(v))
})
Vue.filter('filesize', (v) => { let mde
return _.toUpper(filesize(v)) 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 */ mde = new SimpleMDE({
//=include editor-image.js autofocus: true,
//=include editor-file.js autoDownloadFontAwesome: false,
//=include editor-video.js element: $('#mk-editor').get(0),
//=include editor-codeblock.js placeholder: 'Enter Markdown formatted content here...',
/* eslint-enable spaced-comment */ spellChecker: false,
status: false,
var mde = new SimpleMDE({ toolbar: [
autofocus: true, {
autoDownloadFontAwesome: false, name: 'bold',
element: $('#mk-editor').get(0), action: SimpleMDE.toggleBold,
placeholder: 'Enter Markdown formatted content here...', className: 'icon-bold',
spellChecker: false, title: 'Bold'
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: 'italic',
}, action: SimpleMDE.toggleItalic,
{ className: 'icon-italic',
name: 'image', title: 'Italic'
action: (editor) => {
if (!mdeModalOpenState) {
vueImage.open()
}
}, },
className: 'icon-image', {
title: 'Insert Image' name: 'strikethrough',
}, action: SimpleMDE.toggleStrikethrough,
{ className: 'icon-strikethrough',
name: 'file', title: 'Strikethrough'
action: (editor) => {
if (!mdeModalOpenState) {
vueFile.open()
}
}, },
className: 'icon-paper', '|',
title: 'Insert File' {
}, name: 'heading-1',
{ action: SimpleMDE.toggleHeading1,
name: 'video', className: 'icon-header fa-header-x fa-header-1',
action: (editor) => { title: 'Big Heading'
if (!mdeModalOpenState) {
vueVideo.open()
}
}, },
className: 'icon-video-camera2', {
title: 'Insert Video Player' name: 'heading-2',
}, action: SimpleMDE.toggleHeading2,
'|', className: 'icon-header fa-header-x fa-header-2',
{ title: 'Medium Heading'
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: 'heading-3',
}, action: SimpleMDE.toggleHeading3,
{ className: 'icon-header fa-header-x fa-header-3',
name: 'code-block', title: 'Small Heading'
action: (editor) => { },
if (!mdeModalOpenState) { {
mdeModalOpenState = true name: 'quote',
action: SimpleMDE.toggleBlockquote,
if (mde.codemirror.doc.somethingSelected()) { className: 'icon-quote-left',
vueCodeBlock.initContent = mde.codemirror.doc.getSelection() 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
vueCodeBlock.open() if (mde.codemirror.doc.somethingSelected()) {
} vueCodeBlock.initContent = mde.codemirror.doc.getSelection()
}
vueCodeBlock.open()
}
},
className: 'icon-code',
title: 'Code Block'
}, },
className: 'icon-code', '|',
title: 'Code Block' {
}, name: 'table',
'|', action: (editor) => {
{ window.alert('Coming soon!')
name: 'table', // todo
action: (editor) => { },
window.alert('Coming soon!') className: 'icon-table',
// todo title: 'Insert Table'
}, },
className: 'icon-table', {
title: 'Insert Table' name: 'horizontal-rule',
}, action: SimpleMDE.drawHorizontalRule,
{ className: 'icon-minus2',
name: 'horizontal-rule', title: 'Horizontal Rule'
action: SimpleMDE.drawHorizontalRule, }
className: 'icon-minus2', ],
title: 'Horizontal Rule' shortcuts: {
'toggleBlockquote': null,
'toggleFullScreen': null
} }
], })
shortcuts: {
'toggleBlockquote': null, // -> Save
'toggleFullScreen': null
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) => { $(window).bind('keydown', (ev) => {
$.ajax(window.location.href, { if (ev.ctrlKey || ev.metaKey) {
data: { switch (String.fromCharCode(ev.which).toLowerCase()) {
markdown: mde.value() case 's':
}, ev.preventDefault()
dataType: 'json', saveCurrentDocument(ev)
method: 'PUT' break
}).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.')
}) })
} }
$('.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
}
}
})
} }

View File

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

View File

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

View File

@ -1,13 +1,19 @@
/* global _ */ 'use strict'
/* eslint-disable no-unused-vars */
function makeSafePath (rawPath) { import _ from 'lodash'
let rawParts = _.split(_.trim(rawPath), '/')
rawParts = _.map(rawParts, (r) => {
return _.kebabCase(_.deburr(_.trim(r)))
})
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)))
})
return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r) }), '/')
}
} }
/* eslint-enable no-unused-vars */

View File

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

View File

@ -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 // Vue Delete User instance
let vueDeleteUser = new Vue({ module.exports = (alerts) => {
el: '#modal-admin-users-delete', let vueDeleteUser = new Vue({
data: { el: '#modal-admin-users-delete',
loading: false data: {
}, loading: false
methods: {
open: (ev) => {
$('#modal-admin-users-delete').addClass('is-active')
}, },
cancel: (ev) => { methods: {
$('#modal-admin-users-delete').removeClass('is-active') open: (ev) => {
}, $('#modal-admin-users-delete').addClass('is-active')
deleteUser: (ev) => { },
vueDeleteUser.loading = true cancel: (ev) => {
$.ajax('/admin/users/' + usrData._id, { $('#modal-admin-users-delete').removeClass('is-active')
dataType: 'json', },
method: 'DELETE' deleteUser: (ev) => {
}).then((rData, rStatus, rXHR) => { vueDeleteUser.loading = true
vueDeleteUser.loading = false $.ajax('/admin/users/' + usrData._id, {
vueDeleteUser.cancel() dataType: 'json',
window.location.assign('/admin/users') method: 'DELETE'
}, (rXHR, rStatus, err) => { }).then((rData, rStatus, rXHR) => {
vueDeleteUser.loading = false vueDeleteUser.loading = false
alerts.pushError('Error', rXHR.responseJSON.msg) 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)
}

View File

@ -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 // -> Create New Document
let suggestedCreatePath = currentBasePath + '/new-page' module.exports = (currentBasePath) => {
let suggestedCreatePath = currentBasePath + '/new-page'
$('.btn-create-prompt').on('click', (ev) => { $('.btn-create-prompt').on('click', (ev) => {
$('#txt-create-prompt').val(suggestedCreatePath) $('#txt-create-prompt').val(suggestedCreatePath)
$('#modal-create-prompt').toggleClass('is-active') $('#modal-create-prompt').toggleClass('is-active')
setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length + 1, suggestedCreatePath.length) // eslint-disable-line no-undef setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length + 1, suggestedCreatePath.length)
$('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden') $('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden')
}) })
$('#txt-create-prompt').on('keypress', (ev) => { $('#txt-create-prompt').on('keypress', (ev) => {
if (ev.which === 13) { if (ev.which === 13) {
$('.btn-create-go').trigger('click') $('.btn-create-go').trigger('click')
} }
}) })
$('.btn-create-go').on('click', (ev) => { $('.btn-create-go').on('click', (ev) => {
let newDocPath = makeSafePath($('#txt-create-prompt').val()) // eslint-disable-line no-undef let newDocPath = makeSafePath($('#txt-create-prompt').val())
if (_.isEmpty(newDocPath)) { if (_.isEmpty(newDocPath)) {
$('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden') $('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden')
} else { } else {
$('#txt-create-prompt').parent().addClass('is-loading') $('#txt-create-prompt').parent().addClass('is-loading')
window.location.assign('/create/' + newDocPath) window.location.assign('/create/' + newDocPath)
} }
}) })
}

View File

@ -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 // -> Move Existing Document
if (currentBasePath !== '') { module.exports = (currentBasePath, alerts) => {
$('.btn-move-prompt').removeClass('is-hidden') if (currentBasePath !== '') {
$('.btn-move-prompt').removeClass('is-hidden')
}
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)
$('#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())
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.')
})
}
})
} }
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')
})
$('#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.')
})
}
})

View File

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

View File

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

View File

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

View File

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

View File

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

35
fuse.js
View File

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

View File

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