Commit b6f1e180 authored by Nick's avatar Nick

feat: locales availability + IE display mode

parent ca06a15b
...@@ -16,13 +16,66 @@ ...@@ -16,13 +16,66 @@
"@babel/plugin-proposal-function-sent", "@babel/plugin-proposal-function-sent",
"@babel/plugin-proposal-export-namespace-from", "@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-numeric-separator", "@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-throw-expressions" "@babel/plugin-proposal-throw-expressions",
[
"prismjs", {
"languages": [
"markup",
"css",
"clike",
"javascript",
"c",
"bash",
"basic",
"cpp",
"csharp",
"arduino",
"ruby",
"elixir",
"fsharp",
"go",
"graphql",
"handlebars",
"haskell",
"ini",
"java",
"json",
"kotlin",
"latex",
"less",
"makefile",
"markdown",
"matlab",
"nginx",
"objectivec",
"perl",
"php",
"powershell",
"pug",
"python",
"typescript",
"rust",
"scss",
"scala",
"smalltalk",
"sql",
"stylus",
"swift",
"vbnet",
"yaml"
],
"plugins": ["line-numbers"],
"theme": "dark",
"css": true
}
]
], ],
"presets": [ "presets": [
[ [
"@babel/preset-env", { "@babel/preset-env", {
"useBuiltIns": "entry", "useBuiltIns": "entry",
"corejs": 3 "corejs": 3,
"debug": true
} }
] ]
] ]
......
...@@ -19,6 +19,7 @@ npm-debug.log* ...@@ -19,6 +19,7 @@ npm-debug.log*
# Generated assets # Generated assets
/assets /assets
server/views/master.pug server/views/master.pug
server/views/legacy.pug
server/views/setup.pug server/views/setup.pug
# Webpack # Webpack
......
...@@ -52,8 +52,6 @@ ...@@ -52,8 +52,6 @@
v-toolbar(color='primary', dark, dense, flat) v-toolbar(color='primary', dark, dense, flat)
v-toolbar-title v-toolbar-title
.subheading {{ $t('admin:locale.namespacing') }} .subheading {{ $t('admin:locale.namespacing') }}
v-spacer
v-chip(label, color='white', small).primary--text coming soon
v-card-text v-card-text
v-switch( v-switch(
v-model='namespacing' v-model='namespacing'
...@@ -102,28 +100,35 @@ ...@@ -102,28 +100,35 @@
v-card.animated.fadeInUp.wait-p4s v-card.animated.fadeInUp.wait-p4s
v-toolbar(color='teal', dark, dense, flat) v-toolbar(color='teal', dark, dense, flat)
v-toolbar-title v-toolbar-title
.subheading {{ $t('admin:locale.download') }} .subheading {{ $t('admin:locale.downloadTitle') }}
v-list(two-line, dense) v-data-table(
template(v-for='(lc, idx) in locales') :headers='headers',
v-list-tile(:key='lc.code') :items='locales',
v-list-tile-avatar hide-actions,
v-avatar.teal.white--text(size='40') {{lc.code.toUpperCase()}} item-key='code',
v-list-tile-content :rows-per-page-items='[-1]'
v-list-tile-title(v-html='lc.name') )
v-list-tile-sub-title(v-html='lc.nativeName') template(v-slot:items='lc')
v-list-tile-action(v-if='lc.isRTL') td
v-chip(label, small, :class='$vuetify.dark ? `text--lighten-5` : `text--darken-2`').caption.grey--text RTL v-chip.white--text(label, color='teal', small) {{lc.item.code}}
v-list-tile-action(v-if='lc.isInstalled && lc.installDate < lc.updatedAt') td
v-btn(icon, @click='download(lc)') strong {{lc.item.name}}
v-icon.blue--text cached td
v-list-tile-action(v-else-if='lc.isInstalled') span {{ lc.item.nativeName }}
td.text-xs-center
v-icon(v-if='lc.item.isRTL') check
td
.d-flex.align-center.pl-4
.caption.mr-2(:class='lc.item.availability <= 33 ? `red--text` : (lc.item.availability <= 66) ? `orange--text` : `green--text`') {{lc.item.availability}}%
v-progress-circular(:value='lc.item.availability', width='2', size='20', :color='lc.item.availability <= 33 ? `red` : (lc.item.availability <= 66) ? `orange` : `green`')
td.text-xs-center
v-progress-circular(v-if='lc.item.isDownloading', indeterminate, color='blue', size='20', :width='2')
v-btn(v-else-if='lc.item.isInstalled && lc.item.installDate < lc.item.updatedAt', icon, @click='download(lc.item)')
v-icon.blue--text cached
v-btn(v-else-if='lc.item.isInstalled', icon, @click='download(lc.item)')
v-icon.green--text check v-icon.green--text check
v-list-tile-action(v-else-if='lc.isDownloading') v-btn(v-else, icon, @click='download(lc.item)')
v-progress-circular(indeterminate, color='blue', size='20', :width='3') v-icon.grey--text cloud_download
v-list-tile-action(v-else)
v-btn(icon, @click='download(lc)')
v-icon.grey--text cloud_download
v-divider.my-0(inset, v-if='idx < locales.length - 1')
v-card.wiki-form.mt-3.animated.fadeInUp.wait-p5s v-card.wiki-form.mt-3.animated.fadeInUp.wait-p5s
v-toolbar(color='teal', dark, dense, flat) v-toolbar(color='teal', dark, dense, flat)
v-toolbar-title v-toolbar-title
...@@ -158,6 +163,46 @@ export default { ...@@ -158,6 +163,46 @@ export default {
computed: { computed: {
installedLocales() { installedLocales() {
return _.filter(this.locales, ['isInstalled', true]) return _.filter(this.locales, ['isInstalled', true])
},
headers() {
return [
{
text: this.$t('admin:locale.code'),
align: 'left',
value: 'code',
width: 10
},
{
text: this.$t('admin:locale.name'),
align: 'left',
value: 'name'
},
{
text: this.$t('admin:locale.nativeName'),
align: 'left',
value: 'nativeName'
},
{
text: this.$t('admin:locale.rtl'),
align: 'center',
value: 'isRTL',
sortable: false,
width: 10
},
{
text: this.$t('admin:locale.availability'),
align: 'center',
value: 'availability',
width: 100
},
{
text: this.$t('admin:locale.download'),
align: 'center',
value: 'code',
sortable: false,
width: 100
}
]
} }
}, },
methods: { methods: {
...@@ -173,6 +218,8 @@ export default { ...@@ -173,6 +218,8 @@ export default {
if (resp.succeeded) { if (resp.succeeded) {
lc.isDownloading = false lc.isDownloading = false
lc.isInstalled = true lc.isInstalled = true
lc.updatedAt = new Date().toISOString()
lc.installDate = lc.updatedAt
this.$store.commit('showNotification', { this.$store.commit('showNotification', {
message: `Locale ${lc.name} has been installed successfully.`, message: `Locale ${lc.name} has been installed successfully.`,
style: 'success', style: 'success',
......
...@@ -3,14 +3,14 @@ ...@@ -3,14 +3,14 @@
v-toolbar(flat, color='primary', dark, dense) v-toolbar(flat, color='primary', dark, dense)
.subheading {{ $t('admin:utilities.authTitle') }} .subheading {{ $t('admin:utilities.authTitle') }}
v-card-text v-card-text
v-subheader.pl-0 Generate New Authentication Public / Private Key Certificates v-subheader.pl-0.primary--text Generate New Authentication Public / Private Key Certificates
.body-1 This will invalidate all current session tokens and cause all users to be logged out. .body-1 This will invalidate all current session tokens and cause all users to be logged out.
.body-1.red--text You will need to log back in after the operation. .body-1.red--text You will need to log back in after the operation.
v-btn(outline, color='primary', @click='regenCerts', :disabled='loading').ml-0.mt-3 v-btn(outline, color='primary', @click='regenCerts', :disabled='loading').ml-0.mt-3
v-icon(left) build v-icon(left) build
span Proceed span Proceed
v-divider.my-3 v-divider.my-3
v-subheader.pl-0 Reset Guest User v-subheader.pl-0.primary--text Reset Guest User
.body-1 This will reset the guest user to its default parameters and permissions. .body-1 This will reset the guest user to its default parameters and permissions.
v-btn(outline, color='primary', @click='resetGuest', :disabled='loading').ml-0.mt-3 v-btn(outline, color='primary', @click='resetGuest', :disabled='loading').ml-0.mt-3
v-icon(left) build v-icon(left) build
......
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
v-toolbar(flat, color='primary', dark, dense) v-toolbar(flat, color='primary', dark, dense)
.subheading {{ $t('admin:utilities.cacheTitle') }} .subheading {{ $t('admin:utilities.cacheTitle') }}
v-card-text v-card-text
v-subheader.pl-0 Flush Pages and Assets Cache v-subheader.pl-0.primary--text Flush Pages and Assets Cache
.body-1 Pages and Assets are cached to disk for better performance. You can flush the cache to force all content to be fetched from the DB again. .body-1 Pages and Assets are cached to disk for better performance. You can flush the cache to force all content to be fetched from the DB again.
v-btn(outline, color='primary', @click='flushCache', :disabled='loading').ml-0.mt-3 v-btn(outline, color='primary', @click='flushCache', :disabled='loading').ml-0.mt-3
v-icon(left) build v-icon(left) build
span Proceed span Proceed
v-divider.my-3 v-divider.my-3
v-subheader.pl-0 Flush Temporary Uploads v-subheader.pl-0.primary--text Flush Temporary Uploads
.body-1 New uploads are temporarily saved to disk while they are being processed. They are automatically deleted after processing, but you can force an immediate cleanup using this tool. .body-1 New uploads are temporarily saved to disk while they are being processed. They are automatically deleted after processing, but you can force an immediate cleanup using this tool.
.body-1.red--text Note that performing this action while an upload is in progress can result in a failed upload. .body-1.red--text Note that performing this action while an upload is in progress can result in a failed upload.
v-btn(outline, color='primary', @click='flushUploads', :disabled='loading').ml-0.mt-3 v-btn(outline, color='primary', @click='flushUploads', :disabled='loading').ml-0.mt-3
......
<template lang='pug'>
v-card
v-toolbar(flat, color='primary', dark, dense)
.subheading {{ $t('admin:utilities.contentTitle') }}
v-card-text
v-subheader.pl-0.primary--text Migrate all pages to base language
.body-1 If you created content before selecting a different locale and activating the namespacing capabilities, you may want to transfer all content to the base locale.
.body-1.red--text: strong This operation is destructive and cannot be reversed! Make sure you have proper backups!
.body-1.mt-3 Based on your current configuration, all pages will be migrated to the locale #[v-chip(label, small): strong {{currentLocale.toUpperCase()}}]
.body-1.mt-3 Pages that are already in the target locale will not be touched. If a page already exists at the target, the source page will not be modified as it would create a conflict. If you want to overwrite the target content, you must first delete that page.
v-btn(outline, color='primary', @click='migrateToLocale', :disabled='loading').ml-0.mt-3
v-icon(left) build
span Proceed
</template>
<script>
import _ from 'lodash'
import utilityContentMigrateLocaleMutation from 'gql/admin/utilities/utilities-mutation-content-migratelocale.gql'
/* global siteLang */
export default {
data: () => {
return {
loading: false
}
},
computed: {
currentLocale() {
return siteConfig.lang
}
},
methods: {
async migrateToLocale() {
this.loading = true
this.$store.commit(`loadingStart`, 'admin-utilities-content-migratelocale')
try {
const respRaw = await this.$apollo.mutate({
mutation: utilityContentMigrateLocaleMutation,
variables: {
targetLocale: siteConfig.lang
}
})
const resp = _.get(respRaw, 'data.pages.migrateToLocale.responseResult', {})
if (resp.succeeded) {
this.$store.commit('showNotification', {
message: 'Migrated all content to target locale successfully.',
style: 'success',
icon: 'check'
})
} else {
throw new Error(resp.message)
}
} catch (err) {
this.$store.commit('pushGraphError', err)
}
this.$store.commit(`loadingStop`, 'admin-utilities-content-migratelocale')
this.loading = false
}
}
}
</script>
<style lang='scss'>
</style>
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
export default { export default {
components: { components: {
UtilityAuth: () => import(/* webpackChunkName: "admin" */ './admin-utilities-auth.vue'), UtilityAuth: () => import(/* webpackChunkName: "admin" */ './admin-utilities-auth.vue'),
UtilityContent: () => import(/* webpackChunkName: "admin" */ './admin-utilities-content.vue'),
UtilityCache: () => import(/* webpackChunkName: "admin" */ './admin-utilities-cache.vue'), UtilityCache: () => import(/* webpackChunkName: "admin" */ './admin-utilities-cache.vue'),
UtilityImportv1: () => import(/* webpackChunkName: "admin" */ './admin-utilities-importv1.vue'), UtilityImportv1: () => import(/* webpackChunkName: "admin" */ './admin-utilities-importv1.vue'),
UtilityTelemetry: () => import(/* webpackChunkName: "admin" */ './admin-utilities-telemetry.vue') UtilityTelemetry: () => import(/* webpackChunkName: "admin" */ './admin-utilities-telemetry.vue')
...@@ -50,6 +51,12 @@ export default { ...@@ -50,6 +51,12 @@ export default {
isAvailable: true isAvailable: true
}, },
{ {
key: 'UtilityContent',
icon: 'insert_drive_file',
i18nKey: 'content',
isAvailable: true
},
{
key: 'UtilityCache', key: 'UtilityCache',
icon: 'invert_colors', icon: 'invert_colors',
i18nKey: 'cache', i18nKey: 'cache',
......
...@@ -91,7 +91,7 @@ export default { ...@@ -91,7 +91,7 @@ export default {
return this.response.suggestions ? this.response.suggestions : [] return this.response.suggestions ? this.response.suggestions : []
}, },
paginationLength() { paginationLength() {
return this.response.totalHits > 0 ? 0 : Math.ceil(this.response.totalHits / 10) return (this.response.totalHits > 0) ? 0 : Math.ceil(this.response.totalHits / 10)
} }
}, },
watch: { watch: {
...@@ -107,7 +107,7 @@ export default { ...@@ -107,7 +107,7 @@ export default {
}, },
mounted() { mounted() {
this.$root.$on('searchMove', (dir) => { this.$root.$on('searchMove', (dir) => {
this.cursor += (dir === 'up' ? -1 : 1) this.cursor += ((dir === 'up') ? -1 : 1)
if (this.cursor < -1) { if (this.cursor < -1) {
this.cursor = -1 this.cursor = -1
} else if (this.cursor > this.results.length + this.suggestions.length - 1) { } else if (this.cursor > this.results.length + this.suggestions.length - 1) {
......
...@@ -190,7 +190,7 @@ import mdMark from 'markdown-it-mark' ...@@ -190,7 +190,7 @@ import mdMark from 'markdown-it-mark'
import mdImsize from 'markdown-it-imsize' import mdImsize from 'markdown-it-imsize'
// Prism (Syntax Highlighting) // Prism (Syntax Highlighting)
import Prism from '@/libs/prism/prism.js' import Prism from 'prismjs'
// ======================================== // ========================================
// INIT // INIT
......
{ {
localization { localization {
locales { locales {
availability
code code
createdAt createdAt
isInstalled isInstalled
......
mutation {
pages {
migrateToLocale {
responseResult {
succeeded
errorCode
slug
message
}
}
}
}
require('./scss/legacy.scss')
window.WIKI = null
require('@babel/polyfill') require('core-js/stable')
require('regenerator-runtime/runtime')
require('vuetify/src/stylus/main.styl') require('vuetify/src/stylus/main.styl')
require('./scss/app.scss') require('./scss/app.scss')
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
@import 'layout/md2'; @import 'layout/md2';
// @import '../libs/twemoji/twemoji-awesome'; // @import '../libs/twemoji/twemoji-awesome';
@import '../libs/prism/prism.css'; // @import '../libs/prism/prism.css';
@import '~vue-tour/dist/vue-tour.css'; @import '~vue-tour/dist/vue-tour.css';
@import '~vue-status-indicator/styles.css'; @import '~vue-status-indicator/styles.css';
@import '~xterm/dist/xterm.css'; @import '~xterm/dist/xterm.css';
......
@import "global";
@import "./base/fonts.scss";
@import "./base/icons.scss";
html {
box-sizing: border-box;
background-color: mc('grey', '50');
font-size: 14px;
}
*, *:before, *:after {
box-sizing: inherit;
}
* {
margin: 0;
padding: 0;
}
.is-hidden {
display: none;
}
body {
margin: 0;
padding: 0;
font-family: "Roboto",sans-serif;
line-height: 1.5;
min-height: 100vh;
}
.header {
background-color: #000;
color: #FFF;
height: 64px;
padding: 0 16px;
display: flex;
justify-content: space-between;
align-items: center;
&-title {
margin: 0;
font-size: 16px;
font-weight: 500;
letter-spacing: .02em;
}
&-deprecated {
color: mc('red', '100');
a {
color: mc('pink', '400');
}
}
&-login {
a {
text-decoration: none;
color: #FFF;
transition: color .3s ease;
&:hover {
color: mc('blue', '500');
}
}
}
}
.main {
display: flex;
align-items: stretch;
min-height: calc(100vh - 64px);
height: 50vh;
&-container {
flex-grow: 1;
}
}
.sidebar {
width: 300px;
background-color: mc('blue', '700');
color: #FFF;
padding: 8px 0;
.sidebar-link {
height: 40px;
font-size: 13px;
display: flex;
align-items: center;
padding: 0 16px;
transition: background .3s cubic-bezier(.25,.8,.5,1);
font-weight: 400;
color: #FFF;
text-decoration: none;
&:hover {
background: hsla(0,0%,100%,.08);
}
}
i.material-icons {
width: 56px;
padding-left: 8px;
}
.sidebar-divider {
border-top: 1px solid hsla(0,0%,100%,.12);
margin: 8px 0;
}
.sidebar-title {
font-size: 13px;
height: 40px;
display: flex;
align-items: center;
padding: 0 16px 0 24px;
font-weight: 500;
color: hsla(0,0%,100%,.7);
}
}
.page-header {
background-color: mc('grey', '100');
padding: 0 24px;
height: 90px;
display: flex;
align-items: center;
border-bottom: 1px solid mc('grey', '200');
h1 {
font-size: 24px;
font-weight: 400;
line-height: 32px;
color: mc('grey', '800');
}
h2 {
color: mc('grey', '600');
font-size: 12px;
font-weight: 400;
}
&-left {
flex-grow: 1;
}
&-right {
flex: 0 0 324px;
padding-left: 16px;
&-title {
color: mc('grey', '500');
font-size: 12px;
}
&-author {
color: mc('grey', '800');
font-weight: 500;
}
&-updated {
color: mc('grey', '600');
font-size: 12px;
}
}
}
.page-contents {
display: flex;
}
.toc {
flex: 0 0 348px;
background-color: mc('grey', '200');
padding: 4px 0;
&-title {
font-size: 13px;
height: 40px;
display: flex;
color: mc('blue', '600');
align-items: center;
font-weight: 500;
padding: 0 16px;
}
&-tile {
text-decoration: none;
height: 40px;
display: flex;
font-size: 13px;
align-items: center;
padding: 0 16px;
color: mc('grey', '800');
transition: background-color .3s ease;
&.inset {
padding-left: 32px;
}
&:hover {
background-color: rgba(0,0,0,.06);
}
}
&-divider {
border-top: 1px solid rgba(0,0,0,.12);
margin: 0 0 0 24px;
&.inset {
margin-left: 40px;
}
}
}
@import "../themes/default/scss/app.scss";
.contents {
flex-grow: 1;
}
...@@ -137,7 +137,7 @@ ...@@ -137,7 +137,7 @@
<script> <script>
import { StatusIndicator } from 'vue-status-indicator' import { StatusIndicator } from 'vue-status-indicator'
import Prism from '@/libs/prism/prism.js' import Prism from 'prismjs'
import { get } from 'vuex-pathify' import { get } from 'vuex-pathify'
import _ from 'lodash' import _ from 'lodash'
......
doctype html
html
head
meta(http-equiv='X-UA-Compatible', content='IE=edge')
meta(charset='UTF-8')
meta(name='viewport', content='user-scalable=yes, width=device-width, initial-scale=1, maximum-scale=5')
meta(name='theme-color', content='#333333')
meta(name='msapplication-TileColor', content='#333333')
meta(name='msapplication-TileImage', content='/favicons/ms-icon-144x144.png')
title= pageMeta.title + ' | ' + config.title
//- SEO / OpenGraph
meta(name='description', content=pageMeta.description)
meta(property='og:title', content=pageMeta.title)
meta(property='og:type', content='website')
meta(property='og:description', content=pageMeta.description)
meta(property='og:image', content=pageMeta.image)
meta(property='og:url', content=pageMeta.url)
meta(property='og:site_name', content=config.title)
//- 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')
each favsize in [32, 96, 16]
link(rel='icon', type='image/png', sizes=favsize + 'x' + favsize, href='/favicons/favicon-' + favsize + 'x' + favsize + '.png')
link(rel='manifest', href='/manifest.json')
//- CSS
<% for (var index in htmlWebpackPlugin.files.css) { %>
<% if (htmlWebpackPlugin.files.cssIntegrity) { %>
link(
type='text/css'
rel='stylesheet'
href='<%= htmlWebpackPlugin.files.css[index] %>'
integrity='<%= htmlWebpackPlugin.files.cssIntegrity[index] %>'
crossorigin='<%= webpackConfig.output.crossOriginLoading %>'
)
<% } else { %>
link(
type='text/css'
rel='stylesheet'
href='<%= htmlWebpackPlugin.files.css[index] %>'
)
<% } %>
<% } %>
script(
crossorigin='anonymous'
src='https://polyfill.io/v3/polyfill.min.js?features=EventSource'
)
//- JS
<% for (var index in htmlWebpackPlugin.files.js) { %>
<% if (htmlWebpackPlugin.files.cssIntegrity) { %>
script(
type='text/javascript'
src='<%= htmlWebpackPlugin.files.js[index] %>'
integrity='<%= htmlWebpackPlugin.files.jsIntegrity[index] %>'
crossorigin='<%= webpackConfig.output.crossOriginLoading %>'
)
<% } else { %>
script(
type='text/javascript'
src='<%= htmlWebpackPlugin.files.js[index] %>'
)
<% } %>
<% } %>
!= analyticsCode.head
if injectCode.css
style(type='text/css')!= injectCode.css
if injectCode.head
!= injectCode.head
body
!= analyticsCode.bodyStart
#root
.header
span.header-title= siteConfig.title
span.header-deprecated Your browser is outdated. Upgrade to a #[a(href='https://bestvpn.org/outdatedbrowser/en', rel='nofollow') modern browser].
span.header-login
a(href='/login')
i.material-icons account_circle
.main
.sidebar
each navItem in sidebar
if navItem.kind === 'link'
a.sidebar-link(href=navItem.target)
i.material-icons= navItem.icon
span= navItem.label
else if navItem.kind === 'divider'
.sidebar-divider
else if navItem.kind === 'header'
.sidebar-title= navItem.label
.main-container
.page-header
.page-header-left
h1= page.title
h2= page.description
.page-header-right
.page-header-right-title Last edited by
.page-header-right-author= page.authorName
.page-header-right-updated= page.updatedAt
.page-contents
.contents
div!= page.render
if page.toc.length
.toc
.toc-title Table of Contents
each tocItem, tocIdx in page.toc
a.toc-tile(href='#' + tocItem.anchor)
i.material-icons arrow_right
span= tocItem.title
if tocIdx < page.toc.length - 1 || tocItem.children.length
.toc-divider
each tocSubItem in tocItem.children
a.toc-tile.inset(href='#' + tocSubItem.anchor)
i.material-icons arrow_right
span= tocSubItem.title
if tocIdx < page.toc.length - 1
.toc-divider.inset
if injectCode.body
!= injectCode.body
!= analyticsCode.bodyEnd
...@@ -24,6 +24,7 @@ module.exports = { ...@@ -24,6 +24,7 @@ module.exports = {
mode: 'development', mode: 'development',
entry: { entry: {
app: ['./client/index-app.js', 'webpack-hot-middleware/client'], app: ['./client/index-app.js', 'webpack-hot-middleware/client'],
legacy: ['./client/index-legacy.js', 'webpack-hot-middleware/client'],
setup: ['./client/index-setup.js', 'webpack-hot-middleware/client'] setup: ['./client/index-setup.js', 'webpack-hot-middleware/client']
}, },
output: { output: {
...@@ -194,14 +195,21 @@ module.exports = { ...@@ -194,14 +195,21 @@ module.exports = {
filename: '../server/views/master.pug', filename: '../server/views/master.pug',
hash: false, hash: false,
inject: false, inject: false,
excludeChunks: ['setup'] excludeChunks: ['setup', 'legacy']
}),
new HtmlWebpackPlugin({
template: 'dev/templates/legacy.pug',
filename: '../server/views/legacy.pug',
hash: false,
inject: false,
excludeChunks: ['setup', 'app']
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: 'dev/templates/setup.pug', template: 'dev/templates/setup.pug',
filename: '../server/views/setup.pug', filename: '../server/views/setup.pug',
hash: false, hash: false,
inject: false, inject: false,
excludeChunks: ['app'] excludeChunks: ['app', 'legacy']
}), }),
new HtmlWebpackPugPlugin(), new HtmlWebpackPugPlugin(),
new SriWebpackPlugin({ new SriWebpackPlugin({
......
...@@ -27,6 +27,7 @@ module.exports = { ...@@ -27,6 +27,7 @@ module.exports = {
mode: 'production', mode: 'production',
entry: { entry: {
app: './client/index-app.js', app: './client/index-app.js',
legacy: './client/index-legacy.js',
setup: './client/index-setup.js' setup: './client/index-setup.js'
}, },
output: { output: {
...@@ -204,14 +205,21 @@ module.exports = { ...@@ -204,14 +205,21 @@ module.exports = {
filename: '../server/views/master.pug', filename: '../server/views/master.pug',
hash: false, hash: false,
inject: false, inject: false,
excludeChunks: ['setup'] excludeChunks: ['setup', 'legacy']
}),
new HtmlWebpackPlugin({
template: 'dev/templates/legacy.pug',
filename: '../server/views/legacy.pug',
hash: false,
inject: false,
excludeChunks: ['setup', 'app']
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: 'dev/templates/setup.pug', template: 'dev/templates/setup.pug',
filename: '../server/views/setup.pug', filename: '../server/views/setup.pug',
hash: false, hash: false,
inject: false, inject: false,
excludeChunks: ['app'] excludeChunks: ['app', 'legacy']
}), }),
new HtmlWebpackPugPlugin(), new HtmlWebpackPugPlugin(),
new ScriptExtHtmlWebpackPlugin({ new ScriptExtHtmlWebpackPlugin({
......
...@@ -180,6 +180,7 @@ ...@@ -180,6 +180,7 @@
"@babel/polyfill": "^7.4.4", "@babel/polyfill": "^7.4.4",
"@babel/preset-env": "^7.4.5", "@babel/preset-env": "^7.4.5",
"@panter/vue-i18next": "0.15.1", "@panter/vue-i18next": "0.15.1",
"@vue/babel-preset-app": "3.9.2",
"animate-sass": "0.8.2", "animate-sass": "0.8.2",
"animated-number-vue": "0.1.5", "animated-number-vue": "0.1.5",
"apollo-cache-inmemory": "1.6.2", "apollo-cache-inmemory": "1.6.2",
...@@ -197,6 +198,7 @@ ...@@ -197,6 +198,7 @@
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",
"babel-plugin-graphql-tag": "2.4.0", "babel-plugin-graphql-tag": "2.4.0",
"babel-plugin-lodash": "3.3.4", "babel-plugin-lodash": "3.3.4",
"babel-plugin-prismjs": "1.0.2",
"babel-plugin-transform-imports": "1.5.1", "babel-plugin-transform-imports": "1.5.1",
"brace": "0.11.1", "brace": "0.11.1",
"cache-loader": "4.0.0", "cache-loader": "4.0.0",
...@@ -243,6 +245,7 @@ ...@@ -243,6 +245,7 @@
"postcss-loader": "3.0.0", "postcss-loader": "3.0.0",
"postcss-preset-env": "6.6.0", "postcss-preset-env": "6.6.0",
"postcss-selector-parser": "6.0.2", "postcss-selector-parser": "6.0.2",
"prismjs": "1.16.0",
"pug-lint": "2.5.0", "pug-lint": "2.5.0",
"pug-loader": "2.4.0", "pug-loader": "2.4.0",
"pug-plain-loader": "1.0.0", "pug-plain-loader": "1.0.0",
...@@ -299,9 +302,12 @@ ...@@ -299,9 +302,12 @@
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",
"last 2 versions", "last 2 major versions",
"Firefox ESR", "Firefox ESR",
"not ie < 11" "not ie > 0",
"not ie_mob > 0",
"not android > 0",
"not dead"
], ],
"postcss": { "postcss": {
"plugins": { "plugins": {
......
...@@ -195,7 +195,15 @@ router.get('/*', async (req, res, next) => { ...@@ -195,7 +195,15 @@ router.get('/*', async (req, res, next) => {
head: WIKI.config.theming.injectHead, head: WIKI.config.theming.injectHead,
body: WIKI.config.theming.injectBody body: WIKI.config.theming.injectBody
} }
res.render('page', { page, sidebar, injectCode })
if (req.query.legacy || req.get('user-agent').indexOf('Trident') >= 0) {
if (_.isString(page.toc)) {
page.toc = JSON.parse(page.toc)
}
res.render('legacy', { page, sidebar, injectCode })
} else {
res.render('page', { page, sidebar, injectCode })
}
} else if (pageArgs.path === 'home') { } else if (pageArgs.path === 'home') {
_.set(res.locals, 'pageMeta.title', 'Welcome') _.set(res.locals, 'pageMeta.title', 'Welcome')
res.render('welcome') res.render('welcome')
......
exports.up = knex => {
return knex.schema
.table('locales', table => {
table.integer('availability').notNullable().defaultTo(0)
})
}
exports.down = knex => {
return knex.schema
.table('locales', table => {
table.dropColumn('availability')
})
}
exports.up = knex => {
return knex.schema
.table('locales', table => {
table.integer('availability').notNullable().defaultTo(0)
})
}
exports.down = knex => {
return knex.schema
.table('locales', table => {
table.dropColumn('availability')
})
}
...@@ -13,7 +13,7 @@ module.exports = { ...@@ -13,7 +13,7 @@ module.exports = {
LocalizationQuery: { LocalizationQuery: {
async locales(obj, args, context, info) { async locales(obj, args, context, info) {
let remoteLocales = await WIKI.cache.get('locales') let remoteLocales = await WIKI.cache.get('locales')
let localLocales = await WIKI.models.locales.query().select('code', 'isRTL', 'name', 'nativeName', 'createdAt', 'updatedAt') let localLocales = await WIKI.models.locales.query().select('code', 'isRTL', 'name', 'nativeName', 'createdAt', 'updatedAt', 'availability')
remoteLocales = remoteLocales || localLocales remoteLocales = remoteLocales || localLocales
return _.map(remoteLocales, rl => { return _.map(remoteLocales, rl => {
let isInstalled = _.some(localLocales, ['code', rl.code]) let isInstalled = _.some(localLocales, ['code', rl.code])
......
...@@ -95,6 +95,16 @@ module.exports = { ...@@ -95,6 +95,16 @@ module.exports = {
} catch (err) { } catch (err) {
return graphHelper.generateError(err) return graphHelper.generateError(err)
} }
},
async migrateToLocale(obj, args, context) {
try {
return {
responseResult: graphHelper.generateSuccess('Migrated all content to target locale successfully.')
}
} catch (err) {
return graphHelper.generateError(err)
}
} }
}, },
Page: { Page: {
......
...@@ -42,6 +42,7 @@ type LocalizationMutation { ...@@ -42,6 +42,7 @@ type LocalizationMutation {
# ----------------------------------------------- # -----------------------------------------------
type LocalizationLocale { type LocalizationLocale {
availability: Int!
code: String! code: String!
createdAt: Date! createdAt: Date!
installDate: Date installDate: Date
......
...@@ -73,6 +73,8 @@ type PageMutation { ...@@ -73,6 +73,8 @@ type PageMutation {
): DefaultResponse @auth(requires: ["delete:pages", "manage:system"]) ): DefaultResponse @auth(requires: ["delete:pages", "manage:system"])
flushCache: DefaultResponse @auth(requires: ["manage:system"]) flushCache: DefaultResponse @auth(requires: ["manage:system"])
migrateToLocale: DefaultResponse @auth(requires: ["manage:system"])
} }
# ----------------------------------------------- # -----------------------------------------------
......
...@@ -28,21 +28,30 @@ module.exports = async (localeCode) => { ...@@ -28,21 +28,30 @@ module.exports = async (localeCode) => {
let lcObj = {} let lcObj = {}
_.forEach(strings, row => { _.forEach(strings, row => {
if (_.includes(row.key, '::')) { return } if (_.includes(row.key, '::')) { return }
if (_.isEmpty(row.value)) { row.value = row.key } if (_.isEmpty(row.value)) {
row.value = row.key
}
_.set(lcObj, row.key.replace(':', '.'), row.value) _.set(lcObj, row.key.replace(':', '.'), row.value)
}) })
const locales = await WIKI.cache.get('locales') const locales = await WIKI.cache.get('locales')
if (locales) { if (locales) {
const currentLocale = _.find(locales, ['code', localeCode]) || {} const currentLocale = _.find(locales, ['code', localeCode]) || {}
await WIKI.models.locales.query().delete().where('code', localeCode) const existingLocale = await WIKI.models.locales.query().where('code', localeCode)
await WIKI.models.locales.query().insert({ if (existingLocale) {
code: localeCode, await WIKI.models.locales.query().patch({
strings: lcObj, strings: lcObj
isRTL: currentLocale.isRTL, }).where('code', localeCode)
name: currentLocale.name, } else {
nativeName: currentLocale.nativeName await WIKI.models.locales.query().insert({
}) code: localeCode,
strings: lcObj,
isRTL: currentLocale.isRTL,
name: currentLocale.name,
nativeName: currentLocale.nativeName,
availability: currentLocale.availability
})
}
} else { } else {
throw new Error('Failed to fetch cached locales list! Restart server to resolve this issue.') throw new Error('Failed to fetch cached locales list! Restart server to resolve this issue.')
} }
......
...@@ -17,6 +17,7 @@ module.exports = async () => { ...@@ -17,6 +17,7 @@ module.exports = async () => {
query: `{ query: `{
localization { localization {
locales { locales {
availability
code code
name name
nativeName nativeName
...@@ -54,7 +55,9 @@ module.exports = async () => { ...@@ -54,7 +55,9 @@ module.exports = async () => {
let lcObj = {} let lcObj = {}
_.forEach(strings, row => { _.forEach(strings, row => {
if (_.includes(row.key, '::')) { return } if (_.includes(row.key, '::')) { return }
if (_.isEmpty(row.value)) { row.value = row.key } if (_.isEmpty(row.value)) {
row.value = row.key
}
_.set(lcObj, row.key.replace(':', '.'), row.value) _.set(lcObj, row.key.replace(':', '.'), row.value)
}) })
...@@ -63,7 +66,8 @@ module.exports = async () => { ...@@ -63,7 +66,8 @@ module.exports = async () => {
strings: lcObj, strings: lcObj,
isRTL: localeInfo.isRTL, isRTL: localeInfo.isRTL,
name: localeInfo.name, name: localeInfo.name,
nativeName: localeInfo.nativeName nativeName: localeInfo.nativeName,
availability: localeInfo.availability
}).where('code', currentLocale) }).where('code', currentLocale)
WIKI.logger.info(`Pulled latest locale updates for ${localeInfo.name} from Graph endpoint: [ COMPLETED ]`) WIKI.logger.info(`Pulled latest locale updates for ${localeInfo.name} from Graph endpoint: [ COMPLETED ]`)
......
...@@ -20,7 +20,8 @@ module.exports = class Locale extends Model { ...@@ -20,7 +20,8 @@ module.exports = class Locale extends Model {
name: {type: 'string'}, name: {type: 'string'},
nativeName: {type: 'string'}, nativeName: {type: 'string'},
createdAt: {type: 'string'}, createdAt: {type: 'string'},
updatedAt: {type: 'string'} updatedAt: {type: 'string'},
availability: {type: 'integer'}
} }
} }
} }
......
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