diff --git a/client/components/common/nav-header.vue b/client/components/common/nav-header.vue index fd4ce781..7b16bec8 100644 --- a/client/components/common/nav-header.vue +++ b/client/components/common/nav-header.vue @@ -137,17 +137,17 @@ icon ) v-icon(color='grey') search - //- v-menu(offset-y, left, transition='slide-y-transition') - //- v-tooltip(bottom, slot='activator') - //- v-btn(icon, slot='activator') - //- v-icon(color='grey') language - //- span Language - //- v-list.py-0 - //- template(v-for='(lc, idx) of locales') - //- v-list-tile(@click='changeLocale(lc)') - //- v-list-tile-action: v-chip(:color='lc.code === $i18n.i18next.language ? `blue` : `grey`', small, label, dark) {{lc.code.toUpperCase()}} - //- v-list-tile-title {{lc.name}} - //- v-divider.my-0(v-if='idx < locales.length - 1') + v-menu(offset-y, left, transition='slide-y-transition', v-if='mode === `view` && locales.length > 0') + v-tooltip(bottom, slot='activator') + v-btn(icon, slot='activator') + v-icon(color='grey') language + span Language + v-list.py-0 + template(v-for='(lc, idx) of locales') + v-list-tile(@click='changeLocale(lc)') + v-list-tile-action: v-chip(:color='lc.code === $i18n.i18next.language ? `blue` : `grey`', small, label, dark) {{lc.code.toUpperCase()}} + v-list-tile-title {{lc.name}} + v-divider.my-0(v-if='idx < locales.length - 1') v-tooltip(bottom, v-if='isAuthenticated && isAdmin') v-btn.btn-animate-rotate(icon, href='/a', slot='activator') v-icon(color='grey') settings @@ -196,6 +196,8 @@ import { get, sync } from 'vuex-pathify' import _ from 'lodash' import Cookies from 'js-cookie' +/* global siteLangs */ + export default { components: { PageDelete: () => import('./page-delete.vue') @@ -217,11 +219,7 @@ export default { searchAdvMenuShown: false, newPageModal: false, deletePageModal: false, - locales: [ - { code: 'en', name: 'English' }, - { code: 'fr', name: 'Français' }, - { code: 'es', name: 'Español' } - ] + locales: siteLangs } }, computed: { diff --git a/config.sample.yml b/config.sample.yml index 69509fe8..f22b6ad6 100644 --- a/config.sample.yml +++ b/config.sample.yml @@ -105,3 +105,11 @@ uploads: maxFileSize: 5242880 # Maximum file uploads per request (default: 20) maxFiles: 10 + +# --------------------------------------------------------------------- +# Offline Mode +# --------------------------------------------------------------------- +# If your server cannot access the internet. Set to true and manually +# download the offline files for sideloading. + +offline: false diff --git a/dev/templates/master.pug b/dev/templates/master.pug index 93caec4b..cebdf93f 100644 --- a/dev/templates/master.pug +++ b/dev/templates/master.pug @@ -29,7 +29,7 @@ html //- Site Properties script. - var siteConfig = !{JSON.stringify({ title: config.title, theme: config.theming.theme, darkMode: config.theming.darkMode, lang: config.lang.code, rtl: config.lang.rtl, company: config.company })} + var siteConfig = !{JSON.stringify(siteConfig)}; var siteLangs = !{JSON.stringify(langs)} //- CSS <% for (var index in htmlWebpackPlugin.files.css) { %> diff --git a/server/app/data.yml b/server/app/data.yml index c4c0ea38..ac717eeb 100644 --- a/server/app/data.yml +++ b/server/app/data.yml @@ -24,6 +24,7 @@ defaults: uploads: maxFileSize: 5242880 maxFiles: 10 + offline: false # DB defaults graphEndpoint: 'https://graph.requarks.io' lang: @@ -63,12 +64,15 @@ jobs: purgeUploads: onInit: true schedule: PT15M + offlineSkip: false syncGraphLocales: onInit: true schedule: P1D + offlineSkip: true syncGraphUpdates: onInit: true schedule: P1D + offlineSkip: true groups: defaultPermissions: - 'read:pages' diff --git a/server/core/kernel.js b/server/core/kernel.js index 0b1c4652..32fac63d 100644 --- a/server/core/kernel.js +++ b/server/core/kernel.js @@ -34,6 +34,7 @@ module.exports = { await this.initTelemetry() WIKI.cache = require('./cache').init() WIKI.scheduler = require('./scheduler').init() + WIKI.sideloader = require('./sideloader').init() WIKI.events = new EventEmitter() } catch (err) { WIKI.logger.error(err) diff --git a/server/core/scheduler.js b/server/core/scheduler.js index 54a51400..cb2dc847 100644 --- a/server/core/scheduler.js +++ b/server/core/scheduler.js @@ -87,7 +87,6 @@ class Job { } } - module.exports = { jobs: [], init() { @@ -95,6 +94,11 @@ module.exports = { }, start() { _.forOwn(WIKI.data.jobs, (queueParams, queueName) => { + if (WIKI.config.offline && queueParams.offlineSkip) { + WIKI.logger.warn(`Skipping job ${queueName} because offline mode is enabled. [SKIPPED]`) + return + } + const schedule = (configHelper.isValidDurationString(queueParams.schedule)) ? queueParams.schedule : _.get(WIKI.config, queueParams.schedule) this.registerJob({ name: _.kebabCase(queueName), diff --git a/server/core/sideloader.js b/server/core/sideloader.js new file mode 100644 index 00000000..8f0a847d --- /dev/null +++ b/server/core/sideloader.js @@ -0,0 +1,76 @@ +const fs = require('fs-extra') +const path = require('path') +const _ = require('lodash') + +/* global WIKI */ + +module.exports = { + async init () { + if (!WIKI.config.offline) { + return + } + + const sideloadExists = await fs.pathExists(path.join(WIKI.ROOTPATH, 'data/sideload')) + + if (!sideloadExists) { + return + } + + WIKI.logger.info('Sideload directory detected. Looking for packages...') + + try { + await this.importLocales() + } catch (err) { + WIKI.logger.warn(err) + } + }, + async importLocales() { + const localeExists = await fs.pathExists(path.join(WIKI.ROOTPATH, 'data/sideload/locales.json')) + if (localeExists) { + WIKI.logger.info('Found locales master file. Importing locale packages...') + let importedLocales = 0 + + const locales = await fs.readJson(path.join(WIKI.ROOTPATH, 'data/sideload/locales.json')) + if (locales && _.has(locales, 'data.localization.locales')) { + for (const locale of locales.data.localization.locales) { + try { + const localeData = await fs.readJson(path.join(WIKI.ROOTPATH, `data/sideload/${locale.code}.json`)) + if (localeData) { + WIKI.logger.info(`Importing ${locale.name} locale package...`) + + let lcObj = {} + _.forOwn(localeData, (value, key) => { + if (_.includes(key, '::')) { return } + if (_.isEmpty(value)) { value = key } + _.set(lcObj, key.replace(':', '.'), value) + }) + + const localeExists = await WIKI.models.locales.query().select('code').where('code', locale.code).first() + if (localeExists) { + await WIKI.models.locales.query().update({ + code: locale.code, + strings: lcObj, + isRTL: locale.isRTL, + name: locale.name, + nativeName: locale.nativeName + }).where('code', locale.code) + } else { + await WIKI.models.locales.query().insert({ + code: locale.code, + strings: lcObj, + isRTL: locale.isRTL, + name: locale.name, + nativeName: locale.nativeName + }) + } + importedLocales++ + } + } catch (err) { + // skip + } + } + WIKI.logger.info(`Imported ${importedLocales} locale packages: [COMPLETED]`) + } + } + } +} diff --git a/server/core/telemetry.js b/server/core/telemetry.js index 712fe604..8484fc7b 100644 --- a/server/core/telemetry.js +++ b/server/core/telemetry.js @@ -28,7 +28,7 @@ module.exports = { }) WIKI.telemetry = this - if (_.get(WIKI.config, 'telemetry.isEnabled', false) === true) { + if (_.get(WIKI.config, 'telemetry.isEnabled', false) === true && WIKI.config.offline !== true) { this.enabled = true this.sendOSInfo() } diff --git a/server/graph/resolvers/localization.js b/server/graph/resolvers/localization.js index 2a63fb58..1e093d10 100644 --- a/server/graph/resolvers/localization.js +++ b/server/graph/resolvers/localization.js @@ -66,6 +66,8 @@ module.exports = { await WIKI.lang.setCurrentLocale(args.locale) await WIKI.lang.refreshNamespaces() + await WIKI.cache.del('nav:locales') + return { responseResult: graphHelper.generateSuccess('Locale config updated') } diff --git a/server/master.js b/server/master.js index 3de1b225..103d30ab 100644 --- a/server/master.js +++ b/server/master.js @@ -105,9 +105,6 @@ module.exports = async () => { // ---------------------------------------- app.locals.basedir = WIKI.ROOTPATH - app.locals._ = require('lodash') - app.locals.moment = require('moment') - app.locals.moment.locale(WIKI.config.lang.code) app.locals.config = WIKI.config app.locals.pageMeta = { title: '', @@ -146,6 +143,19 @@ module.exports = async () => { // Routing // ---------------------------------------- + app.use(async (req, res, next) => { + res.locals.siteConfig = { + title: WIKI.config.title, + theme: WIKI.config.theming.theme, + darkMode: WIKI.config.theming.darkMode, + lang: WIKI.config.lang.code, + rtl: WIKI.config.lang.rtl, + company: WIKI.config.company + } + res.locals.langs = await WIKI.models.locales.getNavLocales({ cache: true }) + next() + }) + app.use('/', ctrl.auth) app.use('/', ctrl.upload) app.use('/', ctrl.common) diff --git a/server/models/locales.js b/server/models/locales.js index e4544dfd..66c3eb55 100644 --- a/server/models/locales.js +++ b/server/models/locales.js @@ -1,5 +1,7 @@ const Model = require('objection').Model +/* global WIKI */ + /** * Locales model */ @@ -34,4 +36,27 @@ module.exports = class Locale extends Model { this.createdAt = new Date().toISOString() this.updatedAt = new Date().toISOString() } + + static async getNavLocales({ cache = false } = {}) { + if (!WIKI.config.lang.namespacing) { + return [] + } + + if (cache) { + const navLocalesCached = await WIKI.cache.get('nav:locales') + if (navLocalesCached) { + return navLocalesCached + } + } + const navLocales = await WIKI.models.locales.query().select('code', 'nativeName AS name').whereIn('code', WIKI.config.lang.namespaces).orderBy('code') + if (navLocales) { + if (cache) { + await WIKI.cache.set('nav:locales', navLocales, 300) + } + return navLocales + } else { + WIKI.logger.warn('Site Locales for navigation are missing or corrupted.') + return [] + } + } } diff --git a/server/views/master.pug b/server/views/master.pug index 6d4cec41..fd8d6810 100644 --- a/server/views/master.pug +++ b/server/views/master.pug @@ -29,7 +29,7 @@ html //- Site Properties script. - var siteConfig = !{JSON.stringify({ title: config.title, theme: config.theming.theme, darkMode: config.theming.darkMode, lang: config.lang.code, rtl: config.lang.rtl, company: config.company })} + var siteConfig = !{JSON.stringify(siteConfig)}; var siteLangs = !{JSON.stringify(langs)} //- CSS