feat: profile (avatar, auth and groups) pages + various fixes

parent acc3b736
...@@ -400,14 +400,14 @@ router.get(['/t', '/t/*'], (req, res, next) => { ...@@ -400,14 +400,14 @@ router.get(['/t', '/t/*'], (req, res, next) => {
/** /**
* User Avatar * User Avatar
*/ */
router.get('/_userav/:uid', async (req, res, next) => { router.get('/_user/:uid/avatar', async (req, res, next) => {
if (!WIKI.auth.checkAccess(req.user, ['read:pages'])) { if (!WIKI.auth.checkAccess(req.user, ['read:pages'])) {
return res.sendStatus(403) return res.sendStatus(403)
} }
const av = await WIKI.db.users.getUserAvatarData(req.params.uid) const av = await WIKI.db.users.getUserAvatarData(req.params.uid)
if (av) { if (av) {
res.set('Content-Type', 'image/jpeg') res.set('Content-Type', 'image/jpeg')
res.send(av) return res.send(av)
} }
return res.sendStatus(404) return res.sendStatus(404)
......
...@@ -300,7 +300,7 @@ exports.up = async knex => { ...@@ -300,7 +300,7 @@ exports.up = async knex => {
table.jsonb('auth').notNullable().defaultTo('{}') table.jsonb('auth').notNullable().defaultTo('{}')
table.jsonb('meta').notNullable().defaultTo('{}') table.jsonb('meta').notNullable().defaultTo('{}')
table.jsonb('prefs').notNullable().defaultTo('{}') table.jsonb('prefs').notNullable().defaultTo('{}')
table.string('pictureUrl') table.boolean('hasAvatar').notNullable().defaultTo(false)
table.boolean('isSystem').notNullable().defaultTo(false) table.boolean('isSystem').notNullable().defaultTo(false)
table.boolean('isActive').notNullable().defaultTo(false) table.boolean('isActive').notNullable().defaultTo(false)
table.boolean('isVerified').notNullable().defaultTo(false) table.boolean('isVerified').notNullable().defaultTo(false)
......
const graphHelper = require('../../helpers/graph') const graphHelper = require('../../helpers/graph')
const _ = require('lodash') const _ = require('lodash')
const path = require('node:path')
const fs = require('fs-extra')
module.exports = { module.exports = {
Query: { Query: {
...@@ -273,9 +275,88 @@ module.exports = { ...@@ -273,9 +275,88 @@ module.exports = {
} catch (err) { } catch (err) {
return graphHelper.generateError(err) return graphHelper.generateError(err)
} }
},
/**
* UPLOAD USER AVATAR
*/
async uploadUserAvatar (obj, args) {
try {
const { filename, mimetype, createReadStream } = await args.image
WIKI.logger.debug(`Processing user ${args.id} avatar ${filename} of type ${mimetype}...`)
if (!WIKI.extensions.ext.sharp.isInstalled) {
throw new Error('This feature requires the Sharp extension but it is not installed.')
}
if (!['.png', '.jpg', '.webp', '.gif'].some(s => filename.endsWith(s))) {
throw new Error('Invalid File Extension. Must be png, jpg, webp or gif.')
}
const destFolder = path.resolve(
process.cwd(),
WIKI.config.dataPath,
`assets`
)
const destPath = path.join(destFolder, `userav-${args.id}.jpg`)
await fs.ensureDir(destFolder)
// -> Resize
await WIKI.extensions.ext.sharp.resize({
format: 'jpg',
inputStream: createReadStream(),
outputPath: destPath,
width: 180,
height: 180
})
// -> Set avatar flag for this user in the DB
await WIKI.db.users.query().findById(args.id).patch({ hasAvatar: true })
// -> Save image data to DB
const imgBuffer = await fs.readFile(destPath)
await WIKI.db.knex('userAvatars').insert({
id: args.id,
data: imgBuffer
}).onConflict('id').merge()
WIKI.logger.debug(`Processed user ${args.id} avatar successfully.`)
return {
operation: graphHelper.generateSuccess('User avatar uploaded successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
}
},
/**
* CLEAR USER AVATAR
*/
async clearUserAvatar (obj, args) {
try {
WIKI.logger.debug(`Clearing user ${args.id} avatar...`)
await WIKI.db.users.query().findById(args.id).patch({ hasAvatar: false })
await WIKI.db.knex('userAvatars').where({ id: args.id }).del()
WIKI.logger.debug(`Cleared user ${args.id} avatar successfully.`)
return {
operation: graphHelper.generateSuccess('User avatar cleared successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
}
} }
}, },
User: { User: {
async auth (usr) {
const authStrategies = await WIKI.db.authentication.getStrategies({ enabledOnly: true })
return _.transform(usr.auth, (result, value, key) => {
const authStrategy = _.find(authStrategies, ['id', key])
const authModule = _.find(WIKI.data.authentication, ['key', authStrategy.module])
if (!authStrategy || !authModule) { return }
result.push({
authId: key,
authName: authStrategy.displayName,
strategyKey: authStrategy.module,
strategyIcon: authModule.icon,
config: authStrategy.module === 'local' ? {
isTfaSetup: value.tfaSecret?.length > 0
} : {}
})
}, [])
},
groups (usr) { groups (usr) {
return usr.$relatedQuery('groups') return usr.$relatedQuery('groups')
} }
......
...@@ -73,6 +73,15 @@ extend type Mutation { ...@@ -73,6 +73,15 @@ extend type Mutation {
timeFormat: String timeFormat: String
appearance: UserSiteAppearance appearance: UserSiteAppearance
): DefaultResponse ): DefaultResponse
uploadUserAvatar(
id: UUID!
image: Upload!
): DefaultResponse
clearUserAvatar(
id: UUID!
): DefaultResponse
} }
# ----------------------------------------------- # -----------------------------------------------
...@@ -104,19 +113,27 @@ type User { ...@@ -104,19 +113,27 @@ type User {
id: UUID id: UUID
name: String name: String
email: String email: String
auth: JSON auth: [UserAuth]
hasAvatar: Boolean
isSystem: Boolean isSystem: Boolean
isActive: Boolean isActive: Boolean
isVerified: Boolean isVerified: Boolean
meta: JSON meta: JSON
prefs: JSON prefs: JSON
pictureUrl: String
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
lastLoginAt: Date lastLoginAt: Date
groups: [Group] groups: [Group]
} }
type UserAuth {
authId: UUID
authName: String
strategyKey: String
strategyIcon: String
config: JSON
}
type UserTokenResponse { type UserTokenResponse {
operation: Operation operation: Operation
jwt: String jwt: String
......
...@@ -105,7 +105,8 @@ module.exports = configure(function (/* ctx */) { ...@@ -105,7 +105,8 @@ module.exports = configure(function (/* ctx */) {
port: 3001, port: 3001,
proxy: { proxy: {
'/_graphql': 'http://localhost:3000/_graphql', '/_graphql': 'http://localhost:3000/_graphql',
'/_site': 'http://localhost:3000' '/_site': 'http://localhost:3000',
'/_user': 'http://localhost:3000'
} }
}, },
......
<template lang='pug'> <template lang='pug'>
q-btn.q-ml-md(flat, round, dense, color='grey') q-btn.q-ml-md(flat, round, dense, color='grey')
q-icon(v-if='!userStore.authenticated || !userStore.pictureUrl', name='las la-user-circle') q-icon(
q-avatar(v-else) v-if='!userStore.authenticated || !userStore.hasAvatar'
img(:src='userStore.pictureUrl') name='las la-user-circle'
)
q-avatar(
v-else
size='32px'
)
img(:src='`/_user/` + userStore.id + `/avatar`')
q-menu(auto-close) q-menu(auto-close)
q-card(flat, style='width: 300px;', :dark='false') q-card(flat, style='width: 300px;', :dark='false')
q-card-section(align='center') q-card-section(align='center')
...@@ -12,24 +18,32 @@ q-btn.q-ml-md(flat, round, dense, color='grey') ...@@ -12,24 +18,32 @@ q-btn.q-ml-md(flat, round, dense, color='grey')
q-card-actions(align='center') q-card-actions(align='center')
q-btn( q-btn(
flat flat
label='Profile' :label='t(`common.header.profile`)'
icon='las la-user-alt' icon='las la-user-alt'
color='primary' color='primary'
to='/_profile' to='/_profile'
no-caps no-caps
) )
q-btn(flat q-btn(flat
label='Logout' :label='t(`common.header.logout`)'
icon='las la-sign-out-alt' icon='las la-sign-out-alt'
color='red' color='red'
href='/logout' href='/logout'
no-caps no-caps
) )
q-tooltip Account q-tooltip {{ t('common.header.account') }}
</template> </template>
<script setup> <script setup>
import { useI18n } from 'vue-i18n'
import { useUserStore } from 'src/stores/user' import { useUserStore } from 'src/stores/user'
// STORES
const userStore = useUserStore() const userStore = useUserStore()
// I18N
const { t } = useI18n()
</script> </script>
...@@ -50,6 +50,7 @@ const { t } = useI18n() ...@@ -50,6 +50,7 @@ const { t } = useI18n()
// METHODS // METHODS
function create (editor) { function create (editor) {
pageStore.pageCreate({ editor }) window.location.assign('/_edit/new')
// pageStore.pageCreate({ editor })
} }
</script> </script>
...@@ -318,13 +318,17 @@ const quickaccess = [ ...@@ -318,13 +318,17 @@ const quickaccess = [
{ key: 'refCardVisibility', icon: 'las la-eye', label: t('editor.props.visibility') } { key: 'refCardVisibility', icon: 'las la-eye', label: t('editor.props.visibility') }
] ]
// REFS
const iptPagePassword = ref(null)
// WATCHERS // WATCHERS
watch(() => state.requirePassword, (newValue) => { watch(() => state.requirePassword, (newValue) => {
if (newValue) { if (newValue) {
nextTick(() => { nextTick(() => {
this.$refs.iptPagePassword.focus() iptPagePassword.value.focus()
this.$refs.iptPagePassword.$el.scrollIntoView({ iptPagePassword.value.$el.scrollIntoView({
behavior: 'smooth' behavior: 'smooth'
}) })
}) })
......
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
"admin.analytics.saveSuccess": "Analytics configuration saved successfully", "admin.analytics.saveSuccess": "Analytics configuration saved successfully",
"admin.analytics.subtitle": "Add analytics and tracking tools to your wiki", "admin.analytics.subtitle": "Add analytics and tracking tools to your wiki",
"admin.analytics.title": "Analytics", "admin.analytics.title": "Analytics",
"admin.api.copyKeyTitle": "Copy API Key",
"admin.api.createInvalidData": "Some fields are missing or have invalid data.",
"admin.api.createSuccess": "API Key created successfully.",
"admin.api.disableButton": "Disable API", "admin.api.disableButton": "Disable API",
"admin.api.disabled": "API Disabled", "admin.api.disabled": "API Disabled",
"admin.api.enableButton": "Enable API", "admin.api.enableButton": "Enable API",
...@@ -16,12 +19,18 @@ ...@@ -16,12 +19,18 @@
"admin.api.expiration30d": "30 days", "admin.api.expiration30d": "30 days",
"admin.api.expiration3y": "3 years", "admin.api.expiration3y": "3 years",
"admin.api.expiration90d": "90 days", "admin.api.expiration90d": "90 days",
"admin.api.groupSelected": "Use {group} group permissions",
"admin.api.groupsMissing": "You must select at least 1 group for this key.",
"admin.api.groupsSelected": "Use permissions from {count} groups",
"admin.api.headerCreated": "Created", "admin.api.headerCreated": "Created",
"admin.api.headerExpiration": "Expiration", "admin.api.headerExpiration": "Expiration",
"admin.api.headerKeyEnding": "Key Ending", "admin.api.headerKeyEnding": "Key Ending",
"admin.api.headerLastUpdated": "Last Updated", "admin.api.headerLastUpdated": "Last Updated",
"admin.api.headerName": "Name", "admin.api.headerName": "Name",
"admin.api.headerRevoke": "Revoke", "admin.api.headerRevoke": "Revoke",
"admin.api.key": "API Key",
"admin.api.nameInvalidChars": "Key name has invalid characters.",
"admin.api.nameMissing": "Key name is missing.",
"admin.api.newKeyButton": "New API Key", "admin.api.newKeyButton": "New API Key",
"admin.api.newKeyCopyWarn": "Copy the key shown below as {bold}", "admin.api.newKeyCopyWarn": "Copy the key shown below as {bold}",
"admin.api.newKeyCopyWarnBold": "it will NOT be shown again.", "admin.api.newKeyCopyWarnBold": "it will NOT be shown again.",
...@@ -40,10 +49,14 @@ ...@@ -40,10 +49,14 @@
"admin.api.newKeySuccess": "API key created successfully.", "admin.api.newKeySuccess": "API key created successfully.",
"admin.api.newKeyTitle": "New API Key", "admin.api.newKeyTitle": "New API Key",
"admin.api.noKeyInfo": "No API keys have been generated yet.", "admin.api.noKeyInfo": "No API keys have been generated yet.",
"admin.api.none": "There are no API keys yet.",
"admin.api.permissionGroups": "Group Permissions",
"admin.api.refreshSuccess": "List of API keys has been refreshed.", "admin.api.refreshSuccess": "List of API keys has been refreshed.",
"admin.api.revoke": "Revoke", "admin.api.revoke": "Revoke",
"admin.api.revokeConfirm": "Revoke API Key?", "admin.api.revokeConfirm": "Revoke API Key?",
"admin.api.revokeConfirmText": "Are you sure you want to revoke key {name}? This action cannot be undone!", "admin.api.revokeConfirmText": "Are you sure you want to revoke key {name}? This action cannot be undone!",
"admin.api.revoked": "Revoked",
"admin.api.revokedHint": "This key has been revoked and can no longer be used.",
"admin.api.revokeSuccess": "The key has been revoked successfully.", "admin.api.revokeSuccess": "The key has been revoked successfully.",
"admin.api.subtitle": "Manage keys to access the API", "admin.api.subtitle": "Manage keys to access the API",
"admin.api.title": "API Access", "admin.api.title": "API Access",
...@@ -63,6 +76,9 @@ ...@@ -63,6 +76,9 @@
"admin.auth.displayNameHint": "The title shown to the end user for this authentication strategy.", "admin.auth.displayNameHint": "The title shown to the end user for this authentication strategy.",
"admin.auth.domainsWhitelist": "Limit to specific email domains", "admin.auth.domainsWhitelist": "Limit to specific email domains",
"admin.auth.domainsWhitelistHint": "A list of domains authorized to register. The user email address domain must match one of these to gain access.", "admin.auth.domainsWhitelistHint": "A list of domains authorized to register. The user email address domain must match one of these to gain access.",
"admin.auth.enabled": "Enabled",
"admin.auth.enabledForced": "This strategy cannot be disabled.",
"admin.auth.enabledHint": "Should this strategy be available to sites for login.",
"admin.auth.force2fa": "Force all users to use Two-Factor Authentication (2FA)", "admin.auth.force2fa": "Force all users to use Two-Factor Authentication (2FA)",
"admin.auth.force2faHint": "Users will be required to setup 2FA the first time they login and cannot be disabled by the user.", "admin.auth.force2faHint": "Users will be required to setup 2FA the first time they login and cannot be disabled by the user.",
"admin.auth.globalAdvSettings": "Global Advanced Settings", "admin.auth.globalAdvSettings": "Global Advanced Settings",
...@@ -75,6 +91,7 @@ ...@@ -75,6 +91,7 @@
"admin.auth.selfRegistration": "Allow self-registration", "admin.auth.selfRegistration": "Allow self-registration",
"admin.auth.selfRegistrationHint": "Allow any user successfully authorized by the strategy to access the wiki.", "admin.auth.selfRegistrationHint": "Allow any user successfully authorized by the strategy to access the wiki.",
"admin.auth.siteUrlNotSetup": "You must set a valid {siteUrl} first! Click on {general} in the left sidebar.", "admin.auth.siteUrlNotSetup": "You must set a valid {siteUrl} first! Click on {general} in the left sidebar.",
"admin.auth.status": "Status",
"admin.auth.strategies": "Strategies", "admin.auth.strategies": "Strategies",
"admin.auth.strategyConfiguration": "Strategy Configuration", "admin.auth.strategyConfiguration": "Strategy Configuration",
"admin.auth.strategyIsEnabled": "Active", "admin.auth.strategyIsEnabled": "Active",
...@@ -86,6 +103,9 @@ ...@@ -86,6 +103,9 @@
"admin.auth.strategyStateLocked": "and cannot be disabled.", "admin.auth.strategyStateLocked": "and cannot be disabled.",
"admin.auth.subtitle": "Configure the authentication settings of your wiki", "admin.auth.subtitle": "Configure the authentication settings of your wiki",
"admin.auth.title": "Authentication", "admin.auth.title": "Authentication",
"admin.auth.vendor": "Vendor",
"admin.auth.vendorWebsite": "Website",
"admin.blocks.title": "Content Blocks",
"admin.comments.provider": "Provider", "admin.comments.provider": "Provider",
"admin.comments.providerConfig": "Provider Configuration", "admin.comments.providerConfig": "Provider Configuration",
"admin.comments.providerNoConfig": "This provider has no configuration options you can modify.", "admin.comments.providerNoConfig": "This provider has no configuration options you can modify.",
...@@ -126,11 +146,11 @@ ...@@ -126,11 +146,11 @@
"admin.editors.wysiwygName": "Visual Editor", "admin.editors.wysiwygName": "Visual Editor",
"admin.extensions.incompatible": "not compatible", "admin.extensions.incompatible": "not compatible",
"admin.extensions.install": "Install", "admin.extensions.install": "Install",
"admin.extensions.installFailed": "Failed to install extension.",
"admin.extensions.installSuccess": "Extension installed successfully.",
"admin.extensions.installed": "Installed", "admin.extensions.installed": "Installed",
"admin.extensions.installFailed": "Failed to install extension.",
"admin.extensions.installing": "Installing extension...", "admin.extensions.installing": "Installing extension...",
"admin.extensions.installingHint": "This may take a while depending on your server.", "admin.extensions.installingHint": "This may take a while depending on your server.",
"admin.extensions.installSuccess": "Extension installed successfully.",
"admin.extensions.instructions": "Instructions", "admin.extensions.instructions": "Instructions",
"admin.extensions.instructionsHint": "Must be installed manually", "admin.extensions.instructionsHint": "Must be installed manually",
"admin.extensions.reinstall": "Reinstall", "admin.extensions.reinstall": "Reinstall",
...@@ -163,13 +183,13 @@ ...@@ -163,13 +183,13 @@
"admin.general.contentLicenseHint": "License shown in the footer of all content pages.", "admin.general.contentLicenseHint": "License shown in the footer of all content pages.",
"admin.general.defaultDateFormat": "Default Date Format", "admin.general.defaultDateFormat": "Default Date Format",
"admin.general.defaultDateFormatHint": "The default date format for new users.", "admin.general.defaultDateFormatHint": "The default date format for new users.",
"admin.general.defaults": "Site Defaults",
"admin.general.defaultTimeFormat": "Default Time Format", "admin.general.defaultTimeFormat": "Default Time Format",
"admin.general.defaultTimeFormat12h": "12 hour", "admin.general.defaultTimeFormat12h": "12 hour",
"admin.general.defaultTimeFormat24h": "24 hour", "admin.general.defaultTimeFormat24h": "24 hour",
"admin.general.defaultTimeFormatHint": "The default time format for new users.", "admin.general.defaultTimeFormatHint": "The default time format for new users.",
"admin.general.defaultTimezone": "Default Timezone", "admin.general.defaultTimezone": "Default Timezone",
"admin.general.defaultTimezoneHint": "The default timezone for new users.", "admin.general.defaultTimezoneHint": "The default timezone for new users.",
"admin.general.defaults": "Site Defaults",
"admin.general.displaySiteTitle": "Display Site Title", "admin.general.displaySiteTitle": "Display Site Title",
"admin.general.displaySiteTitleHint": "Should the site title be displayed next to the logo? If your logo isn't square and contain your brand name, turn this option off.", "admin.general.displaySiteTitleHint": "Should the site title be displayed next to the logo? If your logo isn't square and contain your brand name, turn this option off.",
"admin.general.favicon": "Favicon", "admin.general.favicon": "Favicon",
...@@ -177,11 +197,15 @@ ...@@ -177,11 +197,15 @@
"admin.general.faviconUploadSuccess": "Site Favicon uploaded successfully.", "admin.general.faviconUploadSuccess": "Site Favicon uploaded successfully.",
"admin.general.features": "Features", "admin.general.features": "Features",
"admin.general.footerCopyright": "Footer / Copyright", "admin.general.footerCopyright": "Footer / Copyright",
"admin.general.footerExtra": "Additional Footer Text",
"admin.general.footerExtraHint": "Optionally add more content to the footer, such as additional copyright terms or mandatory regulatory info.",
"admin.general.general": "General", "admin.general.general": "General",
"admin.general.logo": "Logo", "admin.general.logo": "Logo",
"admin.general.logoUpl": "Site Logo", "admin.general.logoUpl": "Site Logo",
"admin.general.logoUplHint": "Logo image file, in SVG, PNG, JPG, WEBP or GIF format.", "admin.general.logoUplHint": "Logo image file, in SVG, PNG, JPG, WEBP or GIF format.",
"admin.general.logoUploadSuccess": "Site logo uploaded successfully.", "admin.general.logoUploadSuccess": "Site logo uploaded successfully.",
"admin.general.pageExtensions": "Page Extensions",
"admin.general.pageExtensionsHint": "A comma-separated list of URL extensions that will be treated as pages. For example, adding md will treat /foobar.md the same as /foobar.",
"admin.general.ratingsOff": "Off", "admin.general.ratingsOff": "Off",
"admin.general.ratingsStars": "Stars", "admin.general.ratingsStars": "Stars",
"admin.general.ratingsThumbs": "Thumbs", "admin.general.ratingsThumbs": "Thumbs",
...@@ -200,6 +224,7 @@ ...@@ -200,6 +224,7 @@
"admin.general.siteHostnameInvalid": "Invalid Hostname", "admin.general.siteHostnameInvalid": "Invalid Hostname",
"admin.general.siteInfo": "Site Info", "admin.general.siteInfo": "Site Info",
"admin.general.sitemap": "Allow Sitemap", "admin.general.sitemap": "Allow Sitemap",
"admin.general.sitemapHint": "Make a sitemap.xml available to search engines with all pages accessible to guests.",
"admin.general.siteTitle": "Site Title", "admin.general.siteTitle": "Site Title",
"admin.general.siteTitleHint": "Displayed in the top bar and appended to all pages meta title.", "admin.general.siteTitleHint": "Displayed in the top bar and appended to all pages meta title.",
"admin.general.siteTitleInvalidChars": "Site Title contains invalid characters.", "admin.general.siteTitleInvalidChars": "Site Title contains invalid characters.",
...@@ -209,6 +234,7 @@ ...@@ -209,6 +234,7 @@
"admin.general.uploadLogo": "Upload Logo", "admin.general.uploadLogo": "Upload Logo",
"admin.general.uploadSizeHint": "An image of {size} pixels is recommended for best results.", "admin.general.uploadSizeHint": "An image of {size} pixels is recommended for best results.",
"admin.general.uploadTypesHint": "{typeList} or {lastType} files only", "admin.general.uploadTypesHint": "{typeList} or {lastType} files only",
"admin.general.urlHandling": "URL Handling",
"admin.groups.assignUser": "Assign User", "admin.groups.assignUser": "Assign User",
"admin.groups.authBehaviors": "Authentication Behaviors", "admin.groups.authBehaviors": "Authentication Behaviors",
"admin.groups.create": "New Group", "admin.groups.create": "New Group",
...@@ -251,10 +277,10 @@ ...@@ -251,10 +277,10 @@
"admin.groups.ruleMatchStart": "Path Starts With...", "admin.groups.ruleMatchStart": "Path Starts With...",
"admin.groups.ruleMatchTag": "Has Any Tag...", "admin.groups.ruleMatchTag": "Has Any Tag...",
"admin.groups.ruleMatchTagAll": "Has All Tags...", "admin.groups.ruleMatchTagAll": "Has All Tags...",
"admin.groups.ruleSites": "Site(s)",
"admin.groups.ruleUntitled": "Untitled Rule",
"admin.groups.rules": "Rules", "admin.groups.rules": "Rules",
"admin.groups.ruleSites": "Site(s)",
"admin.groups.rulesNone": "This group doesn't have any rules yet.", "admin.groups.rulesNone": "This group doesn't have any rules yet.",
"admin.groups.ruleUntitled": "Untitled Rule",
"admin.groups.selectedLocales": "Any Locale | {locale} locale only | {count} locales selected", "admin.groups.selectedLocales": "Any Locale | {locale} locale only | {count} locales selected",
"admin.groups.selectedSites": "Any Site | 1 site selected | {count} sites selected", "admin.groups.selectedSites": "Any Site | 1 site selected | {count} sites selected",
"admin.groups.subtitle": "Manage user groups and permissions", "admin.groups.subtitle": "Manage user groups and permissions",
...@@ -262,6 +288,13 @@ ...@@ -262,6 +288,13 @@
"admin.groups.userCount": "User Count", "admin.groups.userCount": "User Count",
"admin.groups.users": "Users", "admin.groups.users": "Users",
"admin.groups.usersCount": "0 user | 1 user | {count} users", "admin.groups.usersCount": "0 user | 1 user | {count} users",
"admin.groups.usersNone": "This group doesn't have any user yet.",
"admin.instances.activeConnections": "Active Connections",
"admin.instances.activeListeners": "Active Listeners",
"admin.instances.firstSeen": "First Seen",
"admin.instances.lastSeen": "Last Seen",
"admin.instances.subtitle": "View a list of active instances",
"admin.instances.title": "Instances",
"admin.locale.activeNamespaces": "Active Namespaces", "admin.locale.activeNamespaces": "Active Namespaces",
"admin.locale.autoUpdate.hint": "Automatically download updates to this locale as they become available.", "admin.locale.autoUpdate.hint": "Automatically download updates to this locale as they become available.",
"admin.locale.autoUpdate.hintWithNS": "Automatically download updates to all namespaced locales enabled below.", "admin.locale.autoUpdate.hintWithNS": "Automatically download updates to all namespaced locales enabled below.",
...@@ -290,6 +323,7 @@ ...@@ -290,6 +323,7 @@
"admin.logging.title": "Logging", "admin.logging.title": "Logging",
"admin.login.background": "Background Image", "admin.login.background": "Background Image",
"admin.login.backgroundHint": "Specify an image to use as the login background. PNG and JPG are supported, 1920x1080 recommended. Leave empty for default.", "admin.login.backgroundHint": "Specify an image to use as the login background. PNG and JPG are supported, 1920x1080 recommended. Leave empty for default.",
"admin.login.bgUploadSuccess": "Login background image uploaded successfully.",
"admin.login.bypassScreen": "Bypass Login Screen", "admin.login.bypassScreen": "Bypass Login Screen",
"admin.login.bypassScreenHint": "Should the user be redirected automatically to the first authentication provider. Has no effect if the first provider is a username/password provider type.", "admin.login.bypassScreenHint": "Should the user be redirected automatically to the first authentication provider. Has no effect if the first provider is a username/password provider type.",
"admin.login.bypassUnauthorized": "Bypass Unauthorized Screen", "admin.login.bypassUnauthorized": "Bypass Unauthorized Screen",
...@@ -301,6 +335,7 @@ ...@@ -301,6 +335,7 @@
"admin.login.logoutRedirectHint": "Optionally redirect the user to a specific page when he/she logouts. This can be overridden at the group level.", "admin.login.logoutRedirectHint": "Optionally redirect the user to a specific page when he/she logouts. This can be overridden at the group level.",
"admin.login.providers": "Login Providers", "admin.login.providers": "Login Providers",
"admin.login.providersVisbleWarning": "Note that you can always temporarily show all hidden providers by adding ?all=1 to the url. This is useful to login as local admin while hiding it from normal users.", "admin.login.providersVisbleWarning": "Note that you can always temporarily show all hidden providers by adding ?all=1 to the url. This is useful to login as local admin while hiding it from normal users.",
"admin.login.saveSuccess": "Login configuration saved successfully.",
"admin.login.subtitle": "Configure the login user experience of your wiki site", "admin.login.subtitle": "Configure the login user experience of your wiki site",
"admin.login.title": "Login", "admin.login.title": "Login",
"admin.login.welcomeRedirect": "First-time Login Redirect", "admin.login.welcomeRedirect": "First-time Login Redirect",
...@@ -317,13 +352,15 @@ ...@@ -317,13 +352,15 @@
"admin.mail.dkimUse": "Use DKIM", "admin.mail.dkimUse": "Use DKIM",
"admin.mail.dkimUseHint": "Should DKIM be used when sending emails.", "admin.mail.dkimUseHint": "Should DKIM be used when sending emails.",
"admin.mail.saveSuccess": "Configuration saved successfully.", "admin.mail.saveSuccess": "Configuration saved successfully.",
"admin.mail.sendTestSuccess": "A test email was sent successfully.",
"admin.mail.sender": "Sender", "admin.mail.sender": "Sender",
"admin.mail.senderEmail": "Sender Email", "admin.mail.senderEmail": "Sender Email",
"admin.mail.senderName": "Sender Name", "admin.mail.senderName": "Sender Name",
"admin.mail.sendTestSuccess": "A test email was sent successfully.",
"admin.mail.smtp": "SMTP Settings", "admin.mail.smtp": "SMTP Settings",
"admin.mail.smtpHost": "Host", "admin.mail.smtpHost": "Host",
"admin.mail.smtpHostHint": "Hostname or IP address of the SMTP server.", "admin.mail.smtpHostHint": "Hostname or IP address of the SMTP server.",
"admin.mail.smtpName": "Client Identifying Name",
"admin.mail.smtpNameHint": "An optional name to send to the SMTP server to identify your mailer. Leave empty to use server hostname. For Google Workspace customers, this should be your main domain name.",
"admin.mail.smtpPort": "Port", "admin.mail.smtpPort": "Port",
"admin.mail.smtpPortHint": "Usually 465 (recommended), 587 or 25.", "admin.mail.smtpPortHint": "Usually 465 (recommended), 587 or 25.",
"admin.mail.smtpPwd": "Password", "admin.mail.smtpPwd": "Password",
...@@ -336,8 +373,8 @@ ...@@ -336,8 +373,8 @@
"admin.mail.smtpVerifySSLHint": "Some hosts requires SSL certificate checking to be disabled. Leave enabled for proper security.", "admin.mail.smtpVerifySSLHint": "Some hosts requires SSL certificate checking to be disabled. Leave enabled for proper security.",
"admin.mail.subtitle": "Configure mail settings", "admin.mail.subtitle": "Configure mail settings",
"admin.mail.templateResetPwd": "Password Reset Email", "admin.mail.templateResetPwd": "Password Reset Email",
"admin.mail.templateWelcome": "Welcome Email",
"admin.mail.templates": "Mail Templates", "admin.mail.templates": "Mail Templates",
"admin.mail.templateWelcome": "Welcome Email",
"admin.mail.test": "Send a test email", "admin.mail.test": "Send a test email",
"admin.mail.testHint": "Send a test email to ensure your SMTP configuration is working.", "admin.mail.testHint": "Send a test email to ensure your SMTP configuration is working.",
"admin.mail.testRecipient": "Recipient Email Address", "admin.mail.testRecipient": "Recipient Email Address",
...@@ -388,6 +425,38 @@ ...@@ -388,6 +425,38 @@
"admin.pages.title": "Pages", "admin.pages.title": "Pages",
"admin.rendering.subtitle": "Configure the page rendering pipeline", "admin.rendering.subtitle": "Configure the page rendering pipeline",
"admin.rendering.title": "Rendering", "admin.rendering.title": "Rendering",
"admin.scheduler.active": "Active",
"admin.scheduler.activeNone": "There are no active jobs at the moment.",
"admin.scheduler.attempt": "Attempt",
"admin.scheduler.cancelJob": "Cancel Job",
"admin.scheduler.cancelJobSuccess": "Job cancelled successfully.",
"admin.scheduler.completed": "Completed",
"admin.scheduler.completedIn": "Completed in {duration}",
"admin.scheduler.completedNone": "There are no recently completed job to display.",
"admin.scheduler.createdAt": "Created",
"admin.scheduler.createdBy": "by instance {instance}",
"admin.scheduler.cron": "Cron",
"admin.scheduler.error": "Error",
"admin.scheduler.failed": "Failed",
"admin.scheduler.failedNone": "There are no recently failed job to display.",
"admin.scheduler.interrupted": "Interrupted",
"admin.scheduler.pending": "Pending",
"admin.scheduler.result": "Result",
"admin.scheduler.retryJob": "Retry Job",
"admin.scheduler.retryJobSuccess": "Job has been rescheduled and will execute shortly.",
"admin.scheduler.schedule": "Schedule",
"admin.scheduler.scheduled": "Scheduled",
"admin.scheduler.scheduledNone": "There are no scheduled jobs at the moment.",
"admin.scheduler.startedAt": "Started",
"admin.scheduler.state": "State",
"admin.scheduler.subtitle": "View scheduled and completed jobs",
"admin.scheduler.title": "Scheduler",
"admin.scheduler.type": "Type",
"admin.scheduler.upcoming": "Upcoming",
"admin.scheduler.upcomingNone": "There are no upcoming job for the moment.",
"admin.scheduler.updatedAt": "Last Updated",
"admin.scheduler.useWorker": "Execution Mode",
"admin.scheduler.waitUntil": "Start",
"admin.search.configSaveSuccess": "Search engine configuration saved successfully.", "admin.search.configSaveSuccess": "Search engine configuration saved successfully.",
"admin.search.engineConfig": "Engine Configuration", "admin.search.engineConfig": "Engine Configuration",
"admin.search.engineNoConfig": "This engine has no configuration options you can modify.", "admin.search.engineNoConfig": "This engine has no configuration options you can modify.",
...@@ -502,10 +571,10 @@ ...@@ -502,10 +571,10 @@
"admin.storage.assetDirectAccess": "Direct Access", "admin.storage.assetDirectAccess": "Direct Access",
"admin.storage.assetDirectAccessHint": "Assets are accessed directly by the user using a secure / signed link. When enabled, takes priority over file streaming.", "admin.storage.assetDirectAccessHint": "Assets are accessed directly by the user using a secure / signed link. When enabled, takes priority over file streaming.",
"admin.storage.assetDirectAccessNotSupported": "Not supported by this storage target.", "admin.storage.assetDirectAccessNotSupported": "Not supported by this storage target.",
"admin.storage.assetsOnly": "Assets Only",
"admin.storage.assetStreaming": "File Streaming", "admin.storage.assetStreaming": "File Streaming",
"admin.storage.assetStreamingHint": "Assets will be streamed from the storage target, through the server, to the user.", "admin.storage.assetStreamingHint": "Assets will be streamed from the storage target, through the server, to the user.",
"admin.storage.assetStreamingNotSupported": "Not supported by this storage target.", "admin.storage.assetStreamingNotSupported": "Not supported by this storage target.",
"admin.storage.assetsOnly": "Assets Only",
"admin.storage.cancelSetup": "Cancel", "admin.storage.cancelSetup": "Cancel",
"admin.storage.config": "Configuration", "admin.storage.config": "Configuration",
"admin.storage.contentTypeDocuments": "Documents", "admin.storage.contentTypeDocuments": "Documents",
...@@ -576,27 +645,28 @@ ...@@ -576,27 +645,28 @@
"admin.storage.setup": "Setup", "admin.storage.setup": "Setup",
"admin.storage.setupConfiguredHint": "This module is already configured. You can uninstall this module to start over.", "admin.storage.setupConfiguredHint": "This module is already configured. You can uninstall this module to start over.",
"admin.storage.setupHint": "This module requires a setup process to be completed in order to use it. Follow the instructions below to get started.", "admin.storage.setupHint": "This module requires a setup process to be completed in order to use it. Follow the instructions below to get started.",
"admin.storage.setupRequired": "Setup required",
"admin.storage.startSetup": "Start Setup", "admin.storage.startSetup": "Start Setup",
"admin.storage.status": "Status", "admin.storage.status": "Status",
"admin.storage.subtitle": "Set backup and sync targets for your content", "admin.storage.subtitle": "Set backup and sync targets for your content",
"admin.storage.sync": "Synchronization", "admin.storage.sync": "Synchronization",
"admin.storage.syncDirBi": "Bi-directional", "admin.storage.syncDirBi": "Bi-directional",
"admin.storage.syncDirBiHint": "In bi-directional mode, content is first pulled from the storage target. Any newer content overwrites local content. New content since last sync is then pushed to the storage target, overwriting any content on target if present.", "admin.storage.syncDirBiHint": "In bi-directional mode, content is first pulled from the storage target. Any newer content overwrites local content. New content since last sync is then pushed to the storage target, overwriting any content on target if present.",
"admin.storage.syncDirection": "Sync Direction",
"admin.storage.syncDirectionSubtitle": "Choose how content synchronization is handled for this storage target.",
"admin.storage.syncDirPull": "Pull from target", "admin.storage.syncDirPull": "Pull from target",
"admin.storage.syncDirPullHint": "Content is always pulled from the storage target, overwriting any local content which already exists. This choice is usually reserved for single-use content import. Caution with this option as any local content will always be overwritten!", "admin.storage.syncDirPullHint": "Content is always pulled from the storage target, overwriting any local content which already exists. This choice is usually reserved for single-use content import. Caution with this option as any local content will always be overwritten!",
"admin.storage.syncDirPush": "Push to target", "admin.storage.syncDirPush": "Push to target",
"admin.storage.syncDirPushHint": "Content is always pushed to the storage target, overwriting any existing content. This is safest choice for backup scenarios.", "admin.storage.syncDirPushHint": "Content is always pushed to the storage target, overwriting any existing content. This is safest choice for backup scenarios.",
"admin.storage.syncDirection": "Sync Direction",
"admin.storage.syncDirectionSubtitle": "Choose how content synchronization is handled for this storage target.",
"admin.storage.syncSchedule": "Sync Schedule", "admin.storage.syncSchedule": "Sync Schedule",
"admin.storage.syncScheduleCurrent": "Currently set to every {schedule}.", "admin.storage.syncScheduleCurrent": "Currently set to every {schedule}.",
"admin.storage.syncScheduleDefault": "The default is every {schedule}.", "admin.storage.syncScheduleDefault": "The default is every {schedule}.",
"admin.storage.syncScheduleHint": "For performance reasons, this storage target synchronize changes on an interval-based schedule, instead of on every change. Define at which interval should the synchronization occur.", "admin.storage.syncScheduleHint": "For performance reasons, this storage target synchronize changes on an interval-based schedule, instead of on every change. Define at which interval should the synchronization occur.",
"admin.storage.targetConfig": "Target Configuration", "admin.storage.targetConfig": "Target Configuration",
"admin.storage.targets": "Targets",
"admin.storage.targetState": "This storage target is {state}", "admin.storage.targetState": "This storage target is {state}",
"admin.storage.targetStateActive": "active", "admin.storage.targetStateActive": "active",
"admin.storage.targetStateInactive": "inactive", "admin.storage.targetStateInactive": "inactive",
"admin.storage.targets": "Targets",
"admin.storage.title": "Storage", "admin.storage.title": "Storage",
"admin.storage.uninstall": "Uninstall", "admin.storage.uninstall": "Uninstall",
"admin.storage.unsupported": "Unsupported", "admin.storage.unsupported": "Unsupported",
...@@ -610,6 +680,9 @@ ...@@ -610,6 +680,9 @@
"admin.storage.versioningNotSupported": "Not supported by this storage target.", "admin.storage.versioningNotSupported": "Not supported by this storage target.",
"admin.system.browser": "Browser", "admin.system.browser": "Browser",
"admin.system.browserHint": "The browser name and version.", "admin.system.browserHint": "The browser name and version.",
"admin.system.checkForUpdates": "Check",
"admin.system.checkingForUpdates": "Checking for Updates...",
"admin.system.checkUpdate": "Check / Upgrade",
"admin.system.client": "Client", "admin.system.client": "Client",
"admin.system.clientCookies": "Cookies Support", "admin.system.clientCookies": "Cookies Support",
"admin.system.clientCookiesHint": "Whether cookies are enabled on this browser.", "admin.system.clientCookiesHint": "Whether cookies are enabled on this browser.",
...@@ -646,6 +719,7 @@ ...@@ -646,6 +719,7 @@
"admin.system.title": "System Info", "admin.system.title": "System Info",
"admin.system.totalRAM": "Total RAM", "admin.system.totalRAM": "Total RAM",
"admin.system.totalRAMHint": "The total amount of RAM available to Wiki.js.", "admin.system.totalRAMHint": "The total amount of RAM available to Wiki.js.",
"admin.system.upgrade": "Upgrade",
"admin.system.workingDirectory": "Working Directory", "admin.system.workingDirectory": "Working Directory",
"admin.system.workingDirectoryHint": "The directory path where Wiki.js is currently running from.", "admin.system.workingDirectoryHint": "The directory path where Wiki.js is currently running from.",
"admin.tags.date": "Created {created} and last updated {updated}.", "admin.tags.date": "Created {created} and last updated {updated}.",
...@@ -665,11 +739,29 @@ ...@@ -665,11 +739,29 @@
"admin.tags.tag": "Tag", "admin.tags.tag": "Tag",
"admin.tags.title": "Tags", "admin.tags.title": "Tags",
"admin.tags.viewLinkedPages": "View Linked Pages", "admin.tags.viewLinkedPages": "View Linked Pages",
"admin.terminal.clear": "Clear",
"admin.terminal.command": "Command",
"admin.terminal.connect": "Connect",
"admin.terminal.connected": "Connected.",
"admin.terminal.connectError": "Connection Error:",
"admin.terminal.connecting": "Connecting to server...",
"admin.terminal.disconnect": "Disconnect",
"admin.terminal.disconnected": "Disconnected.",
"admin.terminal.logs": "Logs",
"admin.terminal.subtitle": "View process logs in real-time",
"admin.terminal.title": "Terminal",
"admin.theme.accentColor": "Accent Color", "admin.theme.accentColor": "Accent Color",
"admin.theme.accentColorHint": "The accent color for elements that need to stand out or grab the user attention.", "admin.theme.accentColorHint": "The accent color for elements that need to stand out or grab the user attention.",
"admin.theme.appearance": "Appearance",
"admin.theme.baseFont": "Base Font",
"admin.theme.baseFontHint": "The font used across the site for the interface.",
"admin.theme.bodyHtmlInjection": "Body HTML Injection", "admin.theme.bodyHtmlInjection": "Body HTML Injection",
"admin.theme.bodyHtmlInjectionHint": "HTML code to be injected just before the closing body tag.", "admin.theme.bodyHtmlInjectionHint": "HTML code to be injected just before the closing body tag.",
"admin.theme.codeInjection": "Code Injection", "admin.theme.codeInjection": "Code Injection",
"admin.theme.contentFont": "Content Font",
"admin.theme.contentFontHint": "The font used specifically for page content.",
"admin.theme.contentWidth": "Content Width",
"admin.theme.contentWidthHint": "Should the content use all available viewport space or stay centered.",
"admin.theme.cssOverride": "CSS Override", "admin.theme.cssOverride": "CSS Override",
"admin.theme.cssOverrideHint": "CSS code to inject after system default CSS. Injecting too much CSS code can result in poor page load performance! CSS will automatically be minified.", "admin.theme.cssOverrideHint": "CSS code to inject after system default CSS. Injecting too much CSS code can result in poor page load performance! CSS will automatically be minified.",
"admin.theme.cssOverrideWarning": "{caution} When adding styles for page content, you must scope them to the {cssClass} class. Omitting this could break the layout of the editor!", "admin.theme.cssOverrideWarning": "{caution} When adding styles for page content, you must scope them to the {cssClass} class. Omitting this could break the layout of the editor!",
...@@ -680,10 +772,11 @@ ...@@ -680,10 +772,11 @@
"admin.theme.downloadDownload": "Download", "admin.theme.downloadDownload": "Download",
"admin.theme.downloadName": "Name", "admin.theme.downloadName": "Name",
"admin.theme.downloadThemes": "Download Themes", "admin.theme.downloadThemes": "Download Themes",
"admin.theme.headHtmlInjection": "Head HTML Injection", "admin.theme.fonts": "Fonts",
"admin.theme.headHtmlInjectionHint": "HTML code to be injected just before the closing head tag. Usually for script tags.",
"admin.theme.headerColor": "Header Color", "admin.theme.headerColor": "Header Color",
"admin.theme.headerColorHint": "The background color for the site top header. Does not apply to the administration area.", "admin.theme.headerColorHint": "The background color for the site top header. Does not apply to the administration area.",
"admin.theme.headHtmlInjection": "Head HTML Injection",
"admin.theme.headHtmlInjectionHint": "HTML code to be injected just before the closing head tag. Usually for script tags.",
"admin.theme.iconset": "Icon Set", "admin.theme.iconset": "Icon Set",
"admin.theme.iconsetHint": "Set of icons to use for the sidebar navigation.", "admin.theme.iconsetHint": "Set of icons to use for the sidebar navigation.",
"admin.theme.layout": "Layout", "admin.theme.layout": "Layout",
...@@ -712,11 +805,12 @@ ...@@ -712,11 +805,12 @@
"admin.theme.tocPositionHint": "On which side should the Table of Contents sidebar be displayed for content pages.", "admin.theme.tocPositionHint": "On which side should the Table of Contents sidebar be displayed for content pages.",
"admin.users.active": "Active", "admin.users.active": "Active",
"admin.users.activity": "Activity", "admin.users.activity": "Activity",
"admin.users.appearance": "Site Appearance",
"admin.users.assignGroup": "Assign Group", "admin.users.assignGroup": "Assign Group",
"admin.users.auth": "Authentication", "admin.users.auth": "Authentication",
"admin.users.authentication": "Authentication",
"admin.users.authProvider": "Provider", "admin.users.authProvider": "Provider",
"admin.users.authProviderId": "Provider Id", "admin.users.authProviderId": "Provider Id",
"admin.users.authentication": "Authentication",
"admin.users.ban": "Ban User", "admin.users.ban": "Ban User",
"admin.users.banHint": "Block the user from signing in and invalidate any active sessions.", "admin.users.banHint": "Block the user from signing in and invalidate any active sessions.",
"admin.users.banned": "Banned", "admin.users.banned": "Banned",
...@@ -724,10 +818,10 @@ ...@@ -724,10 +818,10 @@
"admin.users.changePassword": "Change Password", "admin.users.changePassword": "Change Password",
"admin.users.changePasswordHint": "Change the user password. Note that the current password cannot be recovered.", "admin.users.changePasswordHint": "Change the user password. Note that the current password cannot be recovered.",
"admin.users.create": "Create User", "admin.users.create": "Create User",
"admin.users.createdAt": "Created {date}",
"admin.users.createInvalidData": "Cannot create user as some fields are invalid or missing.", "admin.users.createInvalidData": "Cannot create user as some fields are invalid or missing.",
"admin.users.createKeepOpened": "Keep dialog opened after create", "admin.users.createKeepOpened": "Keep dialog opened after create",
"admin.users.createSuccess": "User created successfully!", "admin.users.createSuccess": "User created successfully!",
"admin.users.createdAt": "Created {date}",
"admin.users.darkMode": "Dark Mode", "admin.users.darkMode": "Dark Mode",
"admin.users.darkModeHint": "Display the user interface using dark mode.", "admin.users.darkModeHint": "Display the user interface using dark mode.",
"admin.users.dateFormat": "Date Format", "admin.users.dateFormat": "Date Format",
...@@ -748,8 +842,8 @@ ...@@ -748,8 +842,8 @@
"admin.users.groupAlreadyAssigned": "User is already assigned to this group.", "admin.users.groupAlreadyAssigned": "User is already assigned to this group.",
"admin.users.groupAssign": "Assign", "admin.users.groupAssign": "Assign",
"admin.users.groupAssignNotice": "Note that you cannot assign users to the Administrators or Guests groups from this panel.", "admin.users.groupAssignNotice": "Note that you cannot assign users to the Administrators or Guests groups from this panel.",
"admin.users.groupSelected": "Assign to {group}",
"admin.users.groups": "Groups", "admin.users.groups": "Groups",
"admin.users.groupSelected": "Assign to {group}",
"admin.users.groupsMissing": "You must assign the user to at least 1 group.", "admin.users.groupsMissing": "You must assign the user to at least 1 group.",
"admin.users.groupsSelected": "Assign to {count} groups", "admin.users.groupsSelected": "Assign to {count} groups",
"admin.users.id": "ID", "admin.users.id": "ID",
...@@ -788,6 +882,8 @@ ...@@ -788,6 +882,8 @@
"admin.users.passwordTooShort": "Password is too short.", "admin.users.passwordTooShort": "Password is too short.",
"admin.users.preferences": "User Preferences", "admin.users.preferences": "User Preferences",
"admin.users.profile": "User Profile", "admin.users.profile": "User Profile",
"admin.users.pronouns": "Pronouns",
"admin.users.pronounsHint": "The pronouns used to address this user.",
"admin.users.pwdAuthActive": "Can Use Password Authentication", "admin.users.pwdAuthActive": "Can Use Password Authentication",
"admin.users.pwdAuthActiveHint": "Whether the user can login using the password authentication.", "admin.users.pwdAuthActiveHint": "Whether the user can login using the password authentication.",
"admin.users.pwdAuthRestrict": "Restrict Password Authentication", "admin.users.pwdAuthRestrict": "Restrict Password Authentication",
...@@ -844,8 +940,17 @@ ...@@ -844,8 +940,17 @@
"admin.utilities.cacheTitle": "Flush Cache", "admin.utilities.cacheTitle": "Flush Cache",
"admin.utilities.contentSubtitle": "Various tools for pages", "admin.utilities.contentSubtitle": "Various tools for pages",
"admin.utilities.contentTitle": "Content", "admin.utilities.contentTitle": "Content",
"admin.utilities.disconnectWS": "Disconnect WebSocket Sessions",
"admin.utilities.disconnectWSHint": "Force all active websocket connections to be closed.",
"admin.utilities.disconnectWSSuccess": "All active websocket connections have been terminated.",
"admin.utilities.export": "Export",
"admin.utilities.exportHint": "Export content to tarball for backup / migration.",
"admin.utilities.flushCache": "Flush Cache",
"admin.utilities.flushCacheHint": "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.",
"admin.utilities.graphEndpointSubtitle": "Change the GraphQL endpoint for Wiki.js", "admin.utilities.graphEndpointSubtitle": "Change the GraphQL endpoint for Wiki.js",
"admin.utilities.graphEndpointTitle": "GraphQL Endpoint", "admin.utilities.graphEndpointTitle": "GraphQL Endpoint",
"admin.utilities.import": "Import",
"admin.utilities.importHint": "Import content from a tarball backup or a 2.X backup.",
"admin.utilities.importv1Subtitle": "Migrate data from a previous 1.x installation", "admin.utilities.importv1Subtitle": "Migrate data from a previous 1.x installation",
"admin.utilities.importv1Title": "Import from Wiki.js 1.x", "admin.utilities.importv1Title": "Import from Wiki.js 1.x",
"admin.utilities.invalidAuthCertificates": "Invalidate Authentication Certificates", "admin.utilities.invalidAuthCertificates": "Invalidate Authentication Certificates",
...@@ -882,13 +987,13 @@ ...@@ -882,13 +987,13 @@
"admin.webhooks.eventNewComment": "Post a new comment", "admin.webhooks.eventNewComment": "Post a new comment",
"admin.webhooks.eventRenameAsset": "Rename / move an asset", "admin.webhooks.eventRenameAsset": "Rename / move an asset",
"admin.webhooks.eventRenamePage": "Rename / move a page", "admin.webhooks.eventRenamePage": "Rename / move a page",
"admin.webhooks.events": "Events",
"admin.webhooks.eventsMissing": "You must select at least 1 event.",
"admin.webhooks.eventsSelected": "No event selected | 1 event selected | {count} events selected",
"admin.webhooks.eventUploadAsset": "Upload a new asset", "admin.webhooks.eventUploadAsset": "Upload a new asset",
"admin.webhooks.eventUserJoin": "Create / register a new user", "admin.webhooks.eventUserJoin": "Create / register a new user",
"admin.webhooks.eventUserLogin": "User logins", "admin.webhooks.eventUserLogin": "User logins",
"admin.webhooks.eventUserLogout": "User logouts", "admin.webhooks.eventUserLogout": "User logouts",
"admin.webhooks.events": "Events",
"admin.webhooks.eventsMissing": "You must select at least 1 event.",
"admin.webhooks.eventsSelected": "No event selected | 1 event selected | {count} events selected",
"admin.webhooks.includeContent": "Include Content", "admin.webhooks.includeContent": "Include Content",
"admin.webhooks.includeContentHint": "Should the payload include content (e.g. the full page body). Make sure that your remote endpoint can accept large payloads!", "admin.webhooks.includeContentHint": "Should the payload include content (e.g. the full page body). Make sure that your remote endpoint can accept large payloads!",
"admin.webhooks.includeMetadata": "Include Metadata", "admin.webhooks.includeMetadata": "Include Metadata",
...@@ -917,36 +1022,35 @@ ...@@ -917,36 +1022,35 @@
"admin.webhooks.urlMissing": "The URL is missing or is not valid.", "admin.webhooks.urlMissing": "The URL is missing or is not valid.",
"auth.actions.login": "Log In", "auth.actions.login": "Log In",
"auth.actions.register": "Register", "auth.actions.register": "Register",
"auth.changePwd.currentPassword": "Current Password",
"auth.changePwd.instructions": "You must choose a new password:", "auth.changePwd.instructions": "You must choose a new password:",
"auth.changePwd.loading": "Changing password...", "auth.changePwd.loading": "Changing password...",
"auth.changePwd.currentPassword": "Current Password",
"auth.changePwd.newPassword": "New Password", "auth.changePwd.newPassword": "New Password",
"auth.changePwd.newPasswordVerify": "Verify New Password", "auth.changePwd.newPasswordVerify": "Verify New Password",
"auth.changePwd.proceed": "Change Password", "auth.changePwd.proceed": "Change Password",
"auth.changePwd.subtitle": "Choose a new password", "auth.changePwd.subtitle": "Choose a new password",
"auth.enterCredentials": "Enter your credentials", "auth.enterCredentials": "Enter your credentials",
"auth.errors.forgotPassword": "Missing or invalid email address.",
"auth.errors.invalidEmail": "Email is invalid.",
"auth.errors.invalidLogin": "Invalid Login", "auth.errors.invalidLogin": "Invalid Login",
"auth.errors.invalidLoginMsg": "The email or password is invalid.", "auth.errors.invalidLoginMsg": "The email or password is invalid.",
"auth.errors.invalidName": "Name is invalid.",
"auth.errors.invalidUserEmail": "Invalid User Email", "auth.errors.invalidUserEmail": "Invalid User Email",
"auth.errors.login": "Missing or invalid login fields.",
"auth.errors.loginError": "Login error", "auth.errors.loginError": "Login error",
"auth.errors.notYetAuthorized": "You have not been authorized to login to this site yet.",
"auth.errors.tooManyAttempts": "Too many attempts!",
"auth.errors.tooManyAttemptsMsg": "You've made too many failed attempts in a short period of time, please try again {time}.",
"auth.errors.userNotFound": "User not found",
"auth.errors.missingName": "Name is missing.",
"auth.errors.invalidName": "Name is invalid.",
"auth.errors.missingEmail": "Email is missing.", "auth.errors.missingEmail": "Email is missing.",
"auth.errors.invalidEmail": "Email is invalid.", "auth.errors.missingName": "Name is missing.",
"auth.errors.missingPassword": "Password is missing.", "auth.errors.missingPassword": "Password is missing.",
"auth.errors.passwordTooShort": "Password is too short.", "auth.errors.missingUsername": "Username is missing.",
"auth.errors.missingVerifyPassword": "Password Verification is missing.", "auth.errors.missingVerifyPassword": "Password Verification is missing.",
"auth.errors.notYetAuthorized": "You have not been authorized to login to this site yet.",
"auth.errors.passwordsNotMatch": "Passwords do not match.", "auth.errors.passwordsNotMatch": "Passwords do not match.",
"auth.errors.missingUsername": "Username is missing.", "auth.errors.passwordTooShort": "Password is too short.",
"auth.errors.register": "One or more fields are invalid.", "auth.errors.register": "One or more fields are invalid.",
"auth.errors.login": "Missing or invalid login fields.",
"auth.errors.forgotPassword": "Missing or invalid email address.",
"auth.errors.tfaMissing": "Missing or incomplete security code.", "auth.errors.tfaMissing": "Missing or incomplete security code.",
"auth.login.title": "Login", "auth.errors.tooManyAttempts": "Too many attempts!",
"auth.errors.tooManyAttemptsMsg": "You've made too many failed attempts in a short period of time, please try again {time}.",
"auth.errors.userNotFound": "User not found",
"auth.fields.email": "Email Address", "auth.fields.email": "Email Address",
"auth.fields.emailUser": "Email / Username", "auth.fields.emailUser": "Email / Username",
"auth.fields.name": "Name", "auth.fields.name": "Name",
...@@ -963,6 +1067,7 @@ ...@@ -963,6 +1067,7 @@
"auth.invalidEmail": "Email address is invalid.", "auth.invalidEmail": "Email address is invalid.",
"auth.invalidEmailUsername": "Enter a valid email / username.", "auth.invalidEmailUsername": "Enter a valid email / username.",
"auth.invalidPassword": "Enter a valid password.", "auth.invalidPassword": "Enter a valid password.",
"auth.login.title": "Login",
"auth.loginRequired": "Login required", "auth.loginRequired": "Login required",
"auth.loginSuccess": "Login Successful! Redirecting...", "auth.loginSuccess": "Login Successful! Redirecting...",
"auth.loginUsingStrategy": "Login using {strategy}", "auth.loginUsingStrategy": "Login using {strategy}",
...@@ -976,10 +1081,10 @@ ...@@ -976,10 +1081,10 @@
"auth.passwordTooShort": "Password is too short.", "auth.passwordTooShort": "Password is too short.",
"auth.pleaseWait": "Please wait", "auth.pleaseWait": "Please wait",
"auth.registerCheckEmail": "Check your emails to activate your account.", "auth.registerCheckEmail": "Check your emails to activate your account.",
"auth.registering": "Creating account...",
"auth.registerSubTitle": "Fill-in the form below to create an account.", "auth.registerSubTitle": "Fill-in the form below to create an account.",
"auth.registerSuccess": "Account created successfully!", "auth.registerSuccess": "Account created successfully!",
"auth.registerTitle": "Create an account", "auth.registerTitle": "Create an account",
"auth.registering": "Creating account...",
"auth.selectAuthProvider": "Sign in with", "auth.selectAuthProvider": "Sign in with",
"auth.sendResetPassword": "Reset Password", "auth.sendResetPassword": "Reset Password",
"auth.signingIn": "Signing In...", "auth.signingIn": "Signing In...",
...@@ -1052,8 +1157,8 @@ ...@@ -1052,8 +1157,8 @@
"common.comments.newPlaceholder": "Write a new comment...", "common.comments.newPlaceholder": "Write a new comment...",
"common.comments.none": "No comments yet.", "common.comments.none": "No comments yet.",
"common.comments.postComment": "Post Comment", "common.comments.postComment": "Post Comment",
"common.comments.postSuccess": "New comment posted successfully.",
"common.comments.postingAs": "Posting as {name}", "common.comments.postingAs": "Posting as {name}",
"common.comments.postSuccess": "New comment posted successfully.",
"common.comments.sdTitle": "Talk", "common.comments.sdTitle": "Talk",
"common.comments.title": "Comments", "common.comments.title": "Comments",
"common.comments.updateComment": "Update Comment", "common.comments.updateComment": "Update Comment",
...@@ -1065,11 +1170,21 @@ ...@@ -1065,11 +1170,21 @@
"common.duration.minutes": "Minute(s)", "common.duration.minutes": "Minute(s)",
"common.duration.months": "Month(s)", "common.duration.months": "Month(s)",
"common.duration.years": "Year(s)", "common.duration.years": "Year(s)",
"common.error.generic.hint": "Oops, something went wrong...",
"common.error.generic.title": "Unexpected Error",
"common.error.notfound.hint": "That page doesn't exist or is not available.",
"common.error.notfound.title": "Not Found",
"common.error.title": "Error",
"common.error.unauthorized.hint": "You don't have the required permissions to access this page.",
"common.error.unauthorized.title": "Unauthorized",
"common.error.unknownsite.hint": "There's no wiki site at this host.",
"common.error.unknownsite.title": "Unknown Site",
"common.error.unexpected": "An unexpected error occurred.", "common.error.unexpected": "An unexpected error occurred.",
"common.field.createdOn": "Created On", "common.field.createdOn": "Created On",
"common.field.id": "ID", "common.field.id": "ID",
"common.field.lastUpdated": "Last Updated", "common.field.lastUpdated": "Last Updated",
"common.field.name": "Name", "common.field.name": "Name",
"common.field.task": "Task",
"common.footer.copyright": "© {year} {company}. All rights reserved.", "common.footer.copyright": "© {year} {company}. All rights reserved.",
"common.footer.license": "Content is available under the {license}, by {company}.", "common.footer.license": "Content is available under the {license}, by {company}.",
"common.footer.poweredBy": "Powered by", "common.footer.poweredBy": "Powered by",
...@@ -1150,11 +1265,11 @@ ...@@ -1150,11 +1265,11 @@
"common.pageSelector.pages": "Pages", "common.pageSelector.pages": "Pages",
"common.pageSelector.selectTitle": "Select a Page", "common.pageSelector.selectTitle": "Select a Page",
"common.pageSelector.virtualFolders": "Virtual Folders", "common.pageSelector.virtualFolders": "Virtual Folders",
"common.password.good": "Good",
"common.password.average": "Average", "common.password.average": "Average",
"common.password.good": "Good",
"common.password.poor": "Poor",
"common.password.strong": "Strong", "common.password.strong": "Strong",
"common.password.weak": "Weak", "common.password.weak": "Weak",
"common.password.poor": "Poor",
"common.sidebar.browse": "Browse", "common.sidebar.browse": "Browse",
"common.sidebar.currentDirectory": "Current Directory", "common.sidebar.currentDirectory": "Current Directory",
"common.sidebar.mainMenu": "Main Menu", "common.sidebar.mainMenu": "Main Menu",
...@@ -1188,8 +1303,8 @@ ...@@ -1188,8 +1303,8 @@
"editor.assets.folderNameNamingRulesLink": "naming rules", "editor.assets.folderNameNamingRulesLink": "naming rules",
"editor.assets.headerActions": "Actions", "editor.assets.headerActions": "Actions",
"editor.assets.headerAdded": "Added", "editor.assets.headerAdded": "Added",
"editor.assets.headerFileSize": "File Size",
"editor.assets.headerFilename": "Filename", "editor.assets.headerFilename": "Filename",
"editor.assets.headerFileSize": "File Size",
"editor.assets.headerId": "ID", "editor.assets.headerId": "ID",
"editor.assets.headerType": "Type", "editor.assets.headerType": "Type",
"editor.assets.imageAlign": "Image Alignment", "editor.assets.imageAlign": "Image Alignment",
...@@ -1315,9 +1430,9 @@ ...@@ -1315,9 +1430,9 @@
"editor.props.pageProperties": "Page Properties", "editor.props.pageProperties": "Page Properties",
"editor.props.password": "Password", "editor.props.password": "Password",
"editor.props.passwordHint": "The page must be published and the user must have read access rights.", "editor.props.passwordHint": "The page must be published and the user must have read access rights.",
"editor.props.publishState": "Publishing State",
"editor.props.published": "Published", "editor.props.published": "Published",
"editor.props.publishedHint": "Visible to all users with read access.", "editor.props.publishedHint": "Visible to all users with read access.",
"editor.props.publishState": "Publishing State",
"editor.props.relationAdd": "Add Relation...", "editor.props.relationAdd": "Add Relation...",
"editor.props.relationAddHint": "Add links to other pages in the footer (e.g. as part of a series of articles)", "editor.props.relationAddHint": "Add links to other pages in the footer (e.g. as part of a series of articles)",
"editor.props.relations": "Relations", "editor.props.relations": "Relations",
...@@ -1353,24 +1468,13 @@ ...@@ -1353,24 +1468,13 @@
"history.restore.confirmText": "Are you sure you want to restore this page content as it was on {date}? This version will be copied on top of the current history. As such, newer versions will still be preserved.", "history.restore.confirmText": "Are you sure you want to restore this page content as it was on {date}? This version will be copied on top of the current history. As such, newer versions will still be preserved.",
"history.restore.confirmTitle": "Restore page version?", "history.restore.confirmTitle": "Restore page version?",
"history.restore.success": "Page version restored succesfully!", "history.restore.success": "Page version restored succesfully!",
"profile.activity.commentsPosted": "Comments posted", "profile.activity": "Activity",
"profile.activity.joinedOn": "Joined on",
"profile.activity.lastLoginOn": "Last login on",
"profile.activity.lastUpdatedOn": "Profile last updated on",
"profile.activity.pagesCreated": "Pages created",
"profile.activity.title": "Activity",
"profile.appearance": "Site Appearance", "profile.appearance": "Site Appearance",
"profile.appearanceDark": "Dark", "profile.appearanceDark": "Dark",
"profile.appearanceDefault": "Site Default", "profile.appearanceDefault": "Site Default",
"profile.appearanceHint": "Use the light or dark theme.",
"profile.appearanceLight": "Light", "profile.appearanceLight": "Light",
"profile.auth.changePassSuccess": "Password changed successfully.", "profile.auth": "Authentication",
"profile.auth.changePassword": "Change Password",
"profile.auth.currentPassword": "Current Password",
"profile.auth.newPassword": "New Password",
"profile.auth.provider": "Provider",
"profile.auth.title": "Authentication",
"profile.auth.verifyPassword": "Confirm New Password",
"profile.comments.title": "Comments",
"profile.darkMode": "Dark Mode", "profile.darkMode": "Dark Mode",
"profile.darkModeHint": "Change the appareance of the site to a dark theme.", "profile.darkModeHint": "Change the appareance of the site to a dark theme.",
"profile.dateFormat": "Date Format", "profile.dateFormat": "Date Format",
...@@ -1379,7 +1483,8 @@ ...@@ -1379,7 +1483,8 @@
"profile.displayNameHint": "Your full name; shown when authoring content (e.g. pages, comments, etc.).", "profile.displayNameHint": "Your full name; shown when authoring content (e.g. pages, comments, etc.).",
"profile.email": "Email Address", "profile.email": "Email Address",
"profile.emailHint": "The email address used for login.", "profile.emailHint": "The email address used for login.",
"profile.groups.title": "Groups", "profile.groups": "Groups",
"profile.groupsLoadingFailed": "Failed to load groups.",
"profile.jobTitle": "Job Title", "profile.jobTitle": "Job Title",
"profile.jobTitleHint": "Your position in your organization; shown on your profile page.", "profile.jobTitleHint": "Your position in your organization; shown on your profile page.",
"profile.localeDefault": "Locale Default", "profile.localeDefault": "Locale Default",
...@@ -1398,6 +1503,9 @@ ...@@ -1398,6 +1503,9 @@
"profile.pronouns": "Pronouns", "profile.pronouns": "Pronouns",
"profile.pronounsHint": "Let people know which pronouns should they use when referring to you.", "profile.pronounsHint": "Let people know which pronouns should they use when referring to you.",
"profile.save.success": "Profile saved successfully.", "profile.save.success": "Profile saved successfully.",
"profile.saveFailed": "Failed to save profile changes.",
"profile.saveSuccess": "Profile saved successfully.",
"profile.saving": "Saving profile...",
"profile.subtitle": "My personal info", "profile.subtitle": "My personal info",
"profile.timeFormat": "Time Format", "profile.timeFormat": "Time Format",
"profile.timeFormat12h": "12 hour", "profile.timeFormat12h": "12 hour",
...@@ -1414,8 +1522,8 @@ ...@@ -1414,8 +1522,8 @@
"tags.noResults": "Couldn't find any page with the selected tags.", "tags.noResults": "Couldn't find any page with the selected tags.",
"tags.noResultsWithFilter": "Couldn't find any page matching the current filtering options.", "tags.noResultsWithFilter": "Couldn't find any page matching the current filtering options.",
"tags.orderBy": "Order By", "tags.orderBy": "Order By",
"tags.orderByField.ID": "ID",
"tags.orderByField.creationDate": "Creation Date", "tags.orderByField.creationDate": "Creation Date",
"tags.orderByField.ID": "ID",
"tags.orderByField.lastModified": "Last Modified", "tags.orderByField.lastModified": "Last Modified",
"tags.orderByField.path": "Path", "tags.orderByField.path": "Path",
"tags.orderByField.title": "Title", "tags.orderByField.title": "Title",
...@@ -1424,125 +1532,24 @@ ...@@ -1424,125 +1532,24 @@
"tags.searchWithinResultsPlaceholder": "Search within results...", "tags.searchWithinResultsPlaceholder": "Search within results...",
"tags.selectOneMoreTags": "Select one or more tags", "tags.selectOneMoreTags": "Select one or more tags",
"tags.selectOneMoreTagsHint": "Select one or more tags on the left.", "tags.selectOneMoreTagsHint": "Select one or more tags on the left.",
"admin.general.sitemapHint": "Make a sitemap.xml available to search engines with all pages accessible to guests.",
"admin.groups.usersNone": "This group doesn't have any user yet.",
"admin.mail.smtpName": "Client Identifying Name",
"admin.mail.smtpNameHint": "An optional name to send to the SMTP server to identify your mailer. Leave empty to use server hostname. For Google Workspace customers, this should be your main domain name.",
"admin.general.footerExtra": "Additional Footer Text",
"admin.general.footerExtraHint": "Optionally add more content to the footer, such as additional copyright terms or mandatory regulatory info.",
"admin.general.urlHandling": "URL Handling",
"admin.general.pageExtensions": "Page Extensions",
"admin.general.pageExtensionsHint": "A comma-separated list of URL extensions that will be treated as pages. For example, adding md will treat /foobar.md the same as /foobar.",
"admin.theme.appearance": "Appearance",
"admin.theme.fonts": "Fonts",
"admin.theme.baseFont": "Base Font",
"admin.theme.baseFontHint": "The font used across the site for the interface.",
"admin.theme.contentFont": "Content Font",
"admin.theme.contentFontHint": "The font used specifically for page content.",
"admin.theme.contentWidth": "Content Width",
"admin.theme.contentWidthHint": "Should the content use all available viewport space or stay centered.",
"admin.utilities.export": "Export",
"admin.utilities.import": "Import",
"admin.utilities.exportHint": "Export content to tarball for backup / migration.",
"admin.utilities.importHint": "Import content from a tarball backup or a 2.X backup.",
"admin.utilities.flushCache": "Flush Cache",
"admin.utilities.flushCacheHint": "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.",
"admin.auth.enabledForced": "This strategy cannot be disabled.",
"admin.auth.enabled": "Enabled",
"admin.auth.enabledHint": "Should this strategy be available to sites for login.",
"admin.auth.vendor": "Vendor",
"admin.auth.vendorWebsite": "Website",
"admin.auth.status": "Status",
"admin.system.checkingForUpdates": "Checking for Updates...",
"admin.system.upgrade": "Upgrade",
"admin.system.checkForUpdates": "Check",
"admin.system.checkUpdate": "Check / Upgrade",
"admin.api.none": "There are no API keys yet.",
"admin.api.groupsMissing": "You must select at least 1 group for this key.",
"admin.api.nameInvalidChars": "Key name has invalid characters.",
"admin.api.nameMissing": "Key name is missing.",
"admin.api.permissionGroups": "Group Permissions",
"admin.api.groupSelected": "Use {group} group permissions",
"admin.api.groupsSelected": "Use permissions from {count} groups",
"admin.api.createInvalidData": "Some fields are missing or have invalid data.",
"admin.api.copyKeyTitle": "Copy API Key",
"admin.api.key": "API Key",
"admin.api.createSuccess": "API Key created successfully.",
"admin.api.revoked": "Revoked",
"admin.api.revokedHint": "This key has been revoked and can no longer be used.",
"admin.storage.setupRequired": "Setup required",
"admin.blocks.title": "Content Blocks",
"common.error.title": "Error",
"common.error.unauthorized.title": "Unauthorized",
"common.error.unauthorized.hint": "You don't have the required permissions to access this page.",
"common.error.generic.title": "Unexpected Error",
"common.error.generic.hint": "Oops, something went wrong...",
"common.error.notfound.title": "Not Found",
"common.error.notfound.hint": "That page doesn't exist or is not available.",
"welcome.title": "Welcome to Wiki.js!",
"welcome.subtitle": "Let's get started...",
"welcome.createHome": "Create the homepage",
"welcome.admin": "Administration Area", "welcome.admin": "Administration Area",
"admin.terminal.title": "Terminal", "welcome.createHome": "Create the homepage",
"admin.terminal.subtitle": "View process logs in real-time", "welcome.subtitle": "Let's get started...",
"admin.terminal.command": "Command", "welcome.title": "Welcome to Wiki.js!",
"admin.terminal.logs": "Logs", "profile.avatar": "Avatar",
"admin.terminal.connect": "Connect", "profile.uploadNewAvatar": "Upload New Image",
"admin.terminal.disconnect": "Disconnect", "profile.avatarUploadTitle": "Upload your user profile picture.",
"admin.terminal.clear": "Clear", "profile.avatarUploadHint": "For best results, use a 180x180 image of type JPG or PNG.",
"admin.terminal.connecting": "Connecting to server...", "profile.groupsInfo": "You're currently part of the following groups:",
"admin.terminal.connected": "Connected.", "profile.groupsNone": "You're not part of any group.",
"admin.terminal.disconnected": "Disconnected.", "profile.authInfo": "Your account is associated with the following authentication methods:",
"admin.terminal.connectError": "Connection Error:", "profile.authSetTfa": "Set 2FA",
"admin.utilities.disconnectWS": "Disconnect WebSocket Sessions", "profile.authModifyTfa": "Modify 2FA",
"admin.utilities.disconnectWSHint": "Force all active websocket connections to be closed.", "profile.authChangePassword": "Change Password",
"admin.utilities.disconnectWSSuccess": "All active websocket connections have been terminated.", "profile.authLoadingFailed": "Failed to load authentication methods.",
"admin.login.bgUploadSuccess": "Login background image uploaded successfully.", "profile.notifications": "Notifications",
"admin.login.saveSuccess": "Login configuration saved successfully.", "profile.avatarUploadSuccess": "Profile picture uploaded successfully.",
"profile.appearanceHint": "Use the light or dark theme.", "profile.avatarUploadFailed": "Failed to upload user profile picture.",
"profile.saving": "Saving profile...", "profile.avatarClearSuccess": "Profile picture cleared successfully.",
"profile.saveSuccess": "Profile saved successfully.", "profile.avatarClearFailed": "Failed to clear profile picture."
"profile.saveFailed": "Failed to save profile changes.",
"admin.users.pronouns": "Pronouns",
"admin.users.pronounsHint": "The pronouns used to address this user.",
"admin.users.appearance": "Site Appearance",
"admin.scheduler.title": "Scheduler",
"admin.scheduler.subtitle": "View scheduled and completed jobs",
"admin.scheduler.active": "Active",
"admin.scheduler.completed": "Completed",
"admin.scheduler.scheduled": "Scheduled",
"admin.scheduler.activeNone": "There are no active jobs at the moment.",
"admin.scheduler.completedNone": "There are no recently completed job to display.",
"admin.scheduler.scheduledNone": "There are no scheduled jobs at the moment.",
"admin.scheduler.cron": "Cron",
"admin.scheduler.createdBy": "by instance {instance}",
"admin.scheduler.upcoming": "Upcoming",
"admin.scheduler.failed": "Failed",
"admin.scheduler.type": "Type",
"admin.scheduler.createdAt": "Created",
"admin.scheduler.updatedAt": "Last Updated",
"common.field.task": "Task",
"admin.scheduler.upcomingNone": "There are no upcoming job for the moment.",
"admin.scheduler.failedNone": "There are no recently failed job to display.",
"admin.scheduler.waitUntil": "Start",
"admin.scheduler.attempt": "Attempt",
"admin.scheduler.useWorker": "Execution Mode",
"admin.scheduler.schedule": "Schedule",
"admin.scheduler.state": "State",
"admin.scheduler.startedAt": "Started",
"admin.scheduler.result": "Result",
"admin.scheduler.completedIn": "Completed in {duration}",
"admin.scheduler.pending": "Pending",
"admin.scheduler.error": "Error",
"admin.scheduler.interrupted": "Interrupted",
"admin.instances.title": "Instances",
"admin.instances.subtitle": "View a list of active instances",
"admin.instances.lastSeen": "Last Seen",
"admin.instances.firstSeen": "First Seen",
"admin.instances.activeListeners": "Active Listeners",
"admin.instances.activeConnections": "Active Connections",
"admin.scheduler.cancelJob": "Cancel Job",
"admin.scheduler.cancelJobSuccess": "Job cancelled successfully.",
"admin.scheduler.retryJob": "Retry Job",
"admin.scheduler.retryJobSuccess": "Job has been rescheduled and will execute shortly."
} }
...@@ -11,6 +11,7 @@ q-layout(view='hHh Lpr lff') ...@@ -11,6 +11,7 @@ q-layout(view='hHh Lpr lff')
clickable clickable
:to='`/_profile/` + navItem.key' :to='`/_profile/` + navItem.key'
active-class='is-active' active-class='is-active'
:disabled='navItem.disabled'
v-ripple v-ripple
) )
q-item-section(side) q-item-section(side)
...@@ -21,12 +22,12 @@ q-layout(view='hHh Lpr lff') ...@@ -21,12 +22,12 @@ q-layout(view='hHh Lpr lff')
q-item( q-item(
clickable clickable
v-ripple v-ripple
to='/_profile/me' :to='`/_user/` + userStore.id'
) )
q-item-section(side) q-item-section(side)
q-icon(name='las la-id-card') q-icon(name='las la-id-card')
q-item-section q-item-section
q-item-label View Public Profile q-item-label {{ t('profile.viewPublicProfile') }}
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item( q-item(
clickable clickable
...@@ -36,7 +37,7 @@ q-layout(view='hHh Lpr lff') ...@@ -36,7 +37,7 @@ q-layout(view='hHh Lpr lff')
q-item-section(side) q-item-section(side)
q-icon(name='las la-sign-out-alt', color='negative') q-icon(name='las la-sign-out-alt', color='negative')
q-item-section q-item-section
q-item-label.text-negative Logout q-item-label.text-negative {{ t('common.header.logout') }}
router-view router-view
q-footer q-footer
q-bar.justify-center(dense) q-bar.justify-center(dense)
...@@ -44,13 +45,12 @@ q-layout(view='hHh Lpr lff') ...@@ -44,13 +45,12 @@ q-layout(view='hHh Lpr lff')
</template> </template>
<script setup> <script setup>
import gql from 'graphql-tag'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useMeta, useQuasar } from 'quasar' import { useMeta, useQuasar } from 'quasar'
import { onMounted, reactive, watch } from 'vue' import { onMounted, reactive, watch } from 'vue'
import { useSiteStore } from 'src/stores/site' import { useSiteStore } from 'src/stores/site'
import { useUserStore } from 'src/stores/user'
import HeaderNav from '../components/HeaderNav.vue' import HeaderNav from '../components/HeaderNav.vue'
...@@ -61,62 +61,60 @@ const $q = useQuasar() ...@@ -61,62 +61,60 @@ const $q = useQuasar()
// STORES // STORES
const siteStore = useSiteStore() const siteStore = useSiteStore()
const userStore = useUserStore()
// I18N // I18N
const { t } = useI18n() const { t } = useI18n()
// META
useMeta({
titleTemplate: title => `${title} - ${t('profile.title')} - Wiki.js`
})
// DATA // DATA
const sidenav = [ const sidenav = [
{ {
key: 'info', key: 'info',
label: 'Profile', label: t('profile.title'),
icon: 'las la-user-circle' icon: 'las la-user-circle'
}, },
{ {
key: 'avatar', key: 'avatar',
label: 'Avatar', label: t('profile.avatar'),
icon: 'las la-otter' icon: 'las la-otter'
}, },
{ {
key: 'password', key: 'auth',
label: 'Authentication', label: t('profile.auth'),
icon: 'las la-key' icon: 'las la-key'
}, },
{ {
key: 'groups', key: 'groups',
label: 'Groups', label: t('profile.groups'),
icon: 'las la-users' icon: 'las la-users'
}, },
{ {
key: 'notifications', key: 'notifications',
label: 'Notifications', label: t('profile.notifications'),
icon: 'las la-bell' icon: 'las la-bell',
}, disabled: true
{
key: 'pages',
label: 'My Pages',
icon: 'las la-file-alt'
}, },
// {
// key: 'pages',
// label: 'My Pages',
// icon: 'las la-file-alt',
// disabled: true
// },
{ {
key: 'activity', key: 'activity',
label: 'Activity', label: t('profile.activity'),
icon: 'las la-history' icon: 'las la-history',
disabled: true
} }
] ]
const thumbStyle = {
right: '2px',
borderRadius: '5px',
backgroundColor: '#FFF',
width: '5px',
opacity: 0.5
}
const barStyle = {
backgroundColor: '#000',
width: '9px',
opacity: 0.1
}
</script> </script>
<style lang="scss"> <style lang="scss">
......
<template>
<div class="fullscreen bg-blue text-white text-center q-pa-md flex flex-center">
<div>
<div style="font-size: 30vh">
404
</div>
<div class="text-h2" style="opacity:.4">
Oops. Nothing here...
</div>
<q-btn
class="q-mt-xl"
color="white"
text-color="blue"
unelevated
to="/"
label="Go Home"
no-caps
/>
</div>
</div>
</template>
<script>
export default {
name: 'Error404'
}
</script>
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
.errorpage-hint {{error.hint}} .errorpage-hint {{error.hint}}
.errorpage-actions .errorpage-actions
q-btn( q-btn(
v-if='error.showHomeBtn'
push push
color='primary' color='primary'
label='Go to home' label='Go to home'
...@@ -38,6 +39,10 @@ const actions = { ...@@ -38,6 +39,10 @@ const actions = {
notfound: { notfound: {
code: 404 code: 404
}, },
unknownsite: {
code: 'X!?',
showHomeBtn: false
},
generic: { generic: {
code: '!?0' code: '!?0'
} }
...@@ -62,12 +67,14 @@ useMeta({ ...@@ -62,12 +67,14 @@ useMeta({
const error = computed(() => { const error = computed(() => {
if (route.params.action && actions[route.params.action]) { if (route.params.action && actions[route.params.action]) {
return { return {
showHomeBtn: true,
...actions[route.params.action], ...actions[route.params.action],
title: t(`common.error.${route.params.action}.title`), title: t(`common.error.${route.params.action}.title`),
hint: t(`common.error.${route.params.action}.hint`) hint: t(`common.error.${route.params.action}.hint`)
} }
} else { } else {
return { return {
showHomeBtn: true,
...actions.generic, ...actions.generic,
title: t('common.error.generic.title'), title: t('common.error.generic.title'),
hint: t('common.error.generic.hint') hint: t('common.error.generic.hint')
......
<template>
<div class="fullscreen bg-blue text-white text-center q-pa-md flex flex-center">
<div>
<div style="font-size: 30vh">
404
</div>
<div class="text-h2" style="opacity:.4">
Oops. Nothing here...
</div>
<q-btn
class="q-mt-xl"
color="white"
text-color="blue"
unelevated
to="/"
label="Go Home"
no-caps
/>
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'ErrorNotFound'
})
</script>
<template lang="pug">
q-page.q-py-md(:style-fn='pageStyle')
.text-header {{t('profile.auth')}}
.q-pa-md
.text-body2 {{ t('profile.authInfo') }}
q-list.q-mt-lg(
bordered
separator
)
q-item(
v-for='auth of state.authMethods'
:key='auth.id'
)
q-item-section(avatar)
q-avatar(
color='dark-5'
text-color='white'
rounded
)
q-icon(:name='`img:` + auth.strategyIcon')
q-item-section
strong {{auth.authName}}
template(v-if='auth.strategyKey === `local`')
q-item-section(v-if='auth.config.isTfaSetup', side)
q-btn(
icon='las la-fingerprint'
unelevated
:label='t(`profile.authModifyTfa`)'
color='primary'
@click=''
)
q-item-section(v-else, side)
q-btn(
icon='las la-fingerprint'
unelevated
:label='t(`profile.authSetTfa`)'
color='primary'
@click=''
)
q-item-section(side)
q-btn(
icon='las la-key'
unelevated
:label='t(`profile.authChangePassword`)'
color='primary'
@click=''
)
q-inner-loading(:showing='state.loading > 0')
</template>
<script setup>
import gql from 'graphql-tag'
import { useI18n } from 'vue-i18n'
import { useMeta, useQuasar } from 'quasar'
import { onMounted, reactive } from 'vue'
import { useUserStore } from 'src/stores/user'
// QUASAR
const $q = useQuasar()
// STORES
const userStore = useUserStore()
// I18N
const { t } = useI18n()
// META
useMeta({
title: t('profile.auth')
})
// DATA
const state = reactive({
authMethods: [],
loading: 0
})
// METHODS
function pageStyle (offset, height) {
return {
'min-height': `${height - 100 - offset}px`
}
}
async function fetchAuthMethods () {
state.loading++
try {
const respRaw = await APOLLO_CLIENT.query({
query: gql`
query getUserProfileAuthMethods (
$id: UUID!
) {
userById (
id: $id
) {
id
auth {
authId
authName
strategyKey
strategyIcon
config
}
}
}
`,
variables: {
id: userStore.id
},
fetchPolicy: 'network-only'
})
state.authMethods = respRaw.data?.userById?.auth ?? []
} catch (err) {
$q.notify({
type: 'negative',
message: t('profile.authLoadingFailed'),
caption: err.message
})
}
state.loading--
}
// MOUNTED
onMounted(() => {
fetchAuthMethods()
})
</script>
<template lang="pug">
q-page.q-py-md(:style-fn='pageStyle')
.text-header {{t('profile.avatar')}}
.row.q-gutter-lg.q-mt-xl.align-center
.col.text-center
q-avatar.profile-avatar-circ(
size='180px'
:color='userStore.hasAvatar ? `dark-1` : `primary`'
text-color='white'
:class='userStore.hasAvatar ? `is-image` : ``'
)
img(
v-if='userStore.hasAvatar',
:src='`/_user/` + userStore.id + `/avatar?` + state.assetTimestamp'
)
q-icon(
v-else,
name='las la-user'
)
.col
.text-body1 {{ t('profile.avatarUploadTitle') }}
.text-caption {{ t('profile.avatarUploadHint') }}
.q-mt-md
q-btn(
icon='las la-upload'
unelevated
:label='t(`profile.uploadNewAvatar`)'
color='primary'
@click='uploadImage'
)
.q-mt-md
q-btn.q-mr-sm(
icon='las la-times'
outline
:label='t(`common.actions.clear`)'
color='primary'
@click='clearImage'
:disable='!userStore.hasAvatar'
)
q-inner-loading(:showing='state.loading > 0')
</template>
<script setup>
import gql from 'graphql-tag'
import { useI18n } from 'vue-i18n'
import { useMeta, useQuasar } from 'quasar'
import { reactive } from 'vue'
import { useUserStore } from 'src/stores/user'
// QUASAR
const $q = useQuasar()
// STORES
const userStore = useUserStore()
// I18N
const { t } = useI18n()
// META
useMeta({
title: t('profile.avatar')
})
// DATA
const state = reactive({
loading: 0,
assetTimestamp: (new Date()).toISOString()
})
// METHODS
function pageStyle (offset, height) {
return {
'min-height': `${height - 100 - offset}px`
}
}
async function uploadImage () {
const input = document.createElement('input')
input.type = 'file'
input.onchange = async e => {
state.loading++
try {
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation uploadUserAvatar (
$id: UUID!
$image: Upload!
) {
uploadUserAvatar (
id: $id
image: $image
) {
operation {
succeeded
message
}
}
}
`,
variables: {
id: userStore.id,
image: e.target.files[0]
}
})
if (resp?.data?.uploadUserAvatar?.operation?.succeeded) {
$q.notify({
type: 'positive',
message: t('profile.avatarUploadSuccess')
})
state.assetTimestamp = (new Date()).toISOString()
userStore.$patch({
hasAvatar: true
})
} else {
throw new Error(resp?.data?.uploadUserAvatar?.operation?.message || 'An unexpected error occured.')
}
} catch (err) {
$q.notify({
type: 'negative',
message: t('profile.avatarUploadFailed'),
caption: err.message
})
}
state.loading--
}
input.click()
}
async function clearImage () {
state.loading++
try {
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation clearUserAvatar (
$id: UUID!
) {
clearUserAvatar (
id: $id
) {
operation {
succeeded
message
}
}
}
`,
variables: {
id: userStore.id
}
})
if (resp?.data?.clearUserAvatar?.operation?.succeeded) {
$q.notify({
type: 'positive',
message: t('profile.avatarClearSuccess')
})
state.assetTimestamp = (new Date()).toISOString()
userStore.$patch({
hasAvatar: false
})
} else {
throw new Error(resp?.data?.uploadUserAvatar?.operation?.message || 'An unexpected error occured.')
}
} catch (err) {
$q.notify({
type: 'negative',
message: t('profile.avatarClearFailed'),
caption: err.message
})
}
state.loading--
}
</script>
<style lang="scss">
.profile-avatar-circ {
box-shadow: 2px 2px 15px -5px var(--q-primary), -2px -2px 15px -5px var(--q-primary), inset 0 0 2px 8px rgba(255,255,255,.15);
&.is-image {
box-shadow: 0 0 0 5px rgba(0,0,0,.1);
}
}
</style>
<template lang="pug">
q-page.q-py-md(:style-fn='pageStyle')
.text-header {{t('profile.groups')}}
.q-pa-md
.text-body2 {{ t('profile.groupsInfo') }}
q-list.q-mt-lg(
bordered
separator
)
q-item(
v-if='state.groups.length === 0 && state.loading < 1'
)
q-item-section
span.text-negative {{ t('profile.groupsNone') }}
q-item(
v-for='grp of state.groups'
:key='grp.id'
)
q-item-section(avatar)
q-avatar(
color='secondary'
text-color='white'
icon='las la-users'
rounded
)
q-item-section
strong {{grp.name}}
q-inner-loading(:showing='state.loading > 0')
</template>
<script setup>
import gql from 'graphql-tag'
import { useI18n } from 'vue-i18n'
import { useMeta, useQuasar } from 'quasar'
import { onMounted, reactive } from 'vue'
import { useUserStore } from 'src/stores/user'
// QUASAR
const $q = useQuasar()
// STORES
const userStore = useUserStore()
// I18N
const { t } = useI18n()
// META
useMeta({
title: t('profile.avatar')
})
// DATA
const state = reactive({
groups: [],
loading: 0
})
// METHODS
function pageStyle (offset, height) {
return {
'min-height': `${height - 100 - offset}px`
}
}
async function fetchGroups () {
state.loading++
try {
const respRaw = await APOLLO_CLIENT.query({
query: gql`
query getUserProfileGroups (
$id: UUID!
) {
userById (
id: $id
) {
id
groups {
id
name
}
}
}
`,
variables: {
id: userStore.id
},
fetchPolicy: 'network-only'
})
state.groups = respRaw.data?.userById?.groups ?? []
} catch (err) {
$q.notify({
type: 'negative',
message: t('profile.groupsLoadingFailed'),
caption: err.message
})
}
state.loading--
}
// MOUNTED
onMounted(() => {
fetchGroups()
})
</script>
...@@ -80,11 +80,10 @@ q-page.q-py-md(:style-fn='pageStyle') ...@@ -80,11 +80,10 @@ q-page.q-py-md(:style-fn='pageStyle')
q-select( q-select(
outlined outlined
v-model='state.config.timezone' v-model='state.config.timezone'
:options='dataStore.timezones' :options='timezones'
option-value='value' :virtual-scroll-slice-size='100'
option-label='text' :virtual-scroll-slice-ratio-before='2'
emit-value :virtual-scroll-slice-ratio-after='2'
map-options
dense dense
options-dense options-dense
:aria-label='t(`admin.general.defaultTimezone`)' :aria-label='t(`admin.general.defaultTimezone`)'
...@@ -153,7 +152,6 @@ import { useMeta, useQuasar } from 'quasar' ...@@ -153,7 +152,6 @@ import { useMeta, useQuasar } from 'quasar'
import { onMounted, reactive, watch } from 'vue' import { onMounted, reactive, watch } from 'vue'
import { useSiteStore } from 'src/stores/site' import { useSiteStore } from 'src/stores/site'
import { useDataStore } from 'src/stores/data'
import { useUserStore } from 'src/stores/user' import { useUserStore } from 'src/stores/user'
// QUASAR // QUASAR
...@@ -163,7 +161,6 @@ const $q = useQuasar() ...@@ -163,7 +161,6 @@ const $q = useQuasar()
// STORES // STORES
const siteStore = useSiteStore() const siteStore = useSiteStore()
const dataStore = useDataStore()
const userStore = useUserStore() const userStore = useUserStore()
// I18N // I18N
...@@ -173,7 +170,7 @@ const { t } = useI18n() ...@@ -173,7 +170,7 @@ const { t } = useI18n()
// META // META
useMeta({ useMeta({
title: t('profile.title') title: t('profile.myInfo')
}) })
// DATA // DATA
...@@ -209,6 +206,7 @@ const appearances = [ ...@@ -209,6 +206,7 @@ const appearances = [
{ value: 'light', label: t('profile.appearanceLight') }, { value: 'light', label: t('profile.appearanceLight') },
{ value: 'dark', label: t('profile.appearanceDark') } { value: 'dark', label: t('profile.appearanceDark') }
] ]
const timezones = Intl.supportedValuesOf('timeZone')
// METHODS // METHODS
......
<template lang='pug'>
.fullscreen.bg-blue.text-white.text-center.q-pa-md.flex.flex-center
div
.text-h1 Unknown Site
.text-h2(style="opacity:.4") Oops. Nothing here...
q-btn(
class="q-mt-xl"
color="white"
text-color="blue"
unelevated
to="/"
label="Go Home"
no-caps
)
</template>
<script>
export default {
name: 'UnknownSite'
}
</script>
...@@ -20,7 +20,10 @@ const routes = [ ...@@ -20,7 +20,10 @@ const routes = [
component: () => import('layouts/ProfileLayout.vue'), component: () => import('layouts/ProfileLayout.vue'),
children: [ children: [
{ path: '', redirect: '/_profile/info' }, { path: '', redirect: '/_profile/info' },
{ path: 'info', component: () => import('pages/Profile.vue') } { path: 'info', component: () => import('src/pages/ProfileInfo.vue') },
{ path: 'avatar', component: () => import('src/pages/ProfileAvatar.vue') },
{ path: 'auth', component: () => import('src/pages/ProfileAuth.vue') },
{ path: 'groups', component: () => import('src/pages/ProfileGroups.vue') }
] ]
}, },
{ {
...@@ -70,8 +73,16 @@ const routes = [ ...@@ -70,8 +73,16 @@ const routes = [
// component: () => import('../pages/UnknownSite.vue') // component: () => import('../pages/UnknownSite.vue')
// }, // },
// Always leave this as last one, // --------------------------------
// but you can also remove it // SYSTEM ROUTES CATCH-ALL FALLBACK
// --------------------------------
{
path: '/_:catchAll(.*)*',
redirect: '/_error/notfound'
},
// -----------------------
// STANDARD PAGE CATCH-ALL
// -----------------------
{ {
path: '/:catchAll(.*)*', path: '/:catchAll(.*)*',
component: () => import('../layouts/MainLayout.vue'), component: () => import('../layouts/MainLayout.vue'),
......
import { defineStore } from 'pinia'
export const useDataStore = defineStore('data', {
state: () => ({
timezones: [
{ text: '(GMT-11:00) Niue', value: 'Pacific/Niue' },
{ text: '(GMT-11:00) Pago Pago', value: 'Pacific/Pago_Pago' },
{ text: '(GMT-10:00) Hawaii Time', value: 'Pacific/Honolulu' },
{ text: '(GMT-10:00) Rarotonga', value: 'Pacific/Rarotonga' },
{ text: '(GMT-10:00) Tahiti', value: 'Pacific/Tahiti' },
{ text: '(GMT-09:30) Marquesas', value: 'Pacific/Marquesas' },
{ text: '(GMT-09:00) Alaska Time', value: 'America/Anchorage' },
{ text: '(GMT-09:00) Gambier', value: 'Pacific/Gambier' },
{ text: '(GMT-08:00) Pacific Time', value: 'America/Los_Angeles' },
{ text: '(GMT-08:00) Pacific Time - Tijuana', value: 'America/Tijuana' },
{ text: '(GMT-08:00) Pacific Time - Vancouver', value: 'America/Vancouver' },
{ text: '(GMT-08:00) Pacific Time - Whitehorse', value: 'America/Whitehorse' },
{ text: '(GMT-08:00) Pitcairn', value: 'Pacific/Pitcairn' },
{ text: '(GMT-07:00) Mountain Time', value: 'America/Denver' },
{ text: '(GMT-07:00) Mountain Time - Arizona', value: 'America/Phoenix' },
{ text: '(GMT-07:00) Mountain Time - Chihuahua, Mazatlan', value: 'America/Mazatlan' },
{ text: '(GMT-07:00) Mountain Time - Dawson Creek', value: 'America/Dawson_Creek' },
{ text: '(GMT-07:00) Mountain Time - Edmonton', value: 'America/Edmonton' },
{ text: '(GMT-07:00) Mountain Time - Hermosillo', value: 'America/Hermosillo' },
{ text: '(GMT-07:00) Mountain Time - Yellowknife', value: 'America/Yellowknife' },
{ text: '(GMT-06:00) Belize', value: 'America/Belize' },
{ text: '(GMT-06:00) Central Time', value: 'America/Chicago' },
{ text: '(GMT-06:00) Central Time - Mexico City', value: 'America/Mexico_City' },
{ text: '(GMT-06:00) Central Time - Regina', value: 'America/Regina' },
{ text: '(GMT-06:00) Central Time - Tegucigalpa', value: 'America/Tegucigalpa' },
{ text: '(GMT-06:00) Central Time - Winnipeg', value: 'America/Winnipeg' },
{ text: '(GMT-06:00) Costa Rica', value: 'America/Costa_Rica' },
{ text: '(GMT-06:00) El Salvador', value: 'America/El_Salvador' },
{ text: '(GMT-06:00) Galapagos', value: 'Pacific/Galapagos' },
{ text: '(GMT-06:00) Guatemala', value: 'America/Guatemala' },
{ text: '(GMT-06:00) Managua', value: 'America/Managua' },
{ text: '(GMT-05:00) America Cancun', value: 'America/Cancun' },
{ text: '(GMT-05:00) Bogota', value: 'America/Bogota' },
{ text: '(GMT-05:00) Easter Island', value: 'Pacific/Easter' },
{ text: '(GMT-05:00) Eastern Time', value: 'America/New_York' },
{ text: '(GMT-05:00) Eastern Time - Iqaluit', value: 'America/Iqaluit' },
{ text: '(GMT-05:00) Eastern Time - Toronto', value: 'America/Toronto' },
{ text: '(GMT-05:00) Guayaquil', value: 'America/Guayaquil' },
{ text: '(GMT-05:00) Havana', value: 'America/Havana' },
{ text: '(GMT-05:00) Jamaica', value: 'America/Jamaica' },
{ text: '(GMT-05:00) Lima', value: 'America/Lima' },
{ text: '(GMT-05:00) Nassau', value: 'America/Nassau' },
{ text: '(GMT-05:00) Panama', value: 'America/Panama' },
{ text: '(GMT-05:00) Port-au-Prince', value: 'America/Port-au-Prince' },
{ text: '(GMT-05:00) Rio Branco', value: 'America/Rio_Branco' },
{ text: '(GMT-04:00) Atlantic Time - Halifax', value: 'America/Halifax' },
{ text: '(GMT-04:00) Barbados', value: 'America/Barbados' },
{ text: '(GMT-04:00) Bermuda', value: 'Atlantic/Bermuda' },
{ text: '(GMT-04:00) Boa Vista', value: 'America/Boa_Vista' },
{ text: '(GMT-04:00) Caracas', value: 'America/Caracas' },
{ text: '(GMT-04:00) Curacao', value: 'America/Curacao' },
{ text: '(GMT-04:00) Grand Turk', value: 'America/Grand_Turk' },
{ text: '(GMT-04:00) Guyana', value: 'America/Guyana' },
{ text: '(GMT-04:00) La Paz', value: 'America/La_Paz' },
{ text: '(GMT-04:00) Manaus', value: 'America/Manaus' },
{ text: '(GMT-04:00) Martinique', value: 'America/Martinique' },
{ text: '(GMT-04:00) Port of Spain', value: 'America/Port_of_Spain' },
{ text: '(GMT-04:00) Porto Velho', value: 'America/Porto_Velho' },
{ text: '(GMT-04:00) Puerto Rico', value: 'America/Puerto_Rico' },
{ text: '(GMT-04:00) Santo Domingo', value: 'America/Santo_Domingo' },
{ text: '(GMT-04:00) Thule', value: 'America/Thule' },
{ text: '(GMT-03:30) Newfoundland Time - St. Johns', value: 'America/St_Johns' },
{ text: '(GMT-03:00) Araguaina', value: 'America/Araguaina' },
{ text: '(GMT-03:00) Asuncion', value: 'America/Asuncion' },
{ text: '(GMT-03:00) Belem', value: 'America/Belem' },
{ text: '(GMT-03:00) Buenos Aires', value: 'America/Argentina/Buenos_Aires' },
{ text: '(GMT-03:00) Campo Grande', value: 'America/Campo_Grande' },
{ text: '(GMT-03:00) Cayenne', value: 'America/Cayenne' },
{ text: '(GMT-03:00) Cuiaba', value: 'America/Cuiaba' },
{ text: '(GMT-03:00) Fortaleza', value: 'America/Fortaleza' },
{ text: '(GMT-03:00) Godthab', value: 'America/Godthab' },
{ text: '(GMT-03:00) Maceio', value: 'America/Maceio' },
{ text: '(GMT-03:00) Miquelon', value: 'America/Miquelon' },
{ text: '(GMT-03:00) Montevideo', value: 'America/Montevideo' },
{ text: '(GMT-03:00) Palmer', value: 'Antarctica/Palmer' },
{ text: '(GMT-03:00) Paramaribo', value: 'America/Paramaribo' },
{ text: '(GMT-03:00) Punta Arenas', value: 'America/Punta_Arenas' },
{ text: '(GMT-03:00) Recife', value: 'America/Recife' },
{ text: '(GMT-03:00) Rothera', value: 'Antarctica/Rothera' },
{ text: '(GMT-03:00) Salvador', value: 'America/Bahia' },
{ text: '(GMT-03:00) Santiago', value: 'America/Santiago' },
{ text: '(GMT-03:00) Stanley', value: 'Atlantic/Stanley' },
{ text: '(GMT-02:00) Noronha', value: 'America/Noronha' },
{ text: '(GMT-02:00) Sao Paulo', value: 'America/Sao_Paulo' },
{ text: '(GMT-02:00) South Georgia', value: 'Atlantic/South_Georgia' },
{ text: '(GMT-01:00) Azores', value: 'Atlantic/Azores' },
{ text: '(GMT-01:00) Cape Verde', value: 'Atlantic/Cape_Verde' },
{ text: '(GMT-01:00) Scoresbysund', value: 'America/Scoresbysund' },
{ text: '(GMT+00:00) Abidjan', value: 'Africa/Abidjan' },
{ text: '(GMT+00:00) Accra', value: 'Africa/Accra' },
{ text: '(GMT+00:00) Bissau', value: 'Africa/Bissau' },
{ text: '(GMT+00:00) Canary Islands', value: 'Atlantic/Canary' },
{ text: '(GMT+00:00) Casablanca', value: 'Africa/Casablanca' },
{ text: '(GMT+00:00) Danmarkshavn', value: 'America/Danmarkshavn' },
{ text: '(GMT+00:00) Dublin', value: 'Europe/Dublin' },
{ text: '(GMT+00:00) El Aaiun', value: 'Africa/El_Aaiun' },
{ text: '(GMT+00:00) Faeroe', value: 'Atlantic/Faroe' },
{ text: '(GMT+00:00) GMT (no daylight saving)', value: 'Etc/GMT' },
{ text: '(GMT+00:00) Lisbon', value: 'Europe/Lisbon' },
{ text: '(GMT+00:00) London', value: 'Europe/London' },
{ text: '(GMT+00:00) Monrovia', value: 'Africa/Monrovia' },
{ text: '(GMT+00:00) Reykjavik', value: 'Atlantic/Reykjavik' },
{ text: '(GMT+01:00) Algiers', value: 'Africa/Algiers' },
{ text: '(GMT+01:00) Amsterdam', value: 'Europe/Amsterdam' },
{ text: '(GMT+01:00) Andorra', value: 'Europe/Andorra' },
{ text: '(GMT+01:00) Berlin', value: 'Europe/Berlin' },
{ text: '(GMT+01:00) Brussels', value: 'Europe/Brussels' },
{ text: '(GMT+01:00) Budapest', value: 'Europe/Budapest' },
{ text: '(GMT+01:00) Central European Time - Belgrade', value: 'Europe/Belgrade' },
{ text: '(GMT+01:00) Central European Time - Prague', value: 'Europe/Prague' },
{ text: '(GMT+01:00) Ceuta', value: 'Africa/Ceuta' },
{ text: '(GMT+01:00) Copenhagen', value: 'Europe/Copenhagen' },
{ text: '(GMT+01:00) Gibraltar', value: 'Europe/Gibraltar' },
{ text: '(GMT+01:00) Lagos', value: 'Africa/Lagos' },
{ text: '(GMT+01:00) Luxembourg', value: 'Europe/Luxembourg' },
{ text: '(GMT+01:00) Madrid', value: 'Europe/Madrid' },
{ text: '(GMT+01:00) Malta', value: 'Europe/Malta' },
{ text: '(GMT+01:00) Monaco', value: 'Europe/Monaco' },
{ text: '(GMT+01:00) Ndjamena', value: 'Africa/Ndjamena' },
{ text: '(GMT+01:00) Oslo', value: 'Europe/Oslo' },
{ text: '(GMT+01:00) Paris', value: 'Europe/Paris' },
{ text: '(GMT+01:00) Rome', value: 'Europe/Rome' },
{ text: '(GMT+01:00) Stockholm', value: 'Europe/Stockholm' },
{ text: '(GMT+01:00) Tirane', value: 'Europe/Tirane' },
{ text: '(GMT+01:00) Tunis', value: 'Africa/Tunis' },
{ text: '(GMT+01:00) Vienna', value: 'Europe/Vienna' },
{ text: '(GMT+01:00) Warsaw', value: 'Europe/Warsaw' },
{ text: '(GMT+01:00) Zurich', value: 'Europe/Zurich' },
{ text: '(GMT+02:00) Amman', value: 'Asia/Amman' },
{ text: '(GMT+02:00) Athens', value: 'Europe/Athens' },
{ text: '(GMT+02:00) Beirut', value: 'Asia/Beirut' },
{ text: '(GMT+02:00) Bucharest', value: 'Europe/Bucharest' },
{ text: '(GMT+02:00) Cairo', value: 'Africa/Cairo' },
{ text: '(GMT+02:00) Chisinau', value: 'Europe/Chisinau' },
{ text: '(GMT+02:00) Damascus', value: 'Asia/Damascus' },
{ text: '(GMT+02:00) Gaza', value: 'Asia/Gaza' },
{ text: '(GMT+02:00) Helsinki', value: 'Europe/Helsinki' },
{ text: '(GMT+02:00) Jerusalem', value: 'Asia/Jerusalem' },
{ text: '(GMT+02:00) Johannesburg', value: 'Africa/Johannesburg' },
{ text: '(GMT+02:00) Khartoum', value: 'Africa/Khartoum' },
{ text: '(GMT+02:00) Kiev', value: 'Europe/Kiev' },
{ text: '(GMT+02:00) Maputo', value: 'Africa/Maputo' },
{ text: '(GMT+02:00) Moscow-01 - Kaliningrad', value: 'Europe/Kaliningrad' },
{ text: '(GMT+02:00) Nicosia', value: 'Asia/Nicosia' },
{ text: '(GMT+02:00) Riga', value: 'Europe/Riga' },
{ text: '(GMT+02:00) Sofia', value: 'Europe/Sofia' },
{ text: '(GMT+02:00) Tallinn', value: 'Europe/Tallinn' },
{ text: '(GMT+02:00) Tripoli', value: 'Africa/Tripoli' },
{ text: '(GMT+02:00) Vilnius', value: 'Europe/Vilnius' },
{ text: '(GMT+02:00) Windhoek', value: 'Africa/Windhoek' },
{ text: '(GMT+03:00) Baghdad', value: 'Asia/Baghdad' },
{ text: '(GMT+03:00) Istanbul', value: 'Europe/Istanbul' },
{ text: '(GMT+03:00) Minsk', value: 'Europe/Minsk' },
{ text: '(GMT+03:00) Moscow+00 - Moscow', value: 'Europe/Moscow' },
{ text: '(GMT+03:00) Nairobi', value: 'Africa/Nairobi' },
{ text: '(GMT+03:00) Qatar', value: 'Asia/Qatar' },
{ text: '(GMT+03:00) Riyadh', value: 'Asia/Riyadh' },
{ text: '(GMT+03:00) Syowa', value: 'Antarctica/Syowa' },
{ text: '(GMT+03:30) Tehran', value: 'Asia/Tehran' },
{ text: '(GMT+04:00) Baku', value: 'Asia/Baku' },
{ text: '(GMT+04:00) Dubai', value: 'Asia/Dubai' },
{ text: '(GMT+04:00) Mahe', value: 'Indian/Mahe' },
{ text: '(GMT+04:00) Mauritius', value: 'Indian/Mauritius' },
{ text: '(GMT+04:00) Moscow+01 - Samara', value: 'Europe/Samara' },
{ text: '(GMT+04:00) Reunion', value: 'Indian/Reunion' },
{ text: '(GMT+04:00) Tbilisi', value: 'Asia/Tbilisi' },
{ text: '(GMT+04:00) Yerevan', value: 'Asia/Yerevan' },
{ text: '(GMT+04:30) Kabul', value: 'Asia/Kabul' },
{ text: '(GMT+05:00) Aqtau', value: 'Asia/Aqtau' },
{ text: '(GMT+05:00) Aqtobe', value: 'Asia/Aqtobe' },
{ text: '(GMT+05:00) Ashgabat', value: 'Asia/Ashgabat' },
{ text: '(GMT+05:00) Dushanbe', value: 'Asia/Dushanbe' },
{ text: '(GMT+05:00) Karachi', value: 'Asia/Karachi' },
{ text: '(GMT+05:00) Kerguelen', value: 'Indian/Kerguelen' },
{ text: '(GMT+05:00) Maldives', value: 'Indian/Maldives' },
{ text: '(GMT+05:00) Mawson', value: 'Antarctica/Mawson' },
{ text: '(GMT+05:00) Moscow+02 - Yekaterinburg', value: 'Asia/Yekaterinburg' },
{ text: '(GMT+05:00) Tashkent', value: 'Asia/Tashkent' },
{ text: '(GMT+05:30) Colombo', value: 'Asia/Colombo' },
{ text: '(GMT+05:30) India Standard Time', value: 'Asia/Kolkata' },
{ text: '(GMT+05:45) Kathmandu', value: 'Asia/Kathmandu' },
{ text: '(GMT+06:00) Almaty', value: 'Asia/Almaty' },
{ text: '(GMT+06:00) Bishkek', value: 'Asia/Bishkek' },
{ text: '(GMT+06:00) Chagos', value: 'Indian/Chagos' },
{ text: '(GMT+06:00) Dhaka', value: 'Asia/Dhaka' },
{ text: '(GMT+06:00) Moscow+03 - Omsk', value: 'Asia/Omsk' },
{ text: '(GMT+06:00) Thimphu', value: 'Asia/Thimphu' },
{ text: '(GMT+06:00) Vostok', value: 'Antarctica/Vostok' },
{ text: '(GMT+06:30) Cocos', value: 'Indian/Cocos' },
{ text: '(GMT+06:30) Rangoon', value: 'Asia/Yangon' },
{ text: '(GMT+07:00) Bangkok', value: 'Asia/Bangkok' },
{ text: '(GMT+07:00) Christmas', value: 'Indian/Christmas' },
{ text: '(GMT+07:00) Davis', value: 'Antarctica/Davis' },
{ text: '(GMT+07:00) Hanoi', value: 'Asia/Saigon' },
{ text: '(GMT+07:00) Hovd', value: 'Asia/Hovd' },
{ text: '(GMT+07:00) Jakarta', value: 'Asia/Jakarta' },
{ text: '(GMT+07:00) Moscow+04 - Krasnoyarsk', value: 'Asia/Krasnoyarsk' },
{ text: '(GMT+08:00) Brunei', value: 'Asia/Brunei' },
{ text: '(GMT+08:00) China Time - Beijing', value: 'Asia/Shanghai' },
{ text: '(GMT+08:00) Choibalsan', value: 'Asia/Choibalsan' },
{ text: '(GMT+08:00) Hong Kong', value: 'Asia/Hong_Kong' },
{ text: '(GMT+08:00) Kuala Lumpur', value: 'Asia/Kuala_Lumpur' },
{ text: '(GMT+08:00) Macau', value: 'Asia/Macau' },
{ text: '(GMT+08:00) Makassar', value: 'Asia/Makassar' },
{ text: '(GMT+08:00) Manila', value: 'Asia/Manila' },
{ text: '(GMT+08:00) Moscow+05 - Irkutsk', value: 'Asia/Irkutsk' },
{ text: '(GMT+08:00) Singapore', value: 'Asia/Singapore' },
{ text: '(GMT+08:00) Taipei', value: 'Asia/Taipei' },
{ text: '(GMT+08:00) Ulaanbaatar', value: 'Asia/Ulaanbaatar' },
{ text: '(GMT+08:00) Western Time - Perth', value: 'Australia/Perth' },
{ text: '(GMT+08:30) Pyongyang', value: 'Asia/Pyongyang' },
{ text: '(GMT+09:00) Dili', value: 'Asia/Dili' },
{ text: '(GMT+09:00) Jayapura', value: 'Asia/Jayapura' },
{ text: '(GMT+09:00) Moscow+06 - Yakutsk', value: 'Asia/Yakutsk' },
{ text: '(GMT+09:00) Palau', value: 'Pacific/Palau' },
{ text: '(GMT+09:00) Seoul', value: 'Asia/Seoul' },
{ text: '(GMT+09:00) Tokyo', value: 'Asia/Tokyo' },
{ text: '(GMT+09:30) Central Time - Darwin', value: 'Australia/Darwin' },
{ text: '(GMT+10:00) Dumont D\'Urville', value: 'Antarctica/DumontDUrville' },
{ text: '(GMT+10:00) Eastern Time - Brisbane', value: 'Australia/Brisbane' },
{ text: '(GMT+10:00) Guam', value: 'Pacific/Guam' },
{ text: '(GMT+10:00) Moscow+07 - Vladivostok', value: 'Asia/Vladivostok' },
{ text: '(GMT+10:00) Port Moresby', value: 'Pacific/Port_Moresby' },
{ text: '(GMT+10:00) Truk', value: 'Pacific/Chuuk' },
{ text: '(GMT+10:30) Central Time - Adelaide', value: 'Australia/Adelaide' },
{ text: '(GMT+11:00) Casey', value: 'Antarctica/Casey' },
{ text: '(GMT+11:00) Eastern Time - Hobart', value: 'Australia/Hobart' },
{ text: '(GMT+11:00) Eastern Time - Melbourne, Sydney', value: 'Australia/Sydney' },
{ text: '(GMT+11:00) Efate', value: 'Pacific/Efate' },
{ text: '(GMT+11:00) Guadalcanal', value: 'Pacific/Guadalcanal' },
{ text: '(GMT+11:00) Kosrae', value: 'Pacific/Kosrae' },
{ text: '(GMT+11:00) Moscow+08 - Magadan', value: 'Asia/Magadan' },
{ text: '(GMT+11:00) Norfolk', value: 'Pacific/Norfolk' },
{ text: '(GMT+11:00) Noumea', value: 'Pacific/Noumea' },
{ text: '(GMT+11:00) Ponape', value: 'Pacific/Pohnpei' },
{ text: '(GMT+12:00) Funafuti', value: 'Pacific/Funafuti' },
{ text: '(GMT+12:00) Kwajalein', value: 'Pacific/Kwajalein' },
{ text: '(GMT+12:00) Majuro', value: 'Pacific/Majuro' },
{ text: '(GMT+12:00) Moscow+09 - Petropavlovsk-Kamchatskiy', value: 'Asia/Kamchatka' },
{ text: '(GMT+12:00) Nauru', value: 'Pacific/Nauru' },
{ text: '(GMT+12:00) Tarawa', value: 'Pacific/Tarawa' },
{ text: '(GMT+12:00) Wake', value: 'Pacific/Wake' },
{ text: '(GMT+12:00) Wallis', value: 'Pacific/Wallis' },
{ text: '(GMT+13:00) Auckland', value: 'Pacific/Auckland' },
{ text: '(GMT+13:00) Enderbury', value: 'Pacific/Enderbury' },
{ text: '(GMT+13:00) Fakaofo', value: 'Pacific/Fakaofo' },
{ text: '(GMT+13:00) Fiji', value: 'Pacific/Fiji' },
{ text: '(GMT+13:00) Tongatapu', value: 'Pacific/Tongatapu' },
{ text: '(GMT+14:00) Apia', value: 'Pacific/Apia' },
{ text: '(GMT+14:00) Kiritimati', value: 'Pacific/Kiritimati' }
]
}),
getters: {},
actions: {}
})
...@@ -9,7 +9,7 @@ export const useUserStore = defineStore('user', { ...@@ -9,7 +9,7 @@ export const useUserStore = defineStore('user', {
id: '10000000-0000-4000-8000-000000000001', id: '10000000-0000-4000-8000-000000000001',
email: '', email: '',
name: '', name: '',
pictureUrl: '', hasAvatar: false,
localeCode: '', localeCode: '',
timezone: '', timezone: '',
dateFormat: 'YYYY-MM-DD', dateFormat: 'YYYY-MM-DD',
...@@ -58,6 +58,7 @@ export const useUserStore = defineStore('user', { ...@@ -58,6 +58,7 @@ export const useUserStore = defineStore('user', {
id id
name name
email email
hasAvatar
meta meta
prefs prefs
lastLoginAt lastLoginAt
...@@ -78,7 +79,7 @@ export const useUserStore = defineStore('user', { ...@@ -78,7 +79,7 @@ export const useUserStore = defineStore('user', {
} }
this.name = resp.name || 'Unknown User' this.name = resp.name || 'Unknown User'
this.email = resp.email this.email = resp.email
this.pictureUrl = (resp.pictureUrl === 'local') ? `/_user/${this.id}/avatar` : resp.pictureUrl this.hasAvatar = resp.hasAvatar ?? false
this.location = resp.meta.location || '' this.location = resp.meta.location || ''
this.jobTitle = resp.meta.jobTitle || '' this.jobTitle = resp.meta.jobTitle || ''
this.pronouns = resp.meta.pronouns || '' this.pronouns = resp.meta.pronouns || ''
......
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