Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wiki-js
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
wiki-js
Commits
1522b269
Unverified
Commit
1522b269
authored
May 28, 2023
by
NGPixel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: admin language switcher
parent
e89f304d
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
186 additions
and
89 deletions
+186
-89
localization.mjs
server/graph/resolvers/localization.mjs
+1
-1
localization.graphql
server/graph/schemas/localization.graphql
+1
-0
App.vue
ux/src/App.vue
+45
-43
apollo.js
ux/src/boot/apollo.js
+6
-2
i18n.js
ux/src/boot/i18n.js
+5
-1
HeaderNav.vue
ux/src/components/HeaderNav.vue
+3
-1
AdminLayout.vue
ux/src/layouts/AdminLayout.vue
+27
-14
admin.js
ux/src/stores/admin.js
+28
-13
common.js
ux/src/stores/common.js
+43
-0
site.js
ux/src/stores/site.js
+27
-14
No files found.
server/graph/resolvers/localization.mjs
View file @
1522b269
...
...
@@ -5,7 +5,7 @@ export default {
Query
:
{
async
locales
(
obj
,
args
,
context
,
info
)
{
let
remoteLocales
=
await
WIKI
.
cache
.
get
(
'locales'
)
let
localLocales
=
await
WIKI
.
db
.
locales
.
query
().
select
(
'code'
,
'isRTL'
,
'name'
,
'nativeName'
,
'createdAt'
,
'updatedAt'
,
'completeness'
)
let
localLocales
=
await
WIKI
.
db
.
locales
.
query
().
select
(
'code'
,
'isRTL'
,
'
language'
,
'
name'
,
'nativeName'
,
'createdAt'
,
'updatedAt'
,
'completeness'
)
remoteLocales
=
remoteLocales
||
localLocales
return
_
.
map
(
remoteLocales
,
rl
=>
{
let
isInstalled
=
_
.
some
(
localLocales
,
[
'code'
,
rl
.
code
])
...
...
server/graph/schemas/localization.graphql
View file @
1522b269
...
...
@@ -31,6 +31,7 @@ type LocalizationLocale {
installDate
:
Date
isInstalled
:
Boolean
isRTL
:
Boolean
language
:
String
name
:
String
nativeName
:
String
region
:
String
...
...
ux/src/App.vue
View file @
1522b269
...
...
@@ -5,15 +5,16 @@ router-view
<
script
setup
>
import
{
nextTick
,
onMounted
,
reactive
,
watch
}
from
'vue'
import
{
useRouter
,
useRoute
}
from
'vue-router'
import
{
useFlagsStore
}
from
'src/stores/flags'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useUserStore
}
from
'src/stores/user'
import
{
setCssVar
,
useQuasar
}
from
'quasar'
import
{
useI18n
}
from
'vue-i18n'
import
gql
from
'graphql-tag'
import
'@mdi/font/css/materialdesignicons.css'
import
{
useCommonStore
}
from
'./stores/common'
import
{
useFlagsStore
}
from
'src/stores/flags'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useUserStore
}
from
'src/stores/user'
/* global siteConfig */
// QUASAR
...
...
@@ -22,6 +23,7 @@ const $q = useQuasar()
// STORES
const
commonStore
=
useCommonStore
()
const
flagsStore
=
useFlagsStore
()
const
siteStore
=
useSiteStore
()
const
userStore
=
useUserStore
()
...
...
@@ -54,6 +56,25 @@ watch(() => userStore.cvd, () => {
applyTheme
()
})
watch
(()
=>
commonStore
.
locale
,
applyLocale
)
// LOCALE
async
function
applyLocale
(
locale
)
{
if
(
!
i18n
.
availableLocales
.
includes
(
locale
))
{
try
{
i18n
.
setLocaleMessage
(
locale
,
await
commonStore
.
fetchLocaleStrings
(
locale
))
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
message
:
`Failed to load
${
locale
}
locale strings.`
,
caption
:
err
.
message
})
}
}
i18n
.
locale
.
value
=
locale
}
// THEME
async
function
applyTheme
()
{
...
...
@@ -89,35 +110,6 @@ async function applyTheme () {
}
}
// LOCALE
async
function
fetchLocaleStrings
(
locale
)
{
try
{
const
resp
=
await
APOLLO_CLIENT
.
query
({
query
:
gql
`
query fetchLocaleStrings (
$locale: String!
) {
localeStrings (
locale: $locale
)
}
`
,
fetchPolicy
:
'cache-first'
,
variables
:
{
locale
}
})
return
resp
?.
data
?.
localeStrings
}
catch
(
err
)
{
console
.
warn
(
err
)
$q
.
notify
({
type
:
'negative'
,
message
:
'Failed to load locale strings.'
})
}
}
// INIT SITE STORE
if
(
typeof
siteConfig
!==
'undefined'
)
{
...
...
@@ -128,30 +120,40 @@ if (typeof siteConfig !== 'undefined') {
applyTheme
()
}
// ROUTE GUARDS
router
.
beforeEach
(
async
(
to
,
from
)
=>
{
siteStore
.
routerLoading
=
true
// System Flags
commonStore
.
routerLoading
=
true
// -> System Flags
if
(
!
flagsStore
.
loaded
)
{
flagsStore
.
load
()
}
// Site Info
// -> Site Info
if
(
!
siteStore
.
id
)
{
console
.
info
(
'No pre-cached site config. Loading site info...'
)
await
siteStore
.
loadSite
(
window
.
location
.
hostname
)
console
.
info
(
`Using Site ID
${
siteStore
.
id
}
`
)
}
// Locales
if
(
!
i18n
.
availableLocales
.
includes
(
'en'
))
{
i18n
.
setLocaleMessage
(
'en'
,
await
fetchLocaleStrings
(
'en'
))
// -> Locale
if
(
!
commonStore
.
desiredLocale
||
!
siteStore
.
locales
.
active
.
includes
(
commonStore
.
desiredLocale
))
{
commonStore
.
setLocale
(
siteStore
.
locales
.
primary
)
}
else
{
applyLocale
(
commonStore
.
desiredLocale
)
}
// User Auth
// -> User Auth
await
userStore
.
refreshAuth
()
// User Profile
// -> User Profile
if
(
userStore
.
authenticated
&&
!
userStore
.
profileLoaded
)
{
console
.
info
(
`Refreshing user
${
userStore
.
id
}
profile...`
)
await
userStore
.
refreshProfile
()
}
// Page Permissions
// -> Page Permissions
await
userStore
.
fetchPagePermissions
(
to
.
path
)
})
...
...
@@ -177,7 +179,7 @@ router.afterEach(() => {
applyTheme
()
document
.
querySelector
(
'.init-loading'
).
remove
()
}
site
Store
.
routerLoading
=
false
common
Store
.
routerLoading
=
false
})
</
script
>
ux/src/boot/apollo.js
View file @
1522b269
...
...
@@ -3,10 +3,14 @@ import { ApolloClient, InMemoryCache } from '@apollo/client/core'
import
{
setContext
}
from
'@apollo/client/link/context'
import
{
createUploadLink
}
from
'apollo-upload-client'
export
default
boot
(({
app
,
store
})
=>
{
import
{
useUserStore
}
from
'src/stores/user'
export
default
boot
(({
app
})
=>
{
const
userStore
=
useUserStore
()
// Authentication Link
const
authLink
=
setContext
(
async
(
req
,
{
headers
})
=>
{
const
token
=
store
.
state
.
value
.
user
.
token
const
token
=
userStore
.
token
return
{
headers
:
{
...
headers
,
...
...
ux/src/boot/i18n.js
View file @
1522b269
import
{
boot
}
from
'quasar/wrappers'
import
{
createI18n
}
from
'vue-i18n'
import
{
useCommonStore
}
from
'src/stores/common'
export
default
boot
(({
app
})
=>
{
const
commonStore
=
useCommonStore
()
const
i18n
=
createI18n
({
legacy
:
false
,
locale
:
'en'
,
locale
:
commonStore
.
locale
||
'en'
,
fallbackLocale
:
'en'
,
fallbackWarn
:
false
,
messages
:
{}
...
...
ux/src/components/HeaderNav.vue
View file @
1522b269
...
...
@@ -68,7 +68,7 @@ q-header.bg-header.text-white.site-header(
q-space
transition(name='syncing')
q-spinner-tail(
v-show='
site
Store.routerLoading'
v-show='
common
Store.routerLoading'
color='accent'
size='24px'
)
...
...
@@ -130,6 +130,7 @@ import { useI18n } from 'vue-i18n'
import
{
useQuasar
}
from
'quasar'
import
{
reactive
}
from
'vue'
import
{
useCommonStore
}
from
'src/stores/common'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useUserStore
}
from
'src/stores/user'
...
...
@@ -139,6 +140,7 @@ const $q = useQuasar()
// STORES
const
commonStore
=
useCommonStore
()
const
siteStore
=
useSiteStore
()
const
userStore
=
useUserStore
()
...
...
ux/src/layouts/AdminLayout.vue
View file @
1522b269
...
...
@@ -18,11 +18,25 @@ q-layout.admin(view='hHh Lpr lff')
q-space
transition(name='syncing')
q-spinner-tail(
v-show='
site
Store.routerLoading'
v-show='
common
Store.routerLoading'
color='accent'
size='24px'
)
q-btn.q-ml-md(flat, dense, icon='las la-times-circle', label='Exit' color='pink', to='/')
q-btn.q-ml-md(flat, dense, icon='las la-times-circle', :label='t(`common.actions.exit`)' color='pink', to='/')
q-btn.q-ml-md(flat, dense, icon='las la-language', :label='commonStore.locale' color='grey-4')
q-menu.translucent-menu(auto-close, anchor='bottom right', self='top right')
q-list(separator)
q-item(
v-for='lang of adminStore.locales'
clickable
@click='commonStore.setLocale(lang.code)'
)
q-item-section(side)
q-avatar(rounded, :color='lang.code === commonStore.locale ? `secondary` : `primary`', text-color='white', size='sm')
.text-caption.text-uppercase: strong
{{
lang
.
language
}}
q-item-section
q-item-label
{{
lang
.
name
}}
q-item-label(caption)
{{
lang
.
nativeName
}}
account-menu
q-drawer.admin-sidebar(v-model='leftDrawerOpen', show-if-above, bordered)
q-scroll-area.admin-nav(
...
...
@@ -108,6 +122,9 @@ q-layout.admin(view='hHh Lpr lff')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-ssd.svg')
q-item-section
{{
t
(
'admin.storage.title'
)
}}
q-item-section(side)
//- TODO: Reflect site storage status
status-light(:color='true ? `positive` : `warning`', :pulse='false')
q-item(:to='`/_admin/` + adminStore.currentSiteId + `/theme`', v-ripple, active-class='bg-primary text-white', v-if='userStore.can(`manage:sites`) || userStore.can(`manage:theme`)')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-paint-roller.svg')
...
...
@@ -159,7 +176,7 @@ q-layout.admin(view='hHh Lpr lff')
q-icon(name='img:/_assets/icons/fluent-message-settings.svg')
q-item-section
{{
t
(
'admin.mail.title'
)
}}
q-item-section(side)
status-light(:color='adminStore.info.isMailConfigured ? `positive` : `warning`')
status-light(:color='adminStore.info.isMailConfigured ? `positive` : `warning`'
, :pulse='!adminStore.info.isMailConfigured'
)
q-item(to='/_admin/rendering', v-ripple, active-class='bg-primary text-white')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-rich-text-converter.svg')
...
...
@@ -169,7 +186,7 @@ q-layout.admin(view='hHh Lpr lff')
q-icon(name='img:/_assets/icons/fluent-bot.svg')
q-item-section
{{
t
(
'admin.scheduler.title'
)
}}
q-item-section(side)
status-light(:color='adminStore.info.isSchedulerHealthy ? `positive` : `warning`')
status-light(:color='adminStore.info.isSchedulerHealthy ? `positive` : `warning`'
, :pulse='!adminStore.info.isSchedulerHealthy'
)
q-item(to='/_admin/security', v-ripple, active-class='bg-primary text-white')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-protect.svg')
...
...
@@ -223,6 +240,7 @@ import { useRouter, useRoute } from 'vue-router'
import
{
useI18n
}
from
'vue-i18n'
import
{
useAdminStore
}
from
'src/stores/admin'
import
{
useCommonStore
}
from
'src/stores/common'
import
{
useFlagsStore
}
from
'src/stores/flags'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useUserStore
}
from
'src/stores/user'
...
...
@@ -244,6 +262,7 @@ const $q = useQuasar()
// STORES
const
adminStore
=
useAdminStore
()
const
commonStore
=
useCommonStore
()
const
flagsStore
=
useFlagsStore
()
const
siteStore
=
useSiteStore
()
const
userStore
=
useUserStore
()
...
...
@@ -266,13 +285,6 @@ useMeta({
// DATA
const
leftDrawerOpen
=
ref
(
true
)
const
overlayIsShown
=
ref
(
false
)
const
search
=
ref
(
''
)
const
user
=
reactive
({
name
:
'John Doe'
,
email
:
'test@example.com'
,
picture
:
null
})
const
thumbStyle
=
{
right
:
'1px'
,
borderRadius
:
'5px'
,
...
...
@@ -292,6 +304,9 @@ const siteSectionShown = computed(() => {
const
usersSectionShown
=
computed
(()
=>
{
return
userStore
.
can
(
'manage:groups'
)
||
userStore
.
can
(
'manage:users'
)
})
const
overlayIsShown
=
computed
(()
=>
{
return
Boolean
(
adminStore
.
overlay
)
})
// WATCHERS
...
...
@@ -308,9 +323,6 @@ watch(() => adminStore.sites, (newValue) => {
})
}
})
watch
(()
=>
adminStore
.
overlay
,
(
newValue
)
=>
{
overlayIsShown
.
value
=
!!
newValue
})
watch
(()
=>
adminStore
.
currentSiteId
,
(
newValue
)
=>
{
if
(
newValue
&&
route
.
params
.
siteid
!==
newValue
)
{
router
.
push
({
params
:
{
siteid
:
newValue
}
})
...
...
@@ -325,6 +337,7 @@ onMounted(async () => {
return
}
adminStore
.
fetchLocales
()
await
adminStore
.
fetchSites
()
if
(
route
.
params
.
siteid
)
{
adminStore
.
$patch
({
...
...
ux/src/stores/admin.js
View file @
1522b269
...
...
@@ -35,24 +35,20 @@ export const useAdminStore = defineStore('admin', {
}
},
actions
:
{
async
fetch
Sit
es
()
{
async
fetch
Local
es
()
{
const
resp
=
await
APOLLO_CLIENT
.
query
({
query
:
gql
`
query get
Sit
es {
sit
es {
id
hostnam
e
isEnabled
titl
e
query get
AdminLocal
es {
local
es {
code
languag
e
name
nativeNam
e
}
}
`
,
fetchPolicy
:
'network-only'
`
})
this
.
sites
=
cloneDeep
(
resp
?.
data
?.
sites
??
[])
if
(
!
this
.
currentSiteId
)
{
this
.
currentSiteId
=
this
.
sites
[
0
].
id
}
this
.
locales
=
cloneDeep
(
resp
?.
data
?.
locales
??
[])
},
async
fetchInfo
()
{
const
resp
=
await
APOLLO_CLIENT
.
query
({
...
...
@@ -78,6 +74,25 @@ export const useAdminStore = defineStore('admin', {
this
.
info
.
isApiEnabled
=
clone
(
resp
?.
data
?.
apiState
??
false
)
this
.
info
.
isMailConfigured
=
clone
(
resp
?.
data
?.
systemInfo
?.
isMailConfigured
??
false
)
this
.
info
.
isSchedulerHealthy
=
clone
(
resp
?.
data
?.
systemInfo
?.
isSchedulerHealthy
??
false
)
},
async
fetchSites
()
{
const
resp
=
await
APOLLO_CLIENT
.
query
({
query
:
gql
`
query getSites {
sites {
id
hostname
isEnabled
title
}
}
`
,
fetchPolicy
:
'network-only'
})
this
.
sites
=
cloneDeep
(
resp
?.
data
?.
sites
??
[])
if
(
!
this
.
currentSiteId
)
{
this
.
currentSiteId
=
this
.
sites
[
0
].
id
}
}
}
})
ux/src/stores/common.js
0 → 100644
View file @
1522b269
import
{
defineStore
}
from
'pinia'
import
gql
from
'graphql-tag'
export
const
useCommonStore
=
defineStore
(
'common'
,
{
state
:
()
=>
({
routerLoading
:
false
,
locale
:
localStorage
.
getItem
(
'locale'
)
||
'en'
,
desiredLocale
:
localStorage
.
getItem
(
'locale'
)
}),
getters
:
{},
actions
:
{
async
fetchLocaleStrings
(
locale
)
{
try
{
const
resp
=
await
APOLLO_CLIENT
.
query
({
query
:
gql
`
query fetchLocaleStrings (
$locale: String!
) {
localeStrings (
locale: $locale
)
}
`
,
fetchPolicy
:
'cache-first'
,
variables
:
{
locale
}
})
return
resp
?.
data
?.
localeStrings
}
catch
(
err
)
{
console
.
warn
(
err
)
throw
err
}
},
setLocale
(
locale
)
{
this
.
$patch
({
locale
,
desiredLocale
:
locale
})
localStorage
.
setItem
(
'locale'
,
locale
)
}
}
})
ux/src/stores/site.js
View file @
1522b269
...
...
@@ -6,9 +6,7 @@ import { useUserStore } from './user'
export
const
useSiteStore
=
defineStore
(
'site'
,
{
state
:
()
=>
({
routerLoading
:
false
,
id
:
null
,
useLocales
:
false
,
hostname
:
''
,
company
:
''
,
contentLicense
:
''
,
...
...
@@ -39,6 +37,10 @@ export const useSiteStore = defineStore('site', {
markdown
:
false
,
wysiwyg
:
false
},
locales
:
{
primary
:
'en'
,
active
:
[
'en'
]
},
theme
:
{
dark
:
false
,
injectCSS
:
''
,
...
...
@@ -84,6 +86,9 @@ export const useSiteStore = defineStore('site', {
opacity
:
isDark
?
0.25
:
1
}
}
},
useLocales
:
(
state
)
=>
{
return
state
.
locales
?.
active
?.
length
>
1
}
},
actions
:
{
...
...
@@ -104,20 +109,9 @@ export const useSiteStore = defineStore('site', {
hostname: $hostname
exact: false
) {
id
hostname
title
description
logoText
company
contentLicense
footerExtra
features {
profile
ratingsMode
reasonForChange
search
}
description
editors {
asciidoc {
isActive
...
...
@@ -129,6 +123,20 @@ export const useSiteStore = defineStore('site', {
isActive
}
}
features {
profile
ratingsMode
reasonForChange
search
}
footerExtra
hostname
id
locales {
primary
active
}
logoText
theme {
dark
colorPrimary
...
...
@@ -144,6 +152,7 @@ export const useSiteStore = defineStore('site', {
baseFont
contentFont
}
title
}
}
`
,
...
...
@@ -171,6 +180,10 @@ export const useSiteStore = defineStore('site', {
markdown
:
clone
(
siteInfo
.
editors
.
markdown
.
isActive
),
wysiwyg
:
clone
(
siteInfo
.
editors
.
wysiwyg
.
isActive
)
},
locales
:
{
primary
:
clone
(
siteInfo
.
locales
.
primary
),
active
:
clone
(
siteInfo
.
locales
.
active
)
},
theme
:
{
...
this
.
theme
,
...
clone
(
siteInfo
.
theme
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment