You need to sign in or sign up before continuing.
Commit fe0c4ce0 authored by NGPixel's avatar NGPixel

Fuse-box client scripts integration + deps update

parent f6c519c5
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -8,8 +8,6 @@ switch (logic) { ...@@ -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
......
...@@ -2,14 +2,14 @@ ...@@ -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 ($) { ...@@ -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 */
/* 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) => { ...@@ -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(() => { watch: {
ace.require('ace/mode/' + val) modeSelected: (val, oldVal) => {
codeEditor.getSession().setMode('ace/mode/' + val) loadAceMode(val).done(() => {
}) ace.require('ace/mode/' + val)
} codeEditor.getSession().setMode('ace/mode/' + val)
}, })
methods: { }
open: (ev) => { },
$('#modal-editor-codeblock').addClass('is-active') methods: {
open: (ev) => {
$('#modal-editor-codeblock').addClass('is-active')
_.delay(() => { _.delay(() => {
codeEditor = ace.edit('codeblock-editor') codeEditor = ace.edit('codeblock-editor')
codeEditor.setTheme('ace/theme/tomorrow_night') codeEditor.setTheme('ace/theme/tomorrow_night')
codeEditor.getSession().setMode('ace/mode/' + vueCodeBlock.modeSelected) codeEditor.getSession().setMode('ace/mode/' + vueCodeBlock.modeSelected)
codeEditor.setOption('fontSize', '14px') codeEditor.setOption('fontSize', '14px')
codeEditor.setOption('hScrollBarAlwaysVisible', false) codeEditor.setOption('hScrollBarAlwaysVisible', false)
codeEditor.setOption('wrap', true) codeEditor.setOption('wrap', true)
codeEditor.setValue(vueCodeBlock.initContent) codeEditor.setValue(vueCodeBlock.initContent)
codeEditor.focus() codeEditor.focus()
codeEditor.renderer.updateFull() codeEditor.renderer.updateFull()
}, 300) }, 300)
}, },
cancel: (ev) => { cancel: (ev) => {
mdeModalOpenState = false // eslint-disable-line no-undef mdeModalOpenState = false // eslint-disable-line no-undef
$('#modal-editor-codeblock').removeClass('is-active') $('#modal-editor-codeblock').removeClass('is-active')
vueCodeBlock.initContent = '' vueCodeBlock.initContent = ''
}, },
insertCode: (ev) => { insertCode: (ev) => {
if (mde.codemirror.doc.somethingSelected()) { if (mde.codemirror.doc.somethingSelected()) {
mde.codemirror.execCommand('singleSelection') mde.codemirror.execCommand('singleSelection')
} }
let codeBlockText = '\n```' + vueCodeBlock.modeSelected + '\n' + codeEditor.getValue() + '\n```\n' let codeBlockText = '\n```' + vueCodeBlock.modeSelected + '\n' + codeEditor.getValue() + '\n```\n'
mde.codemirror.doc.replaceSelection(codeBlockText) mde.codemirror.doc.replaceSelection(codeBlockText)
vueCodeBlock.cancel() vueCodeBlock.cancel()
}
} }
} })
}) return vueCodeBlock
}
/* global $, Vue, _, alerts, mde, socket */ 'use strict'
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')
},
// ------------------------------------------- import $ from 'jquery'
// INSERT LINK TO FILE import Vue from 'vue'
// ------------------------------------------- import _ from 'lodash'
import 'jquery-contextmenu'
import 'jquery-simple-upload'
selectFile: (fileId) => { module.exports = (alerts, mde, mdeModalOpenState, socket) => {
vueFile.currentFile = fileId 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) => { methods: {
if (mde.codemirror.doc.somethingSelected()) {
mde.codemirror.execCommand('singleSelection') 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]) let selFile = _.find(vueFile.files, ['_id', vueFile.currentFile])
selFile.normalizedPath = (selFile.folder === 'f:') ? selFile.filename : selFile.folder.slice(2) + '/' + selFile.filename selFile.normalizedPath = (selFile.folder === 'f:') ? selFile.filename : selFile.folder.slice(2) + '/' + selFile.filename
selFile.titleGuess = _.startCase(selFile.basename) 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) Vue.nextTick(() => {
vueFile.cancel() 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(() => {
// NEW FOLDER 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 = '' // MOVE FILE
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)) { moveFile: (uid, fld) => {
vueFile.newFolderError = true vueFile.isLoadingText = 'Moving file...'
return 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...' // DELETE FILE
vueFile.isLoading = true // -------------------------------------------
Vue.nextTick(() => { deleteFileWarn: (show) => {
socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => { if (show) {
vueFile.folders = data let c = _.find(vueFile.files, [ '_id', vueFile.deleteFileId ])
vueFile.currentFolder = vueFile.newFolderName vueFile.deleteFileFilename = c.filename || 'this file'
vueFile.files = [] }
vueFile.isLoading = false 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 // LOAD FROM REMOTE
// ------------------------------------------- // -------------------------------------------
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)
}
})
})
},
// ------------------------------------------- selectFolder: (fldName) => {
// MOVE FILE vueFile.currentFolder = fldName
// ------------------------------------------- vueFile.loadFiles()
},
moveFile: (uid, fld) => { refreshFolders: () => {
vueFile.isLoadingText = 'Moving file...' vueFile.isLoadingText = 'Fetching folders list...'
vueFile.isLoading = true vueFile.isLoading = true
Vue.nextTick(() => { vueFile.currentFolder = ''
socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => { vueFile.currentImage = ''
if (data.ok) { Vue.nextTick(() => {
socket.emit('uploadsGetFolders', { }, (data) => {
vueFile.folders = data
vueFile.loadFiles() vueFile.loadFiles()
} else { })
vueFile.isLoading = false
alerts.pushError('Rename error', data.msg)
}
}) })
}) },
},
// -------------------------------------------
// DELETE FILE
// -------------------------------------------
deleteFileWarn: (show) => { loadFiles: (silent) => {
if (show) { if (!silent) {
let c = _.find(vueFile.files, [ '_id', vueFile.deleteFileId ]) vueFile.isLoadingText = 'Fetching files...'
vueFile.deleteFileFilename = c.filename || 'this file' vueFile.isLoading = true
} }
vueFile.deleteFileShow = show return new Promise((resolve, reject) => {
}, Vue.nextTick(() => {
deleteFileGo: () => { socket.emit('uploadsGetFiles', { folder: vueFile.currentFolder }, (data) => {
vueFile.deleteFileWarn(false) vueFile.files = data
vueFile.isLoadingText = 'Deleting file...' if (!silent) {
vueFile.isLoading = true vueFile.isLoading = false
Vue.nextTick(() => { }
socket.emit('uploadsDeleteFile', { uid: vueFile.deleteFileId }, (data) => { vueFile.attachContextMenus()
vueFile.loadFiles() resolve(true)
})
})
}) })
}) },
},
// ------------------------------------------- waitChangeComplete: (oldAmount, expectChange) => {
// LOAD FROM REMOTE expectChange = (_.isBoolean(expectChange)) ? expectChange : true
// -------------------------------------------
selectFolder: (fldName) => { vueFile.postUploadChecks++
vueFile.currentFolder = fldName vueFile.isLoadingText = 'Processing...'
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(() => { Vue.nextTick(() => {
socket.emit('uploadsGetFiles', { folder: vueFile.currentFolder }, (data) => { vueFile.loadFiles(true).then(() => {
vueFile.files = data if ((vueFile.files.length !== oldAmount) === expectChange) {
if (!silent) { vueFile.postUploadChecks = 0
vueFile.isLoading = false
} else if (vueFile.postUploadChecks > 5) {
vueFile.postUploadChecks = 0
vueFile.isLoading = false 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++ attachContextMenus: () => {
vueFile.isLoadingText = 'Processing...' let moveFolders = _.map(vueFile.folders, (f) => {
return {
Vue.nextTick(() => { name: (f !== '') ? f : '/ (root)',
vueFile.loadFiles(true).then(() => { icon: 'fa-folder',
if ((vueFile.files.length !== oldAmount) === expectChange) { callback: (key, opt) => {
vueFile.postUploadChecks = 0 let moveFileId = _.toString($(opt.$trigger).data('uid'))
vueFile.isLoading = false let moveFileDestFolder = _.nth(vueFile.folders, key)
} else if (vueFile.postUploadChecks > 5) { vueFile.moveFile(moveFileId, moveFileDestFolder)
vueFile.postUploadChecks = 0 }
vueFile.isLoading = false
alerts.pushError('Unable to fetch updated listing', 'Try again later')
} else {
_.delay(() => {
vueFile.waitChangeComplete(oldAmount, expectChange)
}, 1500)
} }
}) })
})
},
// ------------------------------------------- $.contextMenu('destroy', '.editor-modal-file-choices > figure')
// IMAGE CONTEXT MENU $.contextMenu({
// ------------------------------------------- selector: '.editor-modal-file-choices > figure',
appendTo: '.editor-modal-file-choices',
attachContextMenus: () => { position: (opt, x, y) => {
let moveFolders = _.map(vueFile.folders, (f) => { $(opt.$trigger).addClass('is-contextopen')
return { let trigPos = $(opt.$trigger).position()
name: (f !== '') ? f : '/ (root)', let trigDim = { w: $(opt.$trigger).width() / 5, h: $(opt.$trigger).height() / 2 }
icon: 'fa-folder', opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w })
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: { events: {
name: 'Move to...', hide: (opt) => {
icon: 'fa-folder-open-o', $(opt.$trigger).removeClass('is-contextopen')
items: moveFolders }
}, },
delete: { items: {
name: 'Delete', rename: {
icon: 'fa-trash', name: 'Rename',
callback: (key, opt) => { icon: 'fa-edit',
vueFile.deleteFileId = _.toString(opt.$trigger[0].dataset.uid) callback: (key, opt) => {
vueFile.deleteFileWarn(true) 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', $(ev.currentTarget).simpleUpload('/uploads/file', {
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) => { name: 'binfile',
vueFile.isLoadingText = 'Uploading...' + Math.round(progress) + '%' data: {
}, folder: vueFile.currentFolder
},
limit: 20,
expect: 'json',
maxFileSize: 0,
success: (data) => { init: (totalUploads) => {
if (data.ok) { vueFile.uploadSucceeded = false
let failedUpls = _.filter(data.results, ['ok', false]) vueFile.isLoadingText = 'Preparing to upload...'
if (failedUpls.length) { vueFile.isLoading = true
_.forEach(failedUpls, (u) => { },
alerts.pushError('Upload error', u.msg)
}) progress: (progress) => {
if (failedUpls.length < data.results.length) { vueFile.isLoadingText = 'Uploading...' + Math.round(progress) + '%'
alerts.push({ },
title: 'Some uploads succeeded',
message: 'Files that are not mentionned in the errors above were uploaded successfully.' 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)
} }
} else { },
alerts.pushError('Upload error', data.msg)
}
},
error: (error) => { error: (error) => {
alerts.pushError('Upload error', error.message) alerts.pushError('Upload error', error.message)
}, },
finish: () => { finish: () => {
if (vueFile.uploadSucceeded) { if (vueFile.uploadSucceeded) {
vueFile.waitChangeComplete(curFileAmount, true) vueFile.waitChangeComplete(curFileAmount, true)
} else { } else {
vueFile.isLoading = false vueFile.isLoading = false
}
} }
}
})
}) })
}) return vueFile
}
/* global $, Vue, mde, _, alerts, socket */ 'use strict'
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')
}
let selImage = _.find(vueImage.images, ['_id', vueImage.currentImage]) import $ from 'jquery'
selImage.normalizedPath = (selImage.folder === 'f:') ? selImage.filename : selImage.folder.slice(2) + '/' + selImage.filename import Vue from 'vue'
selImage.titleGuess = _.startCase(selImage.basename) import _ from 'lodash'
import 'jquery-contextmenu'
let imageText = '![' + selImage.titleGuess + '](/uploads/' + selImage.normalizedPath + ' "' + selImage.titleGuess + '")' import 'jquery-simple-upload'
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) module.exports = (alerts, mde, mdeModalOpenState, socket) => {
vueImage.cancel() 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')
}
// ------------------------------------------- let selImage = _.find(vueImage.images, ['_id', vueImage.currentImage])
// NEW FOLDER selImage.normalizedPath = (selImage.folder === 'f:') ? selImage.filename : selImage.folder.slice(2) + '/' + selImage.filename
// ------------------------------------------- selImage.titleGuess = _.startCase(selImage.basename)
newFolder: (ev) => { let imageText = '![' + selImage.titleGuess + '](/uploads/' + selImage.normalizedPath + ' "' + selImage.titleGuess + '")'
vueImage.newFolderName = '' switch (vueImage.currentAlign) {
vueImage.newFolderError = false case 'center':
vueImage.newFolderShow = true imageText += '{.align-center}'
_.delay(() => { $('#txt-editor-image-newfoldername').focus() }, 400) break
}, case 'right':
newFolderDiscard: (ev) => { imageText += '{.align-right}'
vueImage.newFolderShow = false break
}, case 'logo':
newFolderCreate: (ev) => { imageText += '{.pagelogo}'
let regFolderName = new RegExp('^[a-z0-9][a-z0-9-]*[a-z0-9]$') break
vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName)) }
if (_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) { mde.codemirror.doc.replaceSelection(imageText)
vueImage.newFolderError = true vueImage.cancel()
return },
}
// -------------------------------------------
// 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.newFolderDiscard()
vueImage.isLoadingText = 'Creating new folder...' vueImage.isLoadingText = 'Creating new folder...'
vueImage.isLoading = true vueImage.isLoading = true
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => { socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => {
vueImage.folders = data vueImage.folders = data
vueImage.currentFolder = vueImage.newFolderName vueImage.currentFolder = vueImage.newFolderName
vueImage.images = [] vueImage.images = []
vueImage.isLoading = false 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(() => {
// FETCH FROM URL 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) => { Vue.nextTick(() => {
vueImage.fetchFromUrlURL = '' socket.emit('uploadsRenameFile', { uid: vueImage.renameImageId, folder: vueImage.currentFolder, filename: vueImage.renameImageFilename }, (data) => {
vueImage.fetchFromUrlShow = true if (data.ok) {
_.delay(() => { $('#txt-editor-image-fetchurl').focus() }, 400) vueImage.waitChangeComplete(vueImage.images.length, false)
}, } else {
fetchFromUrlDiscard: (ev) => { vueImage.isLoading = false
vueImage.fetchFromUrlShow = false alerts.pushError('Rename error', data.msg)
}, }
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 // MOVE IMAGE
// ------------------------------------------- // -------------------------------------------
renameImage: () => { moveImage: (uid, fld) => {
let c = _.find(vueImage.images, [ '_id', vueImage.renameImageId ]) vueImage.isLoadingText = 'Moving image...'
vueImage.renameImageFilename = c.basename || '' vueImage.isLoading = true
vueImage.renameImageShow = true Vue.nextTick(() => {
_.delay(() => { socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
$('#txt-editor-image-rename').focus() if (data.ok) {
_.defer(() => { $('#txt-editor-image-rename').select() }) vueImage.loadImages()
}, 400) } else {
}, vueImage.isLoading = false
renameImageDiscard: () => { alerts.pushError('Rename error', data.msg)
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 // DELETE IMAGE
// ------------------------------------------- // -------------------------------------------
moveImage: (uid, fld) => { deleteImageWarn: (show) => {
vueImage.isLoadingText = 'Moving image...' if (show) {
vueImage.isLoading = true let c = _.find(vueImage.images, [ '_id', vueImage.deleteImageId ])
Vue.nextTick(() => { vueImage.deleteImageFilename = c.filename || 'this image'
socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => { }
if (data.ok) { 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() vueImage.loadImages()
} else { })
vueImage.isLoading = false
alerts.pushError('Rename error', data.msg)
}
}) })
}) },
},
// ------------------------------------------- // -------------------------------------------
// DELETE IMAGE // LOAD FROM REMOTE
// ------------------------------------------- // -------------------------------------------
deleteImageWarn: (show) => { selectFolder: (fldName) => {
if (show) { vueImage.currentFolder = fldName
let c = _.find(vueImage.images, [ '_id', vueImage.deleteImageId ]) vueImage.loadImages()
vueImage.deleteImageFilename = c.filename || 'this image' },
}
vueImage.deleteImageShow = show refreshFolders: () => {
}, vueImage.isLoadingText = 'Fetching folders list...'
deleteImageGo: () => { vueImage.isLoading = true
vueImage.deleteImageWarn(false) vueImage.currentFolder = ''
vueImage.isLoadingText = 'Deleting image...' vueImage.currentImage = ''
vueImage.isLoading = true Vue.nextTick(() => {
Vue.nextTick(() => { socket.emit('uploadsGetFolders', { }, (data) => {
socket.emit('uploadsDeleteFile', { uid: vueImage.deleteImageId }, (data) => { vueImage.folders = data
vueImage.loadImages() vueImage.loadImages()
})
}) })
}) },
},
// ------------------------------------------- loadImages: (silent) => {
// LOAD FROM REMOTE 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) => { waitChangeComplete: (oldAmount, expectChange) => {
vueImage.currentFolder = fldName expectChange = (_.isBoolean(expectChange)) ? expectChange : true
vueImage.loadImages()
},
refreshFolders: () => { vueImage.postUploadChecks++
vueImage.isLoadingText = 'Fetching folders list...' vueImage.isLoadingText = 'Processing...'
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(() => { Vue.nextTick(() => {
socket.emit('uploadsGetImages', { folder: vueImage.currentFolder }, (data) => { vueImage.loadImages(true).then(() => {
vueImage.images = data if ((vueImage.images.length !== oldAmount) === expectChange) {
if (!silent) { vueImage.postUploadChecks = 0
vueImage.isLoading = false 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(() => { attachContextMenus: () => {
vueImage.loadImages(true).then(() => { let moveFolders = _.map(vueImage.folders, (f) => {
if ((vueImage.images.length !== oldAmount) === expectChange) { return {
vueImage.postUploadChecks = 0 name: (f !== '') ? f : '/ (root)',
vueImage.isLoading = false icon: 'fa-folder',
} else if (vueImage.postUploadChecks > 5) { callback: (key, opt) => {
vueImage.postUploadChecks = 0 let moveImageId = _.toString($(opt.$trigger).data('uid'))
vueImage.isLoading = false let moveImageDestFolder = _.nth(vueImage.folders, key)
alerts.pushError('Unable to fetch updated listing', 'Try again later') vueImage.moveImage(moveImageId, moveImageDestFolder)
} else { }
_.delay(() => {
vueImage.waitChangeComplete(oldAmount, expectChange)
}, 1500)
} }
}) })
})
},
// ------------------------------------------- $.contextMenu('destroy', '.editor-modal-image-choices > figure')
// IMAGE CONTEXT MENU $.contextMenu({
// ------------------------------------------- selector: '.editor-modal-image-choices > figure',
appendTo: '.editor-modal-image-choices',
attachContextMenus: () => { position: (opt, x, y) => {
let moveFolders = _.map(vueImage.folders, (f) => { $(opt.$trigger).addClass('is-contextopen')
return { let trigPos = $(opt.$trigger).position()
name: (f !== '') ? f : '/ (root)', let trigDim = { w: $(opt.$trigger).width() / 2, h: $(opt.$trigger).height() / 2 }
icon: 'fa-folder', opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w })
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: { events: {
name: 'Move to...', hide: (opt) => {
icon: 'fa-folder-open-o', $(opt.$trigger).removeClass('is-contextopen')
items: moveFolders }
}, },
delete: { items: {
name: 'Delete', rename: {
icon: 'fa-trash', name: 'Rename',
callback: (key, opt) => { icon: 'fa-edit',
vueImage.deleteImageId = _.toString(opt.$trigger[0].dataset.uid) callback: (key, opt) => {
vueImage.deleteImageWarn(true) 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', $(ev.currentTarget).simpleUpload('/uploads/img', {
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) => { name: 'imgfile',
vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%' 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) => { init: (totalUploads) => {
if (data.ok) { vueImage.uploadSucceeded = false
let failedUpls = _.filter(data.results, ['ok', false]) vueImage.isLoadingText = 'Preparing to upload...'
if (failedUpls.length) { vueImage.isLoading = true
_.forEach(failedUpls, (u) => { },
alerts.pushError('Upload error', u.msg)
}) progress: (progress) => {
if (failedUpls.length < data.results.length) { vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%'
alerts.push({ },
title: 'Some uploads succeeded',
message: 'Files that are not mentionned in the errors above were uploaded successfully.' 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)
} }
} else { },
alerts.pushError('Upload error', data.msg)
}
},
error: (error) => { error: (error) => {
alerts.pushError(error.message, this.upload.file.name) alerts.pushError(error.message, this.upload.file.name)
}, },
finish: () => { finish: () => {
if (vueImage.uploadSucceeded) { if (vueImage.uploadSucceeded) {
vueImage.waitChangeComplete(curImageAmount, true) vueImage.waitChangeComplete(curImageAmount, true)
} else { } else {
vueImage.isLoading = false vueImage.isLoading = false
}
} }
}
})
}) })
}) return vueImage
}
/* 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 = { ...@@ -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({
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) => { let vueVideo = new Vue({
return vr.test(vueVideo.link) el: '#modal-editor-video',
}) data: {
if (_.isNil(videoType)) { link: ''
videoType = 'video' },
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
}
'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,
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({ if (mde.codemirror.doc.somethingSelected()) {
autofocus: true, vueCodeBlock.initContent = mde.codemirror.doc.getSelection()
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.open()
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
}
}
})
} }
'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: {
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) => { watch: {
if (val > 0) { searchq: (val, oldVal) => {
vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1]) vueHeader.searchmoveidx = 0
? 'res.' + vueHeader.searchmovearr[val - 1].entryPath if (val.length >= 3) {
: 'sug.' + vueHeader.searchmovearr[val - 1] vueHeader.searchactive = true
} else { vueHeader.searchload++
vueHeader.searchmovekey = '' 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: () => { methods: {
vueHeader.searchq = '' useSuggestion: (sug) => {
}, vueHeader.searchq = sug
moveSelectSearch: () => { },
if (vueHeader.searchmoveidx < 1) { return } closeSearch: () => {
let i = vueHeader.searchmoveidx - 1 vueHeader.searchq = ''
},
moveSelectSearch: () => {
if (vueHeader.searchmoveidx < 1) { return }
let i = vueHeader.searchmoveidx - 1
if (vueHeader.searchmovearr[i]) { if (vueHeader.searchmovearr[i]) {
window.location.assign('/' + vueHeader.searchmovearr[i].entryPath) window.location.assign('/' + vueHeader.searchmovearr[i].entryPath)
} else { } else {
vueHeader.searchq = vueHeader.searchmovearr[i] vueHeader.searchq = vueHeader.searchmovearr[i]
} }
}, },
moveDownSearch: () => { moveDownSearch: () => {
if (vueHeader.searchmoveidx < vueHeader.searchmovearr.length) { if (vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
vueHeader.searchmoveidx++ vueHeader.searchmoveidx++
} }
}, },
moveUpSearch: () => { moveUpSearch: () => {
if (vueHeader.searchmoveidx > 0) { if (vueHeader.searchmoveidx > 0) {
vueHeader.searchmoveidx-- vueHeader.searchmoveidx--
}
} }
} }
} })
})
$('main').on('click', vueHeader.closeSearch) $('main').on('click', vueHeader.closeSearch)
}
} }
/* 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 */
/* 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)))
})
/* eslint-enable no-unused-vars */ return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r) }), '/')
}
}
/* 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) => {
$('#modal-admin-users-create').removeClass('is-active')
vueCreateUser.email = ''
vueCreateUser.provider = 'local'
}, },
create: (ev) => { methods: {
vueCreateUser.loading = true open: (ev) => {
$.ajax('/admin/users/create', { $('#modal-admin-users-create').addClass('is-active')
data: { $('#modal-admin-users-create input').first().focus()
email: vueCreateUser.email, },
provider: vueCreateUser.provider, cancel: (ev) => {
password: vueCreateUser.password, $('#modal-admin-users-create').removeClass('is-active')
name: vueCreateUser.name vueCreateUser.email = ''
}, vueCreateUser.provider = 'local'
dataType: 'json', },
method: 'POST' create: (ev) => {
}).then((rData, rStatus, rXHR) => { vueCreateUser.loading = true
vueCreateUser.loading = false $.ajax('/admin/users/create', {
if (rData.ok) { data: {
vueCreateUser.cancel() email: vueCreateUser.email,
window.location.reload(true) provider: vueCreateUser.provider,
} else { password: vueCreateUser.password,
alerts.pushError('Something went wrong', rData.msg) name: vueCreateUser.name
} },
}, (rXHR, rStatus, err) => { dataType: 'json',
vueCreateUser.loading = false method: 'POST'
alerts.pushError('Error', rXHR.responseJSON.msg) }).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)
}
/* 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) => {
$('#modal-admin-users-delete').removeClass('is-active')
}, },
deleteUser: (ev) => { methods: {
vueDeleteUser.loading = true open: (ev) => {
$.ajax('/admin/users/' + usrData._id, { $('#modal-admin-users-delete').addClass('is-active')
dataType: 'json', },
method: 'DELETE' cancel: (ev) => {
}).then((rData, rStatus, rXHR) => { $('#modal-admin-users-delete').removeClass('is-active')
vueDeleteUser.loading = false },
vueDeleteUser.cancel() deleteUser: (ev) => {
window.location.assign('/admin/users') vueDeleteUser.loading = true
}, (rXHR, rStatus, err) => { $.ajax('/admin/users/' + usrData._id, {
vueDeleteUser.loading = false dataType: 'json',
alerts.pushError('Error', rXHR.responseJSON.msg) 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)
}
/* 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)
} }
}) })
}
/* 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 let moveInitialDocument = _.lastIndexOf(currentBasePath, '/') + 1
$('.btn-move-prompt').on('click', (ev) => { $('.btn-move-prompt').on('click', (ev) => {
$('#txt-move-prompt').val(currentBasePath) $('#txt-move-prompt').val(currentBasePath)
$('#modal-move-prompt').toggleClass('is-active') $('#modal-move-prompt').toggleClass('is-active')
setInputSelection($('#txt-move-prompt').get(0), moveInitialDocument, currentBasePath.length) // eslint-disable-line no-undef setInputSelection($('#txt-move-prompt').get(0), moveInitialDocument, currentBasePath.length)
$('#txt-move-prompt').removeClass('is-danger').next().addClass('is-hidden') $('#txt-move-prompt').removeClass('is-danger').next().addClass('is-hidden')
}) })
$('#txt-move-prompt').on('keypress', (ev) => { $('#txt-move-prompt').on('keypress', (ev) => {
if (ev.which === 13) { if (ev.which === 13) {
$('.btn-move-go').trigger('click') $('.btn-move-go').trigger('click')
} }
}) })
$('.btn-move-go').on('click', (ev) => { $('.btn-move-go').on('click', (ev) => {
let newDocPath = makeSafePath($('#txt-move-prompt').val()) // eslint-disable-line no-undef let newDocPath = makeSafePath($('#txt-move-prompt').val())
if (_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') { if (_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') {
$('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden') $('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden')
} else { } else {
$('#txt-move-prompt').parent().addClass('is-loading') $('#txt-move-prompt').parent().addClass('is-loading')
$.ajax(window.location.href, { $.ajax(window.location.href, {
data: { data: {
move: newDocPath move: newDocPath
}, },
dataType: 'json', dataType: 'json',
method: 'PUT' method: 'PUT'
}).then((rData, rStatus, rXHR) => { }).then((rData, rStatus, rXHR) => {
if (rData.ok) { if (rData.ok) {
window.location.assign('/' + newDocPath) window.location.assign('/' + newDocPath)
} else { } else {
alerts.pushError('Something went wrong', rData.error) alerts.pushError('Something went wrong', rData.error)
} }
}, (rXHR, rStatus, err) => { }, (rXHR, rStatus, err) => {
alerts.pushError('Something went wrong', 'Save operation failed.') alerts.pushError('Something went wrong', 'Save operation failed.')
}) })
} }
}) })
}
/* 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',
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) {
/* eslint-disable spaced-comment */ import $ from 'jquery'
//=include ../modals/admin-users-create.js import _ from 'lodash'
/* eslint-enable spaced-comment */ import Vue from 'vue'
} else if ($('#page-type-admin-users-edit').length) { module.exports = (alerts) => {
let vueEditUser = new Vue({ if ($('#page-type-admin-profile').length) {
el: '#page-type-admin-users-edit', let vueProfile = new Vue({
data: { el: '#page-type-admin-profile',
id: '', data: {
email: '', password: '********',
password: '********', passwordVerify: '********',
name: '', 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()
}, },
saveUser: (ev) => { methods: {
let formattedRights = _.cloneDeep(vueEditUser.rights) saveUser: (ev) => {
switch (vueEditUser.roleoverride) { if (vueProfile.password !== vueProfile.passwordVerify) {
case 'admin': alerts.pushError('Error', "Passwords don't match!")
formattedRights.push({ return
role: 'admin', }
path: '/', $.post(window.location.href, {
exact: false, password: vueProfile.password,
deny: false name: vueProfile.name
}) }).done((resp) => {
break 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) => { created: function () {
vueSettings.upgradeModal.state = false this.name = usrDataName
}, }
upgradeStart: (ev) => { })
vueSettings.upgradeModal.step = 'running' } else if ($('#page-type-admin-users').length) {
$.post('/admin/settings/install', { require('../modals/admin-users-create.js')(alerts)
mode: vueSettings.upgradeModal.mode } else if ($('#page-type-admin-users-edit').length) {
}).done((resp) => { let vueEditUser = new Vue({
// todo el: '#page-type-admin-users-edit',
}).fail((jqXHR, txtStatus, resp) => { data: {
vueSettings.upgradeModal.step = 'error' id: '',
vueSettings.upgradeModal.error = jqXHR.responseText email: '',
}) password: '********',
name: '',
rights: [],
roleoverride: 'none'
}, },
flushcache: (ev) => { methods: {
window.alert('Coming soon!') 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) => { created: function () {
window.alert('Coming soon!') 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) => { methods: {
window.alert('Coming soon!') 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!')
}
} }
} })
}) }
} }
/* 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)
}
} }
/* 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)
}
} }
/* 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)
}
} }
...@@ -19,6 +19,8 @@ $primary: 'indigo'; ...@@ -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';
......
...@@ -27,6 +27,21 @@ const args = require('yargs') ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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({
......
...@@ -43,13 +43,13 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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": [
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment