diff --git a/.eslintrc.json b/.eslintrc.json index 72ce2be8..3c0701fc 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,12 +1,10 @@ { "extends": "standard", - "env": { "node": true, "es6": true, "jest": true }, - "globals": { "document": false, "navigator": false, @@ -16,5 +14,8 @@ "ROOTPATH": true, "SERVERPATH": true, "IS_DEBUG": true + }, + "rules": { + "space-before-function-paren": 0 } } diff --git a/client/js/app.js b/client/js/app.js index ab93d206..246c890d 100644 --- a/client/js/app.js +++ b/client/js/app.js @@ -1,14 +1,64 @@ 'use strict' -/* global alertsData */ +/* global alertsData, siteLang */ +/* eslint-disable no-new */ import $ from 'jquery' import _ from 'lodash' +import Vue from 'vue' +import Vuex from 'vuex' 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 'jquery-smooth-scroll' 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 @@ -27,28 +77,47 @@ $(() => { // ==================================== $(window).bind('beforeunload', () => { - $('#notifload').addClass('active') + store.commit('startLoading') }) $(document).ajaxSend(() => { - $('#notifload').addClass('active') + store.commit('startLoading') }).ajaxComplete(() => { - $('#notifload').removeClass('active') + store.commit('stopLoading') }) - var alerts = new Alerts() + var alerts = {} + /*var alerts = new Alerts() if (alertsData) { _.forEach(alertsData, (alertRow) => { alerts.push(alertRow) }) - } + }*/ // ==================================== // 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 diff --git a/client/js/components/anchor.vue b/client/js/components/anchor.vue new file mode 100644 index 00000000..72250aad --- /dev/null +++ b/client/js/components/anchor.vue @@ -0,0 +1,17 @@ + + + diff --git a/client/js/components/color-picker.vue b/client/js/components/color-picker.vue new file mode 100644 index 00000000..5e41a7f9 --- /dev/null +++ b/client/js/components/color-picker.vue @@ -0,0 +1,15 @@ + + + diff --git a/client/js/components/copy-path.vue b/client/js/components/copy-path.vue deleted file mode 100644 index 86567a54..00000000 --- a/client/js/components/copy-path.vue +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/client/js/components/loading-spinner.vue b/client/js/components/loading-spinner.vue new file mode 100644 index 00000000..d93d317f --- /dev/null +++ b/client/js/components/loading-spinner.vue @@ -0,0 +1,12 @@ + + + diff --git a/client/js/components/search.js b/client/js/components/search.js deleted file mode 100644 index 1c6177a2..00000000 --- a/client/js/components/search.js +++ /dev/null @@ -1,87 +0,0 @@ -'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) - } -} diff --git a/client/js/components/search.vue b/client/js/components/search.vue new file mode 100644 index 00000000..1935512b --- /dev/null +++ b/client/js/components/search.vue @@ -0,0 +1,101 @@ + + + diff --git a/client/js/modals/create.vue b/client/js/modals/create.vue new file mode 100644 index 00000000..5ae92a05 --- /dev/null +++ b/client/js/modals/create.vue @@ -0,0 +1,54 @@ + + + diff --git a/client/js/pages/admin-profile.component.js b/client/js/pages/admin-profile.component.js new file mode 100644 index 00000000..4c4486b4 --- /dev/null +++ b/client/js/pages/admin-profile.component.js @@ -0,0 +1,30 @@ +'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) + }) + } + } +} diff --git a/client/js/pages/admin-settings.component.js b/client/js/pages/admin-settings.component.js new file mode 100644 index 00000000..f21b3eb2 --- /dev/null +++ b/client/js/pages/admin-settings.component.js @@ -0,0 +1,52 @@ +'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!') + } + } +} diff --git a/client/js/pages/admin.js b/client/js/pages/admin.js index 08d0fa95..030d7c96 100644 --- a/client/js/pages/admin.js +++ b/client/js/pages/admin.js @@ -1,41 +1,13 @@ 'use strict' -/* global usrData, usrDataName */ +/* global usrData */ import $ from 'jquery' import _ from 'lodash' import Vue from 'vue' module.exports = (alerts) => { - if ($('#page-type-admin-profile').length) { - let vueProfile = new Vue({ - el: '#page-type-admin-profile', - data: { - password: '********', - passwordVerify: '********', - name: '' - }, - methods: { - saveUser: (ev) => { - if (vueProfile.password !== vueProfile.passwordVerify) { - alerts.pushError('Error', "Passwords don't match!") - return - } - $.post(window.location.href, { - password: vueProfile.password, - name: vueProfile.name - }).done((resp) => { - alerts.pushSuccess('Saved successfully', 'Changes have been applied.') - }).fail((jqXHR, txtStatus, resp) => { - alerts.pushError('Error', resp) - }) - } - }, - created: function () { - this.name = usrDataName - } - }) - } else if ($('#page-type-admin-users').length) { + if ($('#page-type-admin-users').length) { require('../modals/admin-users-create.js')(alerts) } else if ($('#page-type-admin-users-edit').length) { let vueEditUser = new Vue({ @@ -98,52 +70,5 @@ module.exports = (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!') - } - } - }) } } diff --git a/client/js/pages/view.js b/client/js/pages/view.js index bde05222..0a6f4922 100644 --- a/client/js/pages/view.js +++ b/client/js/pages/view.js @@ -4,7 +4,7 @@ import $ from 'jquery' import MathJax from 'mathjax' -import * as CopyPath from '../components/copy-path.vue' +// import * as CopyPath from '../components/copy-path.vue' import Vue from 'vue' module.exports = (alerts) => { @@ -13,10 +13,10 @@ module.exports = (alerts) => { // Copy Path - new Vue({ - el: '.modal-copypath', - render: h => h(CopyPath) - }) + // new Vue({ + // el: '.modal-copypath', + // render: h => h(CopyPath) + // }) // MathJax Render diff --git a/client/scss/components/search.scss b/client/scss/components/search.scss index eafbef6d..fe30714e 100644 --- a/client/scss/components/search.scss +++ b/client/scss/components/search.scss @@ -1,47 +1,56 @@ .searchresults { position: fixed; - top: 45px; + top: 50px; left: 0; right: 0; margin: 0 auto; width: 500px; z-index: 1; - background-color: mc($primary, '700'); - border-bottom: 5px solid mc($primary, '800'); - box-shadow: 0 0 5px mc($primary, '500'); + background-color: darken(mc('blue-grey', '900'), 2%); + border: 1px solid mc('blue-grey', '900'); + box-shadow: 0 0 5px mc('blue-grey', '500'); color: #FFF; + transition: max-height 1s ease; - &.slideInDown { - @include prefix(animation-duration, .6s); - } + &-enter-active, &-leave-active { + overflow: hidden; + } + &-enter-to, &-leave { + max-height: 500px; + } + &-enter, &-leave-to { + max-height: 0px; + } .searchresults-label { - color: mc($primary, '200'); - padding: 15px 10px 10px; + background-color: mc('blue-grey', '800'); + color: mc('blue-grey', '300'); + padding: 8px; font-size: 13px; - text-transform: uppercase; - border-bottom: 1px dotted mc($primary, '400'); + letter-spacing: 1px; + text-transform: uppercase; + box-shadow: 0 0 5px rgba(0,0,0,0.3); } .searchresults-list { + padding-bottom: 5px; > li { display: flex; font-size: 14px; - transition: background-color .3s linear; + transition: background-color .2s linear; &:nth-child(odd) { - background-color: mc($primary, '600'); + background-color: mc('blue-grey', '900'); } &.is-active, &:hover { - background-color: mc($primary, '400'); + background-color: mc('blue-grey', '600'); color: #FFF; - border-left: 5px solid mc($primary, '200'); } a { - color: mc($primary, '50'); + color: mc('blue-grey', '50'); display: flex; align-items: center; height: 30px; diff --git a/client/scss/layout/_header.scss b/client/scss/layout/_header.scss index 83eab039..0a014dd5 100644 --- a/client/scss/layout/_header.scss +++ b/client/scss/layout/_header.scss @@ -24,7 +24,7 @@ #notifload { @include spinner(mc('indigo', '100'),0.5s,24px); } - &.active { + &.is-active { opacity: 1; } @@ -33,4 +33,4 @@ #notifload { #search-input { max-width: 300px; width: 33vw; -} \ No newline at end of file +} diff --git a/fuse.js b/fuse.js index 956d2e51..88a77b50 100644 --- a/fuse.js +++ b/fuse.js @@ -88,43 +88,66 @@ let globalTasks = Promise.mapSeries([ if (err.code === 'ENOENT') { console.info(colors.white(' └── ') + colors.green('Copy MathJax dependencies to assets...')) return fs.ensureDirAsync('./assets/js/mathjax').then(() => { - return fs.copyAsync('./node_modules/mathjax', './assets/js/mathjax', { filter: (src, dest) => { - let srcNormalized = src.replace(/\\/g, '/') - let shouldCopy = false - console.log(srcNormalized) - _.forEach([ - '/node_modules/mathjax', - '/node_modules/mathjax/jax', - '/node_modules/mathjax/jax/input', - '/node_modules/mathjax/jax/output' - ], chk => { - if (srcNormalized.endsWith(chk)) { - shouldCopy = true + return fs.copyAsync('./node_modules/mathjax', './assets/js/mathjax', { + filter: (src, dest) => { + let srcNormalized = src.replace(/\\/g, '/') + let shouldCopy = false + console.info(colors.white(' ' + srcNormalized)) + _.forEach([ + '/node_modules/mathjax', + '/node_modules/mathjax/jax', + '/node_modules/mathjax/jax/input', + '/node_modules/mathjax/jax/output' + ], chk => { + 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 } - }) - _.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 } - return shouldCopy - }}) + }) }) } else { throw err } }) }, + /** + * 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 */ @@ -144,6 +167,7 @@ let globalTasks = Promise.mapSeries([ * Delete Fusebox cache */ () => { + console.info(colors.white(' └── ') + colors.green('Clearing fuse-box cache...')) return fs.emptyDirAsync('./.fusebox') } ], f => { return f() }) @@ -156,7 +180,7 @@ const ALIASES = { 'brace-ext-modelist': 'brace/ext/modelist.js', 'simplemde': 'simplemde/dist/simplemde.min.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 = { _preinit: { @@ -182,7 +206,7 @@ globalTasks.then(() => { plugins: [ fsbx.EnvPlugin({ NODE_ENV: (dev) ? 'development' : 'production' }), 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.JSONPlugin(), !dev && fsbx.UglifyJSPlugin({ diff --git a/package.json b/package.json index 8b328ec4..17503c36 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "fs-extra": "^3.0.1", "git-wrapper2-promise": "^0.2.9", "highlight.js": "^9.11.0", - "i18next": "^8.2.0", + "i18next": "^8.3.0", "i18next-express-middleware": "^1.0.5", "i18next-node-fs-backend": "^1.0.0", "image-size": "^0.5.4", @@ -78,7 +78,7 @@ "markdown-it": "^8.3.1", "markdown-it-abbr": "^1.0.4", "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-expand-tabs": "^1.0.12", "markdown-it-external-links": "0.0.6", @@ -126,11 +126,13 @@ }, "devDependencies": { "@glimpse/glimpse": "^0.20.9", + "@panter/vue-i18next": "^0.4.1", "babel-cli": "latest", "babel-jest": "latest", "babel-preset-es2015": "latest", "brace": "^0.10.0", "colors": "^1.1.2", + "consolidate": "^0.14.5", "eslint": "latest", "eslint-config-standard": "latest", "eslint-plugin-import": "latest", @@ -138,6 +140,7 @@ "eslint-plugin-promise": "latest", "eslint-plugin-standard": "latest", "fuse-box": "^2.0.0", + "i18next-xhr-backend": "^1.4.1", "jest": "latest", "jquery": "^3.2.1", "jquery-contextmenu": "^2.4.5", @@ -155,7 +158,8 @@ "vee-validate": "^2.0.0-rc.3", "vue": "^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": { "collectCoverage": false, @@ -166,4 +170,4 @@ "verbose": true }, "snyk": true -} \ No newline at end of file +} diff --git a/server/controllers/admin.js b/server/controllers/admin.js index 4514257d..09295faf 100644 --- a/server/controllers/admin.js +++ b/server/controllers/admin.js @@ -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() }) +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 diff --git a/server/locales/en/admin.json b/server/locales/en/admin.json index 7a46d992..e62976fb 100644 --- a/server/locales/en/admin.json +++ b/server/locales/en/admin.json @@ -48,4 +48,4 @@ "edituser": "Edit User", "uniqueid": "Unique ID" } -} \ No newline at end of file +} diff --git a/server/locales/en/browser.json b/server/locales/en/browser.json new file mode 100644 index 00000000..9ef3b278 --- /dev/null +++ b/server/locales/en/browser.json @@ -0,0 +1,16 @@ +{ + "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...?" + } +} diff --git a/server/locales/en/common.json b/server/locales/en/common.json index 5461985c..40703a40 100644 --- a/server/locales/en/common.json +++ b/server/locales/en/common.json @@ -9,12 +9,6 @@ "home": "Home", "top": "Return to top" }, - "search": { - "placeholder": "Search...", - "results": "Search Results", - "nomatch": "No results matching your query", - "didyoumean": "Did you mean...?" - }, "sidebar": { "nav": "NAV", "navigation": "Navigation", @@ -24,9 +18,11 @@ "nav": { "home": "Home", "account": "Account", + "settings": "Settings", "myprofile": "My Profile", "stats": "Stats", "syssettings": "System Settings", + "theme": "Color Theme", "users": "Users", "logout": "Logout", "create": "Create", @@ -51,4 +47,4 @@ "source": "Loading source...", "editor": "Loading editor..." } -} \ No newline at end of file +} diff --git a/server/locales/fr/common.json b/server/locales/fr/common.json index f3a0a455..f8a809e3 100644 --- a/server/locales/fr/common.json +++ b/server/locales/fr/common.json @@ -24,9 +24,11 @@ "nav": { "home": "Accueil", "account": "Compte", + "settings": "Paramètres", "myprofile": "Mon Profil", "stats": "Statistiques", "syssettings": "Paramètres système", + "theme": "Thème de couleur", "users": "Utilisateurs", "logout": "Se Déconnecter", "create": "Créer", @@ -51,4 +53,4 @@ "source": "Chargement de la source...", "editor": "Chargement de l'éditeur" } -} \ No newline at end of file +} diff --git a/server/views/common/header.pug b/server/views/common/header.pug index 79451f64..cff22a80 100644 --- a/server/views/common/header.pug +++ b/server/views/common/header.pug @@ -9,26 +9,11 @@ = appconfig.title .nav-center block rootNavCenter - .nav-item - 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')) + search span.nav-toggle span span span .nav-right block rootNavRight - i.nav-item#notifload - - 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 }} + loading-spinner diff --git a/server/views/layout.pug b/server/views/layout.pug index c1e5a970..66bc8dcc 100644 --- a/server/views/layout.pug +++ b/server/views/layout.pug @@ -9,7 +9,7 @@ html meta(name='msapplication-TileImage', content='/favicons/ms-icon-144x144.png') title= appconfig.title - // Favicon + //- Favicon 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='icon', type='image/png', sizes='192x192', href='/favicons/android-icon-192x192.png') @@ -17,7 +17,10 @@ html link(rel='icon', type='image/png', sizes=favsize + 'x' + favsize, href='/favicons/favicon-' + favsize + 'x' + favsize + '.png') 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/app.min.js') @@ -26,7 +29,7 @@ html body #root.has-stickynav include ./common/header.pug - include ./common/alerts.pug + //-include ./common/alerts.pug main block content include ./common/footer.pug diff --git a/server/views/pages/admin/_layout.pug b/server/views/pages/admin/_layout.pug index dfcac89b..5fd87c55 100644 --- a/server/views/pages/admin/_layout.pug +++ b/server/views/pages/admin/_layout.pug @@ -4,7 +4,7 @@ block rootNavCenter h2.nav-item= t('nav.account') block rootNavRight - i.nav-item#notifload + loading-spinner .nav-item a.button.btn-edit-discard(href='/') i.icon-home @@ -48,6 +48,10 @@ block content a(href='/admin/settings') i.icon-cog span= t('nav.syssettings') + li + a(href='/admin/theme') + i.icon-drop + span= t('nav.theme') li a(href='/logout') i.icon-delete2 diff --git a/server/views/pages/admin/profile.pug b/server/views/pages/admin/profile.pug index b32a5154..0ba46dcc 100644 --- a/server/views/pages/admin/profile.pug +++ b/server/views/pages/admin/profile.pug @@ -1,53 +1,51 @@ extends ./_layout.pug block adminContent - #page-type-admin-profile - .hero - h1.title#title= t('nav.myprofile') - h2.subtitle= t('admin:profile.subtitle') - .form-sections - .columns.is-gapless - .column.is-two-thirds - section - 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' + .hero + h1.title#title= t('nav.myprofile') + h2.subtitle= t('admin:profile.subtitle') + .form-sections + .columns.is-gapless + .column.is-two-thirds + admin-profile(inline-template, email=user.email, name=user.name, provider=user.provider) + div section - label.label= t('admin:profile.password') + label.label= t('admin:profile.email') 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 - label.label= t('admin:profile.passwordverify') + label.label= t('admin:profile.displayname') p.control.is-fullwidth - input.input(type='password', placeholder=t('admin:profile.password'), value='********', v-model='passwordVerify') - section - label.label= t('admin:profile.displayname') - p.control.is-fullwidth - input.input(type='text', placeholder=t('admin:profile.displaynameexample'), v-model='name') - section - button.button.is-green(v-on:click='saveUser') - i.icon-check - span= t('admin:profile.savechanges') - .column - .panel-aside - label.label= t('admin:profile.provider') - p.control.account-profile-provider - case user.provider - when 'local': i.icon-server - when 'windowslive': i.icon-windows2.is-blue - when 'azure': i.icon-windows2.is-blue - when 'google': i.icon-google.is-blue - when 'facebook': i.icon-facebook.is-indigo - when 'github': i.icon-github.is-grey - when 'slack': i.icon-slack.is-purple - when 'ldap': i.icon-arrow-repeat-outline - default: i.icon-warning - = t('auth:providers.' + user.provider) - 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}"; + input.input(type='text', placeholder=t('admin:profile.displaynameexample'), v-model='name') + section + button.button.is-green(v-on:click='saveUser') + i.icon-check + span= t('admin:profile.savechanges') + .column + .panel-aside + label.label= t('admin:profile.provider') + p.control.account-profile-provider + case user.provider + when 'local': i.icon-server + when 'windowslive': i.icon-windows2.is-blue + when 'azure': i.icon-windows2.is-blue + when 'google': i.icon-google.is-blue + when 'facebook': i.icon-facebook.is-indigo + when 'github': i.icon-github.is-grey + when 'slack': i.icon-slack.is-purple + when 'ldap': i.icon-arrow-repeat-outline + default: i.icon-warning + = t('auth:providers.' + user.provider) + 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') diff --git a/server/views/pages/admin/settings.pug b/server/views/pages/admin/settings.pug index 29601be8..79d9926c 100644 --- a/server/views/pages/admin/settings.pug +++ b/server/views/pages/admin/settings.pug @@ -1,10 +1,10 @@ extends ./_layout.pug block adminContent - #page-type-admin-settings - .hero - h1.title#title= t('nav.syssettings') - h2.subtitle= t('admin:settings.subtitle') + .hero + h1.title#title= t('nav.syssettings') + h2.subtitle= t('admin:settings.subtitle') + admin-settings(inline-template) .form-sections section img(src='/images/logo.png', style={width:'200px', float:'right'}) @@ -34,4 +34,4 @@ block adminContent p.is-small= t('admin:settings.flushsessionstext') 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 diff --git a/server/views/pages/admin/theme.pug b/server/views/pages/admin/theme.pug new file mode 100644 index 00000000..301f9d8d --- /dev/null +++ b/server/views/pages/admin/theme.pug @@ -0,0 +1,11 @@ +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 diff --git a/server/views/pages/view.pug b/server/views/pages/view.pug index fb4a8e21..f4a63c77 100644 --- a/server/views/pages/view.pug +++ b/server/views/pages/view.pug @@ -57,8 +57,8 @@ block content if !isGuest li a(href='/admin') - i.icon-head - span= t('nav.account') + i.icon-cog + span= t('nav.settings') else li a(href='/login') diff --git a/yarn.lock b/yarn.lock index c0886a64..fc32004a 100644 Binary files a/yarn.lock and b/yarn.lock differ