Commit c20c935f authored by NGPixel's avatar NGPixel

refactor: Migrate to Vue components

parent 2876b693
{ {
"extends": "standard", "extends": "standard",
"env": { "env": {
"node": true, "node": true,
"es6": true, "es6": true,
"jest": true "jest": true
}, },
"globals": { "globals": {
"document": false, "document": false,
"navigator": false, "navigator": false,
...@@ -16,5 +14,8 @@ ...@@ -16,5 +14,8 @@
"ROOTPATH": true, "ROOTPATH": true,
"SERVERPATH": true, "SERVERPATH": true,
"IS_DEBUG": true "IS_DEBUG": true
},
"rules": {
"space-before-function-paren": 0
} }
} }
'use strict' 'use strict'
/* global alertsData */ /* global alertsData, siteLang */
/* eslint-disable no-new */
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import _ from 'lodash'
import Vue from 'vue'
import Vuex from 'vuex'
import io from 'socket.io-client' import io from 'socket.io-client'
import i18next from 'i18next'
import i18nextXHR from 'i18next-xhr-backend'
import VueI18Next from '@panter/vue-i18next'
import Alerts from './components/alerts.js' import Alerts from './components/alerts.js'
import 'jquery-smooth-scroll' import 'jquery-smooth-scroll'
import 'jquery-sticky' import 'jquery-sticky'
// ====================================
// Load Vue Components
// ====================================
import anchorComponent from './components/anchor.vue'
import colorPickerComponent from './components/color-picker.vue'
import loadingSpinnerComponent from './components/loading-spinner.vue'
import searchComponent from './components/search.vue'
import adminProfileComponent from './pages/admin-profile.component.js'
import adminSettingsComponent from './pages/admin-settings.component.js'
// ====================================
// Initialize i18next
// ====================================
Vue.use(VueI18Next)
i18next
.use(i18nextXHR)
.init({
backend: {
loadPath: '/js/i18n/{{lng}}.json'
},
lng: siteLang,
fallbackLng: siteLang
})
// ====================================
// Initialize Vuex
// ====================================
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
loading: false
},
mutations: {
startLoading: state => { state.loading = true },
stopLoading: state => { state.loading = false }
}
})
$(() => { $(() => {
// ==================================== // ====================================
// Scroll // Scroll
...@@ -27,28 +77,47 @@ $(() => { ...@@ -27,28 +77,47 @@ $(() => {
// ==================================== // ====================================
$(window).bind('beforeunload', () => { $(window).bind('beforeunload', () => {
$('#notifload').addClass('active') store.commit('startLoading')
}) })
$(document).ajaxSend(() => { $(document).ajaxSend(() => {
$('#notifload').addClass('active') store.commit('startLoading')
}).ajaxComplete(() => { }).ajaxComplete(() => {
$('#notifload').removeClass('active') store.commit('stopLoading')
}) })
var alerts = new Alerts() var alerts = {}
/*var alerts = new Alerts()
if (alertsData) { if (alertsData) {
_.forEach(alertsData, (alertRow) => { _.forEach(alertsData, (alertRow) => {
alerts.push(alertRow) alerts.push(alertRow)
}) })
} }*/
// ==================================== // ====================================
// Establish WebSocket connection // Establish WebSocket connection
// ==================================== // ====================================
var socket = io(window.location.origin) let socket = io(window.location.origin)
window.socket = socket
require('./components/search.js')(socket) // ====================================
// Bootstrap Vue
// ====================================
const i18n = new VueI18Next(i18next)
new Vue({
components: {
adminProfile: adminProfileComponent,
adminSettings: adminSettingsComponent,
anchor: anchorComponent,
colorPicker: colorPickerComponent,
loadingSpinner: loadingSpinnerComponent,
search: searchComponent
},
store,
i18n,
el: '#root'
})
// ==================================== // ====================================
// Pages logic // Pages logic
......
...@@ -6,12 +6,12 @@ ...@@ -6,12 +6,12 @@
</template> </template>
<script> <script>
export default { export default {
name: 'app', name: 'anchor',
data () { data () {
return { return {
msg: 'Welcome to Your Vue.js App' msg: 'Welcome to Your Vue.js App'
} }
}
} }
}
</script> </script>
<template lang="pug">
p.control
input.input(type='text', placeholder='#F0F0F0', v-model='color')
</template>
<script>
export default {
name: 'color-picker',
data () {
return {
color: '000000'
}
}
}
</script>
<template lang="pug">
i.nav-item#notifload(v-bind:class='{ "is-active": loading }')
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'loading-spinner',
computed: mapState(['loading'])
}
</script>
'use strict'
import $ from 'jquery'
import _ from 'lodash'
import Vue from 'vue'
module.exports = (socket) => {
if ($('#search-input').length) {
$('#search-input').focus()
$('.searchresults').css('display', 'block')
var vueHeader = new Vue({
el: '#header-container',
data: {
searchq: '',
searchres: [],
searchsuggest: [],
searchload: 0,
searchactive: false,
searchmoveidx: 0,
searchmovekey: '',
searchmovearr: []
},
watch: {
searchq: (val, oldVal) => {
vueHeader.searchmoveidx = 0
if (val.length >= 3) {
vueHeader.searchactive = true
vueHeader.searchload++
socket.emit('search', { terms: val }, (data) => {
vueHeader.searchres = data.match
vueHeader.searchsuggest = data.suggest
vueHeader.searchmovearr = _.concat([], vueHeader.searchres, vueHeader.searchsuggest)
if (vueHeader.searchload > 0) { vueHeader.searchload-- }
})
} else {
vueHeader.searchactive = false
vueHeader.searchres = []
vueHeader.searchsuggest = []
vueHeader.searchmovearr = []
vueHeader.searchload = 0
}
},
searchmoveidx: (val, oldVal) => {
if (val > 0) {
vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1])
? 'res.' + vueHeader.searchmovearr[val - 1].entryPath
: 'sug.' + vueHeader.searchmovearr[val - 1]
} else {
vueHeader.searchmovekey = ''
}
}
},
methods: {
useSuggestion: (sug) => {
vueHeader.searchq = sug
},
closeSearch: () => {
vueHeader.searchq = ''
},
moveSelectSearch: () => {
if (vueHeader.searchmoveidx < 1) { return }
let i = vueHeader.searchmoveidx - 1
if (vueHeader.searchmovearr[i]) {
window.location.assign('/' + vueHeader.searchmovearr[i].entryPath)
} else {
vueHeader.searchq = vueHeader.searchmovearr[i]
}
},
moveDownSearch: () => {
if (vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
vueHeader.searchmoveidx++
}
},
moveUpSearch: () => {
if (vueHeader.searchmoveidx > 0) {
vueHeader.searchmoveidx--
}
}
}
})
$('main').on('click', vueHeader.closeSearch)
}
}
<template lang="pug">
.nav-item
p.control(v-bind:class='{ "is-loading": searchload > 0 }')
input.input#search-input(type='text', v-model='searchq', autofocus, @keyup.esc='closeSearch', @keyup.down='moveDownSearch', @keyup.up='moveUpSearch', @keyup.enter='moveSelectSearch', debounce='400', v-bind:placeholder='$t("search.placeholder")')
transition(name='searchresults')
.searchresults(v-show='searchactive', v-cloak)
p.searchresults-label {{ $t('search.results') }}
ul.searchresults-list
li(v-if='searchres.length === 0')
a: em {{ $t('search.nomatch') }}
li(v-for='sres in searchres', v-bind:class='{ "is-active": searchmovekey === "res." + sres.entryPath }')
a(v-bind:href='"/" + sres.entryPath') {{ sres.title }}
p.searchresults-label(v-if='searchsuggest.length > 0') {{ $t('search.didyoumean') }}
ul.searchresults-list(v-if='searchsuggest.length > 0')
li(v-for='sug in searchsuggest', v-bind:class='{ "is-active": searchmovekey === "sug." + sug }')
a(v-on:click='useSuggestion(sug)') {{ sug }}
</template>
<script>
import * as _ from 'lodash'
import * as $ from 'jquery'
export default {
data () {
return {
searchq: '',
searchres: [],
searchsuggest: [],
searchload: 0,
searchactive: false,
searchmoveidx: 0,
searchmovekey: '',
searchmovearr: []
}
},
watch: {
searchq: function (val, oldVal) {
let self = this
self.searchmoveidx = 0
if (val.length >= 3) {
self.searchactive = true
self.searchload++
socket.emit('search', { terms: val }, (data) => {
self.searchres = data.match
self.searchsuggest = data.suggest
self.searchmovearr = _.concat([], self.searchres, self.searchsuggest)
if (self.searchload > 0) { self.searchload-- }
})
} else {
self.searchactive = false
self.searchres = []
self.searchsuggest = []
self.searchmovearr = []
self.searchload = 0
}
},
searchmoveidx: function (val, oldVal) {
if (val > 0) {
this.searchmovekey = (this.searchmovearr[val - 1])
? 'res.' + this.searchmovearr[val - 1].entryPath
: 'sug.' + this.searchmovearr[val - 1]
} else {
this.searchmovekey = ''
}
}
},
methods: {
useSuggestion: function (sug) {
this.searchq = sug
},
closeSearch: function() {
this.searchq = ''
},
moveSelectSearch: function () {
if (this.searchmoveidx < 1) { return }
let i = this.searchmoveidx - 1
if (this.searchmovearr[i]) {
window.location.assign('/' + this.searchmovearr[i].entryPath)
} else {
this.searchq = this.searchmovearr[i]
}
},
moveDownSearch: function () {
if (this.searchmoveidx < this.searchmovearr.length) {
this.searchmoveidx++
}
},
moveUpSearch: function () {
if (this.searchmoveidx > 0) {
this.searchmoveidx--
}
}
},
mounted: function () {
let self = this
$('main').on('click', self.closeSearch)
}
}
</script>
<template lang="pug">
.modal(v-if='isShown')
.modal-background
.modal-container
.modal-content
header.is-light-blue Create New Document
section
label.label Enter the new document path:
p.control.is-fullwidth(v-class='{ "is-loading": isLoading }')
input.input(type='text', placeholder='page-name', v-model='entrypath', autofocus)
span.help.is-danger(v-show='isInvalid') This document path is invalid!
footer
a.button.is-grey.is-outlined(v-on:click='hide') Discard
a.button.is-light-blue(v-on:click='create') Create
</template>
<script>
import * as _ from 'lodash'
import { makeSafePath } from '../helpers/pages'
export default {
name: 'modal-create',
data () {
return {
entrypath: ''
isInvalid: false,
isLoading: false,
isShown: false
}
},
methods: {
show: function () {
this.isInvalid = false
this.shown = true
},
hide: function () {
this.shown = false
},
create: function () {
this.isInvalid = false
let newDocPath = makeSafePath(this.entrypath)
if (_.isEmpty(newDocPath)) {
this.isInvalid = true
} else {
$('#txt-create-prompt').parent().addClass('is-loading')
window.location.assign('/create/' + newDocPath)
}
}
},
mounted () {
this.entrypath = currentBasePath + '/new-page'
}
}
</script>
'use strict'
import * as $ from 'jquery'
export default {
name: 'admin-profile',
props: ['email', 'name', 'provider'],
data() {
return {
password: '********',
passwordVerify: '********'
}
},
methods: {
saveUser() {
if (this.password !== this.passwordVerify) {
//alerts.pushError('Error', "Passwords don't match!")
return
}
$.post(window.location.href, {
password: this.password,
name: this.name
}).done((resp) => {
//alerts.pushSuccess('Saved successfully', 'Changes have been applied.')
}).fail((jqXHR, txtStatus, resp) => {
//alerts.pushError('Error', resp)
})
}
}
}
'use strict'
import * as $ from 'jquery'
export default {
name: 'admin-settings',
data() {
return {
upgradeModal: {
state: false,
step: 'confirm',
mode: 'upgrade',
error: 'Something went wrong.'
}
}
},
methods: {
upgrade() {
this.upgradeModal.mode = 'upgrade'
this.upgradeModal.step = 'confirm'
this.upgradeModal.state = true
},
reinstall() {
this.upgradeModal.mode = 're-install'
this.upgradeModal.step = 'confirm'
this.upgradeModal.state = true
},
upgradeCancel() {
this.upgradeModal.state = false
},
upgradeStart() {
this.upgradeModal.step = 'running'
$.post('/admin/settings/install', {
mode: this.upgradeModal.mode
}).done((resp) => {
// todo
}).fail((jqXHR, txtStatus, resp) => {
this.upgradeModal.step = 'error'
this.upgradeModal.error = jqXHR.responseText
})
},
flushcache() {
window.alert('Coming soon!')
},
resetaccounts() {
window.alert('Coming soon!')
},
flushsessions() {
window.alert('Coming soon!')
}
}
}
'use strict' 'use strict'
/* global usrData, usrDataName */ /* global usrData */
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import _ from 'lodash'
import Vue from 'vue' import Vue from 'vue'
module.exports = (alerts) => { module.exports = (alerts) => {
if ($('#page-type-admin-profile').length) { if ($('#page-type-admin-users').length) {
let vueProfile = new Vue({
el: '#page-type-admin-profile',
data: {
password: '********',
passwordVerify: '********',
name: ''
},
methods: {
saveUser: (ev) => {
if (vueProfile.password !== vueProfile.passwordVerify) {
alerts.pushError('Error', "Passwords don't match!")
return
}
$.post(window.location.href, {
password: vueProfile.password,
name: vueProfile.name
}).done((resp) => {
alerts.pushSuccess('Saved successfully', 'Changes have been applied.')
}).fail((jqXHR, txtStatus, resp) => {
alerts.pushError('Error', resp)
})
}
},
created: function () {
this.name = usrDataName
}
})
} else if ($('#page-type-admin-users').length) {
require('../modals/admin-users-create.js')(alerts) require('../modals/admin-users-create.js')(alerts)
} else if ($('#page-type-admin-users-edit').length) { } else if ($('#page-type-admin-users-edit').length) {
let vueEditUser = new Vue({ let vueEditUser = new Vue({
...@@ -98,52 +70,5 @@ module.exports = (alerts) => { ...@@ -98,52 +70,5 @@ module.exports = (alerts) => {
} }
}) })
require('../modals/admin-users-delete.js')(alerts) 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.'
}
},
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!')
}
}
})
} }
} }
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import $ from 'jquery' import $ from 'jquery'
import MathJax from 'mathjax' import MathJax from 'mathjax'
import * as CopyPath from '../components/copy-path.vue' // import * as CopyPath from '../components/copy-path.vue'
import Vue from 'vue' import Vue from 'vue'
module.exports = (alerts) => { module.exports = (alerts) => {
...@@ -13,10 +13,10 @@ module.exports = (alerts) => { ...@@ -13,10 +13,10 @@ module.exports = (alerts) => {
// Copy Path // Copy Path
new Vue({ // new Vue({
el: '.modal-copypath', // el: '.modal-copypath',
render: h => h(CopyPath) // render: h => h(CopyPath)
}) // })
// MathJax Render // MathJax Render
......
.searchresults { .searchresults {
position: fixed; position: fixed;
top: 45px; top: 50px;
left: 0; left: 0;
right: 0; right: 0;
margin: 0 auto; margin: 0 auto;
width: 500px; width: 500px;
z-index: 1; z-index: 1;
background-color: mc($primary, '700'); background-color: darken(mc('blue-grey', '900'), 2%);
border-bottom: 5px solid mc($primary, '800'); border: 1px solid mc('blue-grey', '900');
box-shadow: 0 0 5px mc($primary, '500'); box-shadow: 0 0 5px mc('blue-grey', '500');
color: #FFF; color: #FFF;
transition: max-height 1s ease;
&.slideInDown { &-enter-active, &-leave-active {
@include prefix(animation-duration, .6s); overflow: hidden;
} }
&-enter-to, &-leave {
max-height: 500px;
}
&-enter, &-leave-to {
max-height: 0px;
}
.searchresults-label { .searchresults-label {
color: mc($primary, '200'); background-color: mc('blue-grey', '800');
padding: 15px 10px 10px; color: mc('blue-grey', '300');
padding: 8px;
font-size: 13px; font-size: 13px;
text-transform: uppercase; letter-spacing: 1px;
border-bottom: 1px dotted mc($primary, '400'); text-transform: uppercase;
box-shadow: 0 0 5px rgba(0,0,0,0.3);
} }
.searchresults-list { .searchresults-list {
padding-bottom: 5px;
> li { > li {
display: flex; display: flex;
font-size: 14px; font-size: 14px;
transition: background-color .3s linear; transition: background-color .2s linear;
&:nth-child(odd) { &:nth-child(odd) {
background-color: mc($primary, '600'); background-color: mc('blue-grey', '900');
} }
&.is-active, &:hover { &.is-active, &:hover {
background-color: mc($primary, '400'); background-color: mc('blue-grey', '600');
color: #FFF; color: #FFF;
border-left: 5px solid mc($primary, '200');
} }
a { a {
color: mc($primary, '50'); color: mc('blue-grey', '50');
display: flex; display: flex;
align-items: center; align-items: center;
height: 30px; height: 30px;
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
@include spinner(mc('indigo', '100'),0.5s,24px); @include spinner(mc('indigo', '100'),0.5s,24px);
} }
&.active { &.is-active {
opacity: 1; opacity: 1;
} }
...@@ -33,4 +33,4 @@ ...@@ -33,4 +33,4 @@
#search-input { #search-input {
max-width: 300px; max-width: 300px;
width: 33vw; width: 33vw;
} }
\ No newline at end of file
...@@ -88,37 +88,39 @@ let globalTasks = Promise.mapSeries([ ...@@ -88,37 +88,39 @@ let globalTasks = Promise.mapSeries([
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
console.info(colors.white(' └── ') + colors.green('Copy MathJax dependencies to assets...')) console.info(colors.white(' └── ') + colors.green('Copy MathJax dependencies to assets...'))
return fs.ensureDirAsync('./assets/js/mathjax').then(() => { return fs.ensureDirAsync('./assets/js/mathjax').then(() => {
return fs.copyAsync('./node_modules/mathjax', './assets/js/mathjax', { filter: (src, dest) => { return fs.copyAsync('./node_modules/mathjax', './assets/js/mathjax', {
let srcNormalized = src.replace(/\\/g, '/') filter: (src, dest) => {
let shouldCopy = false let srcNormalized = src.replace(/\\/g, '/')
console.log(srcNormalized) let shouldCopy = false
_.forEach([ console.info(colors.white(' ' + srcNormalized))
'/node_modules/mathjax', _.forEach([
'/node_modules/mathjax/jax', '/node_modules/mathjax',
'/node_modules/mathjax/jax/input', '/node_modules/mathjax/jax',
'/node_modules/mathjax/jax/output' '/node_modules/mathjax/jax/input',
], chk => { '/node_modules/mathjax/jax/output'
if (srcNormalized.endsWith(chk)) { ], chk => {
shouldCopy = true if (srcNormalized.endsWith(chk)) {
shouldCopy = true
}
})
_.forEach([
'/node_modules/mathjax/extensions',
'/node_modules/mathjax/MathJax.js',
'/node_modules/mathjax/jax/element',
'/node_modules/mathjax/jax/input/MathML',
'/node_modules/mathjax/jax/input/TeX',
'/node_modules/mathjax/jax/output/SVG'
], chk => {
if (srcNormalized.indexOf(chk) > 0) {
shouldCopy = true
}
})
if (shouldCopy && srcNormalized.indexOf('/fonts/') > 0 && srcNormalized.indexOf('/STIX-Web') <= 1) {
shouldCopy = false
} }
}) return shouldCopy
_.forEach([
'/node_modules/mathjax/extensions',
'/node_modules/mathjax/MathJax.js',
'/node_modules/mathjax/jax/element',
'/node_modules/mathjax/jax/input/MathML',
'/node_modules/mathjax/jax/input/TeX',
'/node_modules/mathjax/jax/output/SVG'
], chk => {
if (srcNormalized.indexOf(chk) > 0) {
shouldCopy = true
}
})
if (shouldCopy && srcNormalized.indexOf('/fonts/') > 0 && srcNormalized.indexOf('/STIX-Web') <= 1) {
shouldCopy = false
} }
return shouldCopy })
}})
}) })
} else { } else {
throw err throw err
...@@ -126,6 +128,27 @@ let globalTasks = Promise.mapSeries([ ...@@ -126,6 +128,27 @@ let globalTasks = Promise.mapSeries([
}) })
}, },
/** /**
* i18n
*/
() => {
console.info(colors.white(' └── ') + colors.green('Copying i18n client files...'))
return fs.ensureDirAsync('./assets/js/i18n').then(() => {
return fs.readJsonAsync('./server/locales/en/browser.json').then(enContent => {
return fs.readdirAsync('./server/locales').then(langs => {
return Promise.map(langs, lang => {
console.info(colors.white(' ' + lang + '.json'))
let outputPath = path.join('./assets/js/i18n', lang + '.json')
return fs.readJsonAsync(path.join('./server/locales', lang + '.json'), 'utf8').then((content) => {
return fs.outputJsonAsync(outputPath, _.defaultsDeep(content, enContent))
}).catch(err => { // eslint-disable-line handle-callback-err
return fs.outputJsonAsync(outputPath, enContent)
})
})
})
})
})
},
/**
* Bundle pre-init scripts * Bundle pre-init scripts
*/ */
() => { () => {
...@@ -144,6 +167,7 @@ let globalTasks = Promise.mapSeries([ ...@@ -144,6 +167,7 @@ let globalTasks = Promise.mapSeries([
* Delete Fusebox cache * Delete Fusebox cache
*/ */
() => { () => {
console.info(colors.white(' └── ') + colors.green('Clearing fuse-box cache...'))
return fs.emptyDirAsync('./.fusebox') return fs.emptyDirAsync('./.fusebox')
} }
], f => { return f() }) ], f => { return f() })
...@@ -156,7 +180,7 @@ const ALIASES = { ...@@ -156,7 +180,7 @@ const ALIASES = {
'brace-ext-modelist': 'brace/ext/modelist.js', 'brace-ext-modelist': 'brace/ext/modelist.js',
'simplemde': 'simplemde/dist/simplemde.min.js', 'simplemde': 'simplemde/dist/simplemde.min.js',
'socket.io-client': 'socket.io-client/dist/socket.io.js', 'socket.io-client': 'socket.io-client/dist/socket.io.js',
'vue': 'vue/dist/vue.min.js' 'vue': (dev) ? 'vue/dist/vue.js' : 'vue/dist/vue.min.js'
} }
const SHIMS = { const SHIMS = {
_preinit: { _preinit: {
...@@ -182,7 +206,7 @@ globalTasks.then(() => { ...@@ -182,7 +206,7 @@ globalTasks.then(() => {
plugins: [ plugins: [
fsbx.EnvPlugin({ NODE_ENV: (dev) ? 'development' : 'production' }), fsbx.EnvPlugin({ NODE_ENV: (dev) ? 'development' : 'production' }),
fsbx.VuePlugin(), fsbx.VuePlugin(),
[ '.scss', fsbx.SassPlugin({ outputStyle: (dev) ? 'nested' : 'compressed' }), fsbx.CSSPlugin() ], ['.scss', fsbx.SassPlugin({ outputStyle: (dev) ? 'nested' : 'compressed' }), fsbx.CSSPlugin()],
fsbx.BabelPlugin({ comments: false, presets: ['es2015'] }), fsbx.BabelPlugin({ comments: false, presets: ['es2015'] }),
fsbx.JSONPlugin(), fsbx.JSONPlugin(),
!dev && fsbx.UglifyJSPlugin({ !dev && fsbx.UglifyJSPlugin({
......
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
"fs-extra": "^3.0.1", "fs-extra": "^3.0.1",
"git-wrapper2-promise": "^0.2.9", "git-wrapper2-promise": "^0.2.9",
"highlight.js": "^9.11.0", "highlight.js": "^9.11.0",
"i18next": "^8.2.0", "i18next": "^8.3.0",
"i18next-express-middleware": "^1.0.5", "i18next-express-middleware": "^1.0.5",
"i18next-node-fs-backend": "^1.0.0", "i18next-node-fs-backend": "^1.0.0",
"image-size": "^0.5.4", "image-size": "^0.5.4",
...@@ -78,7 +78,7 @@ ...@@ -78,7 +78,7 @@
"markdown-it": "^8.3.1", "markdown-it": "^8.3.1",
"markdown-it-abbr": "^1.0.4", "markdown-it-abbr": "^1.0.4",
"markdown-it-anchor": "^4.0.0", "markdown-it-anchor": "^4.0.0",
"markdown-it-attrs": "^0.8.0", "markdown-it-attrs": "^0.9.0",
"markdown-it-emoji": "^1.3.0", "markdown-it-emoji": "^1.3.0",
"markdown-it-expand-tabs": "^1.0.12", "markdown-it-expand-tabs": "^1.0.12",
"markdown-it-external-links": "0.0.6", "markdown-it-external-links": "0.0.6",
...@@ -126,11 +126,13 @@ ...@@ -126,11 +126,13 @@
}, },
"devDependencies": { "devDependencies": {
"@glimpse/glimpse": "^0.20.9", "@glimpse/glimpse": "^0.20.9",
"@panter/vue-i18next": "^0.4.1",
"babel-cli": "latest", "babel-cli": "latest",
"babel-jest": "latest", "babel-jest": "latest",
"babel-preset-es2015": "latest", "babel-preset-es2015": "latest",
"brace": "^0.10.0", "brace": "^0.10.0",
"colors": "^1.1.2", "colors": "^1.1.2",
"consolidate": "^0.14.5",
"eslint": "latest", "eslint": "latest",
"eslint-config-standard": "latest", "eslint-config-standard": "latest",
"eslint-plugin-import": "latest", "eslint-plugin-import": "latest",
...@@ -138,6 +140,7 @@ ...@@ -138,6 +140,7 @@
"eslint-plugin-promise": "latest", "eslint-plugin-promise": "latest",
"eslint-plugin-standard": "latest", "eslint-plugin-standard": "latest",
"fuse-box": "^2.0.0", "fuse-box": "^2.0.0",
"i18next-xhr-backend": "^1.4.1",
"jest": "latest", "jest": "latest",
"jquery": "^3.2.1", "jquery": "^3.2.1",
"jquery-contextmenu": "^2.4.5", "jquery-contextmenu": "^2.4.5",
...@@ -155,7 +158,8 @@ ...@@ -155,7 +158,8 @@
"vee-validate": "^2.0.0-rc.3", "vee-validate": "^2.0.0-rc.3",
"vue": "^2.3.3", "vue": "^2.3.3",
"vue-template-compiler": "^2.3.3", "vue-template-compiler": "^2.3.3",
"vue-template-es2015-compiler": "^1.5.2" "vue-template-es2015-compiler": "^1.5.2",
"vuex": "^2.3.1"
}, },
"jest": { "jest": {
"collectCoverage": false, "collectCoverage": false,
...@@ -166,4 +170,4 @@ ...@@ -166,4 +170,4 @@
"verbose": true "verbose": true
}, },
"snyk": true "snyk": true
} }
\ No newline at end of file
...@@ -255,4 +255,11 @@ router.post('/settings/install', (req, res) => { ...@@ -255,4 +255,11 @@ router.post('/settings/install', (req, res) => {
res.status(400).send('Sorry, Upgrade/Re-Install via the web UI is not yet ready. You must use the npm upgrade method in the meantime.').end() res.status(400).send('Sorry, Upgrade/Re-Install via the web UI is not yet ready. You must use the npm upgrade method in the meantime.').end()
}) })
router.get('/theme', (req, res) => {
if (!res.locals.rights.manage) {
return res.render('error-forbidden')
}
res.render('pages/admin/theme', { adminTab: 'theme' })
})
module.exports = router module.exports = router
...@@ -48,4 +48,4 @@ ...@@ -48,4 +48,4 @@
"edituser": "Edit User", "edituser": "Edit User",
"uniqueid": "Unique ID" "uniqueid": "Unique ID"
} }
} }
\ No newline at end of file
{
"profile": {
"displayname": "Display Name",
"displaynameexample": "John Smith",
"email": "Email",
"password": "Password",
"passwordverify": "Verify Password",
"savechanges": "Save Changes"
},
"search": {
"placeholder": "Search...",
"results": "Search Results",
"nomatch": "No results matching your query",
"didyoumean": "Did you mean...?"
}
}
...@@ -9,12 +9,6 @@ ...@@ -9,12 +9,6 @@
"home": "Home", "home": "Home",
"top": "Return to top" "top": "Return to top"
}, },
"search": {
"placeholder": "Search...",
"results": "Search Results",
"nomatch": "No results matching your query",
"didyoumean": "Did you mean...?"
},
"sidebar": { "sidebar": {
"nav": "NAV", "nav": "NAV",
"navigation": "Navigation", "navigation": "Navigation",
...@@ -24,9 +18,11 @@ ...@@ -24,9 +18,11 @@
"nav": { "nav": {
"home": "Home", "home": "Home",
"account": "Account", "account": "Account",
"settings": "Settings",
"myprofile": "My Profile", "myprofile": "My Profile",
"stats": "Stats", "stats": "Stats",
"syssettings": "System Settings", "syssettings": "System Settings",
"theme": "Color Theme",
"users": "Users", "users": "Users",
"logout": "Logout", "logout": "Logout",
"create": "Create", "create": "Create",
...@@ -51,4 +47,4 @@ ...@@ -51,4 +47,4 @@
"source": "Loading source...", "source": "Loading source...",
"editor": "Loading editor..." "editor": "Loading editor..."
} }
} }
\ No newline at end of file
...@@ -24,9 +24,11 @@ ...@@ -24,9 +24,11 @@
"nav": { "nav": {
"home": "Accueil", "home": "Accueil",
"account": "Compte", "account": "Compte",
"settings": "Paramètres",
"myprofile": "Mon Profil", "myprofile": "Mon Profil",
"stats": "Statistiques", "stats": "Statistiques",
"syssettings": "Paramètres système", "syssettings": "Paramètres système",
"theme": "Thème de couleur",
"users": "Utilisateurs", "users": "Utilisateurs",
"logout": "Se Déconnecter", "logout": "Se Déconnecter",
"create": "Créer", "create": "Créer",
...@@ -51,4 +53,4 @@ ...@@ -51,4 +53,4 @@
"source": "Chargement de la source...", "source": "Chargement de la source...",
"editor": "Chargement de l'éditeur" "editor": "Chargement de l'éditeur"
} }
} }
\ No newline at end of file
...@@ -9,26 +9,11 @@ ...@@ -9,26 +9,11 @@
= appconfig.title = appconfig.title
.nav-center .nav-center
block rootNavCenter block rootNavCenter
.nav-item search
p.control(v-bind:class='{ "is-loading": searchload > 0 }')
input.input#search-input(type='text', v-model='searchq', @keyup.esc='closeSearch', @keyup.down='moveDownSearch', @keyup.up='moveUpSearch', @keyup.enter='moveSelectSearch', debounce='400', placeholder=t('search.placeholder'))
span.nav-toggle span.nav-toggle
span span
span span
span span
.nav-right .nav-right
block rootNavRight block rootNavRight
i.nav-item#notifload loading-spinner
transition(name='searchresults-anim', enter-active-class='slideInDown', leave-active-class='fadeOutUp')
.searchresults.animated(v-show='searchactive', v-cloak, style={'display':'none'})
p.searchresults-label= t('search.results')
ul.searchresults-list
li(v-if='searchres.length === 0')
a: em= t('search.nomatch')
li(v-for='sres in searchres', v-bind:class='{ "is-active": searchmovekey === "res." + sres.entryPath }')
a(v-bind:href='"/" + sres.entryPath') {{ sres.title }}
p.searchresults-label(v-if='searchsuggest.length > 0')= t('search.didyoumean')
ul.searchresults-list(v-if='searchsuggest.length > 0')
li(v-for='sug in searchsuggest', v-bind:class='{ "is-active": searchmovekey === "sug." + sug }')
a(v-on:click='useSuggestion(sug)') {{ sug }}
...@@ -9,7 +9,7 @@ html ...@@ -9,7 +9,7 @@ html
meta(name='msapplication-TileImage', content='/favicons/ms-icon-144x144.png') meta(name='msapplication-TileImage', content='/favicons/ms-icon-144x144.png')
title= appconfig.title title= appconfig.title
// Favicon //- Favicon
each favsize in [57, 60, 72, 76, 114, 120, 144, 152, 180] each favsize in [57, 60, 72, 76, 114, 120, 144, 152, 180]
link(rel='apple-touch-icon', sizes=favsize + 'x' + favsize, href='/favicons/apple-icon-' + favsize + 'x' + favsize + '.png') link(rel='apple-touch-icon', sizes=favsize + 'x' + favsize, href='/favicons/apple-icon-' + favsize + 'x' + favsize + '.png')
link(rel='icon', type='image/png', sizes='192x192', href='/favicons/android-icon-192x192.png') link(rel='icon', type='image/png', sizes='192x192', href='/favicons/android-icon-192x192.png')
...@@ -17,7 +17,10 @@ html ...@@ -17,7 +17,10 @@ html
link(rel='icon', type='image/png', sizes=favsize + 'x' + favsize, href='/favicons/favicon-' + favsize + 'x' + favsize + '.png') link(rel='icon', type='image/png', sizes=favsize + 'x' + favsize, href='/favicons/favicon-' + favsize + 'x' + favsize + '.png')
link(rel='manifest', href='/manifest.json') link(rel='manifest', href='/manifest.json')
// JS / CSS //- Site Lang
script var siteLang = '!{appconfig.lang}';
//- JS / CSS
script(type='text/javascript', src='/js/libs.min.js') script(type='text/javascript', src='/js/libs.min.js')
script(type='text/javascript', src='/js/app.min.js') script(type='text/javascript', src='/js/app.min.js')
...@@ -26,7 +29,7 @@ html ...@@ -26,7 +29,7 @@ html
body body
#root.has-stickynav #root.has-stickynav
include ./common/header.pug include ./common/header.pug
include ./common/alerts.pug //-include ./common/alerts.pug
main main
block content block content
include ./common/footer.pug include ./common/footer.pug
......
...@@ -4,7 +4,7 @@ block rootNavCenter ...@@ -4,7 +4,7 @@ block rootNavCenter
h2.nav-item= t('nav.account') h2.nav-item= t('nav.account')
block rootNavRight block rootNavRight
i.nav-item#notifload loading-spinner
.nav-item .nav-item
a.button.btn-edit-discard(href='/') a.button.btn-edit-discard(href='/')
i.icon-home i.icon-home
...@@ -48,6 +48,10 @@ block content ...@@ -48,6 +48,10 @@ block content
a(href='/admin/settings') a(href='/admin/settings')
i.icon-cog i.icon-cog
span= t('nav.syssettings') span= t('nav.syssettings')
li
a(href='/admin/theme')
i.icon-drop
span= t('nav.theme')
li li
a(href='/logout') a(href='/logout')
i.icon-delete2 i.icon-delete2
......
extends ./_layout.pug extends ./_layout.pug
block adminContent block adminContent
#page-type-admin-profile .hero
.hero h1.title#title= t('nav.myprofile')
h1.title#title= t('nav.myprofile') h2.subtitle= t('admin:profile.subtitle')
h2.subtitle= t('admin:profile.subtitle') .form-sections
.form-sections .columns.is-gapless
.columns.is-gapless .column.is-two-thirds
.column.is-two-thirds admin-profile(inline-template, email=user.email, name=user.name, provider=user.provider)
section div
label.label= t('admin:profile.email')
p.control.is-fullwidth
input.input(type='text', placeholder=t('admin:profile.email'), value=user.email, disabled)
if user.provider === 'local'
section section
label.label= t('admin:profile.password') label.label= t('admin:profile.email')
p.control.is-fullwidth p.control.is-fullwidth
input.input(type='password', placeholder=t('admin:profile.password'), value='********', v-model='password') input.input(type='text', placeholder=t('admin:profile.email'), value=user.email, disabled)
if user.provider === 'local'
section
label.label= t('admin:profile.password')
p.control.is-fullwidth
input.input(type='password', placeholder=t('admin:profile.password'), value='********', v-model='password')
section
label.label= t('admin:profile.passwordverify')
p.control.is-fullwidth
input.input(type='password', placeholder=t('admin:profile.password'), value='********', v-model='passwordVerify')
section section
label.label= t('admin:profile.passwordverify') label.label= t('admin:profile.displayname')
p.control.is-fullwidth p.control.is-fullwidth
input.input(type='password', placeholder=t('admin:profile.password'), value='********', v-model='passwordVerify') input.input(type='text', placeholder=t('admin:profile.displaynameexample'), v-model='name')
section section
label.label= t('admin:profile.displayname') button.button.is-green(v-on:click='saveUser')
p.control.is-fullwidth i.icon-check
input.input(type='text', placeholder=t('admin:profile.displaynameexample'), v-model='name') span= t('admin:profile.savechanges')
section .column
button.button.is-green(v-on:click='saveUser') .panel-aside
i.icon-check label.label= t('admin:profile.provider')
span= t('admin:profile.savechanges') p.control.account-profile-provider
.column case user.provider
.panel-aside when 'local': i.icon-server
label.label= t('admin:profile.provider') when 'windowslive': i.icon-windows2.is-blue
p.control.account-profile-provider when 'azure': i.icon-windows2.is-blue
case user.provider when 'google': i.icon-google.is-blue
when 'local': i.icon-server when 'facebook': i.icon-facebook.is-indigo
when 'windowslive': i.icon-windows2.is-blue when 'github': i.icon-github.is-grey
when 'azure': i.icon-windows2.is-blue when 'slack': i.icon-slack.is-purple
when 'google': i.icon-google.is-blue when 'ldap': i.icon-arrow-repeat-outline
when 'facebook': i.icon-facebook.is-indigo default: i.icon-warning
when 'github': i.icon-github.is-grey = t('auth:providers.' + user.provider)
when 'slack': i.icon-slack.is-purple label.label= t('admin:profile.membersince')
when 'ldap': i.icon-arrow-repeat-outline p.control= moment(user.createdAt).format('LL')
default: i.icon-warning label.label= t('admin:profile.lastprofileupdate')
= t('auth:providers.' + user.provider) p.control= moment(user.updatedAt).format('LL')
label.label= t('admin:profile.membersince')
p.control= moment(user.createdAt).format('LL')
label.label= t('admin:profile.lastprofileupdate')
p.control= moment(user.updatedAt).format('LL')
script(type='text/javascript').
var usrDataName = "!{user.name}";
extends ./_layout.pug extends ./_layout.pug
block adminContent block adminContent
#page-type-admin-settings .hero
.hero h1.title#title= t('nav.syssettings')
h1.title#title= t('nav.syssettings') h2.subtitle= t('admin:settings.subtitle')
h2.subtitle= t('admin:settings.subtitle') admin-settings(inline-template)
.form-sections .form-sections
section section
img(src='/images/logo.png', style={width:'200px', float:'right'}) img(src='/images/logo.png', style={width:'200px', float:'right'})
...@@ -34,4 +34,4 @@ block adminContent ...@@ -34,4 +34,4 @@ block adminContent
p.is-small= t('admin:settings.flushsessionstext') p.is-small= t('admin:settings.flushsessionstext')
p: button.button.is-teal.is-outlined(v-on:click='flushsessions')= t('admin:settings.flushsessionsbtn') p: button.button.is-teal.is-outlined(v-on:click='flushsessions')= t('admin:settings.flushsessionsbtn')
include ../../modals/admin-upgrade.pug include ../../modals/admin-upgrade.pug
extends ./_layout.pug
block adminContent
#page-type-admin-settings
.hero
h1.title#title= t('nav.theme')
h2.subtitle= t('admin:theme.subtitle')
.form-sections
section
label.label= t('admin:theme.primarycolor')
color-picker
...@@ -57,8 +57,8 @@ block content ...@@ -57,8 +57,8 @@ block content
if !isGuest if !isGuest
li li
a(href='/admin') a(href='/admin')
i.icon-head i.icon-cog
span= t('nav.account') span= t('nav.settings')
else else
li li
a(href='/login') a(href='/login')
......
This diff was suppressed by a .gitattributes entry.
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