feat: locale system groundwork + various improvements

pull/6775/head
NGPixel 1 year ago
parent 4d1ab1f193
commit 102c23ec1e
No known key found for this signature in database
GPG Key ID: B755FB6870B30F63

@ -59,10 +59,14 @@ RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /
RUN echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
# Add ngrok source
# Add ngrok Source
RUN curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc > /dev/null && \
echo "deb https://ngrok-agent.s3.amazonaws.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ngrok.list
# Add Localazy Source
RUN curl -sS https://dist.localazy.com/debian/pubkey.gpg | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/localazy.gpg && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/trusted.gpg.d/localazy.gpg] https://maven.localazy.com/repository/apt/ stable main" | sudo tee /etc/apt/sources.list.d/localazy.list
# Install the packages we need
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && apt-get install -qy \
bash \
@ -73,6 +77,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && apt-get install -
less \
git \
gnupg2 \
localazy \
nano \
netcat \
ngrok \
@ -81,17 +86,17 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && apt-get install -
wget
# avoid million NPM install messages
ENV npm_config_loglevel warn
ENV npm_config_loglevel=warn
# allow installing when the main user is root
ENV npm_config_unsafe_perm true
ENV npm_config_unsafe_perm=true
# disable NPM funding messages
ENV npm_config_fund false
ENV npm_config_fund=false
# Colorize the bash shell
RUN sed -i 's/#force_color_prompt=/force_color_prompt=/' /root/.bashrc
# Fetch wait-for utility
ADD https://raw.githubusercontent.com/eficode/wait-for/v2.2.3/wait-for /usr/local/bin/
# Copy wait-for utility
COPY wait-for.sh /usr/local/bin/wait-for
RUN chmod +rx /usr/local/bin/wait-for
# Copy the startup file
@ -102,3 +107,5 @@ RUN sed -i 's/\r$//' /docker-init.sh && \
# Create workspace
RUN mkdir -p /workspace
WORKDIR /workspace
ENV NODE_ENV=development

@ -0,0 +1,191 @@
#!/bin/sh
# The MIT License (MIT)
#
# Copyright (c) 2017 Eficode Oy
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
VERSION="2.2.3"
set -- "$@" -- "$TIMEOUT" "$QUIET" "$PROTOCOL" "$HOST" "$PORT" "$result"
TIMEOUT=15
QUIET=0
# The protocol to make the request with, either "tcp" or "http"
PROTOCOL="tcp"
echoerr() {
if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi
}
usage() {
exitcode="$1"
cat << USAGE >&2
Usage:
$0 host:port|url [-t timeout] [-- command args]
-q | --quiet Do not output any status messages
-t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout
-v | --version Show the version of this tool
-- COMMAND ARGS Execute command with args after the test finishes
USAGE
exit "$exitcode"
}
wait_for() {
case "$PROTOCOL" in
tcp)
if ! command -v nc >/dev/null; then
echoerr 'nc command is missing!'
exit 1
fi
;;
http)
if ! command -v wget >/dev/null; then
echoerr 'wget command is missing!'
exit 1
fi
;;
esac
TIMEOUT_END=$(($(date +%s) + TIMEOUT))
while :; do
case "$PROTOCOL" in
tcp)
nc -w 1 -z "$HOST" "$PORT" > /dev/null 2>&1
;;
http)
wget --timeout=1 -q "$HOST" -O /dev/null > /dev/null 2>&1
;;
*)
echoerr "Unknown protocol '$PROTOCOL'"
exit 1
;;
esac
result=$?
if [ $result -eq 0 ] ; then
if [ $# -gt 7 ] ; then
for result in $(seq $(($# - 7))); do
result=$1
shift
set -- "$@" "$result"
done
TIMEOUT=$2 QUIET=$3 PROTOCOL=$4 HOST=$5 PORT=$6 result=$7
shift 7
exec "$@"
fi
exit 0
fi
if [ $TIMEOUT -ne 0 -a $(date +%s) -ge $TIMEOUT_END ]; then
echo "Operation timed out" >&2
exit 1
fi
sleep 1
done
}
while :; do
case "$1" in
http://*|https://*)
HOST="$1"
PROTOCOL="http"
shift 1
;;
*:* )
HOST=$(printf "%s\n" "$1"| cut -d : -f 1)
PORT=$(printf "%s\n" "$1"| cut -d : -f 2)
shift 1
;;
-v | --version)
echo $VERSION
exit
;;
-q | --quiet)
QUIET=1
shift 1
;;
-q-*)
QUIET=0
echoerr "Unknown option: $1"
usage 1
;;
-q*)
QUIET=1
result=$1
shift 1
set -- -"${result#-q}" "$@"
;;
-t | --timeout)
TIMEOUT="$2"
shift 2
;;
-t*)
TIMEOUT="${1#-t}"
shift 1
;;
--timeout=*)
TIMEOUT="${1#*=}"
shift 1
;;
--)
shift
break
;;
--help)
usage 0
;;
-*)
QUIET=0
echoerr "Unknown option: $1"
usage 1
;;
*)
QUIET=0
echoerr "Unknown argument: $1"
usage 1
;;
esac
done
if ! [ "$TIMEOUT" -ge 0 ] 2>/dev/null; then
echoerr "Error: invalid timeout '$TIMEOUT'"
usage 3
fi
case "$PROTOCOL" in
tcp)
if [ "$HOST" = "" ] || [ "$PORT" = "" ]; then
echoerr "Error: you need to provide a host and port to test."
usage 2
fi
;;
http)
if [ "$HOST" = "" ]; then
echoerr "Error: you need to provide a host to test."
usage 2
fi
;;
esac
wait_for "$@"

@ -1,13 +1,12 @@
extends:
- requarks
- plugin:vue/strongly-recommended
- plugin:cypress/recommended
env:
node: true
jest: true
parserOptions:
parser: babel-eslint
ecmaVersion: 2017
ecmaVersion: 2020
allowImportExportEverywhere: true
globals:
document: false
@ -22,3 +21,4 @@ ignorePatterns:
- 'repo/**'
- 'data/**'
- 'logs/**'
- 'server/locales/**'

5
.gitignore vendored

@ -21,6 +21,7 @@ npm-debug.log*
# Config Files
/config.yml
/localazy.keys.json
# Data directories
/repo
@ -40,4 +41,6 @@ test-results/
.scannerwork
# Localization Resources
/server/locales/**/*.yml
/server/locales/*.json
!/server/locales/en.json
!/server/locales/list.json

@ -11,8 +11,9 @@
"source.fixAll.eslint": true
},
"i18n-ally.localesPaths": [
"ux/src/i18n/locales"
"server/locales",
],
"i18n-ally.pathMatcher": "{locale}.json",
"i18n-ally.keystyle": "flat",
"i18n-ally.sortKeys": true,
"i18n-ally.enabledFrameworks": [

@ -0,0 +1,13 @@
{
"upload": {
"folder": "server/locales",
"files": "en.json",
"type": "json"
},
"download": {
"folder": "server/locales",
"files": "${lang}.json",
"metadataFileJs": "metadata.mjs"
}
}

@ -70,6 +70,8 @@ export default {
* Post-Web Boot Sequence
*/
async postBootWeb() {
await WIKI.db.locales.refreshFromDisk()
await WIKI.db.analytics.refreshProvidersFromDisk()
await WIKI.db.authentication.refreshStrategiesFromDisk()
await WIKI.db.commentProviders.refreshProvidersFromDisk()

@ -163,11 +163,14 @@ export async function up (knex) {
})
// LOCALES -----------------------------
.createTable('locales', table => {
table.string('code', 5).notNullable().primary()
table.jsonb('strings')
table.boolean('isRTL').notNullable().defaultTo(false)
table.string('code', 10).notNullable().primary()
table.string('name').notNullable()
table.string('nativeName').notNullable()
table.string('language', 2).notNullable().index()
table.string('region', 2)
table.string('script', 4)
table.boolean('isRTL').notNullable().defaultTo(false)
table.jsonb('strings')
table.integer('completeness').notNullable().defaultTo(0)
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
@ -207,7 +210,7 @@ export async function up (knex) {
.createTable('pageLinks', table => {
table.increments('id').primary()
table.string('path').notNullable()
table.string('localeCode', 5).notNullable()
table.string('localeCode', 10).notNullable()
})
// PAGES -------------------------------
.createTable('pages', table => {
@ -284,7 +287,7 @@ export async function up (knex) {
table.string('fileName').notNullable().index()
table.string('hash').notNullable().index()
table.enu('type', ['folder', 'page', 'asset']).notNullable().index()
table.string('localeCode', 5).notNullable().defaultTo('en').index()
table.string('localeCode', 10).notNullable().defaultTo('en').index()
table.string('title').notNullable()
table.jsonb('meta').notNullable().defaultTo('{}')
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
@ -295,6 +298,13 @@ export async function up (knex) {
table.uuid('id').notNullable().primary()
table.binary('data').notNullable()
})
// USER EDITOR SETTINGS ----------------
.createTable('userEditorSettings', table => {
table.uuid('id').notNullable()
table.string('editor').notNullable()
table.jsonb('config').notNullable().defaultTo('{}')
table.primary(['id', 'editor'])
})
// USER KEYS ---------------------------
.createTable('userKeys', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
@ -359,7 +369,7 @@ export async function up (knex) {
table.uuid('siteId').notNullable().references('id').inTable('sites').index()
})
.table('pageHistory', table => {
table.string('localeCode', 5).references('code').inTable('locales')
table.string('localeCode', 10).references('code').inTable('locales')
table.uuid('authorId').notNullable().references('id').inTable('users')
table.uuid('siteId').notNullable().references('id').inTable('sites').index()
})
@ -368,7 +378,7 @@ export async function up (knex) {
table.index(['path', 'localeCode'])
})
.table('pages', table => {
table.string('localeCode', 5).references('code').inTable('locales').index()
table.string('localeCode', 10).references('code').inTable('locales').index()
table.uuid('authorId').notNullable().references('id').inTable('users').index()
table.uuid('creatorId').notNullable().references('id').inTable('users').index()
table.uuid('ownerId').notNullable().references('id').inTable('users').index()
@ -387,9 +397,6 @@ export async function up (knex) {
.table('userKeys', table => {
table.uuid('userId').notNullable().references('id').inTable('users')
})
.table('users', table => {
table.string('localeCode', 5).references('code').inTable('locales').notNullable().defaultTo('en')
})
// =====================================
// DEFAULT DATA
@ -520,16 +527,6 @@ export async function up (knex) {
}
])
// -> DEFAULT LOCALE
await knex('locales').insert({
code: 'en',
strings: {},
isRTL: false,
name: 'English',
nativeName: 'English'
})
// -> DEFAULT SITE
await knex('sites').insert({
@ -544,6 +541,7 @@ export async function up (knex) {
footerExtra: '',
pageExtensions: ['md', 'html', 'txt'],
pageCasing: true,
discoverable: false,
defaults: {
tocDepth: {
min: 1,
@ -566,9 +564,10 @@ export async function up (knex) {
follow: true
},
authStrategies: [{ id: authModuleId, order: 0, isVisible: true }],
locale: 'en',
localeNamespacing: false,
localeNamespaces: [],
locales: {
primary: 'en',
active: ['en']
},
assets: {
logo: false,
logoExt: 'svg',
@ -700,8 +699,7 @@ export async function up (knex) {
timeFormat: '12h',
appearance: 'site',
cvd: 'none'
},
localeCode: 'en'
}
},
{
id: userGuestId,
@ -718,8 +716,7 @@ export async function up (knex) {
timeFormat: '12h',
appearance: 'site',
cvd: 'none'
},
localeCode: 'en'
}
}
])

@ -5,7 +5,7 @@ export default {
Query: {
async locales(obj, args, context, info) {
let remoteLocales = await WIKI.cache.get('locales')
let localLocales = await WIKI.db.locales.query().select('code', 'isRTL', 'name', 'nativeName', 'createdAt', 'updatedAt', 'availability')
let localLocales = await WIKI.db.locales.query().select('code', 'isRTL', 'name', 'nativeName', 'createdAt', 'updatedAt', 'completeness')
remoteLocales = remoteLocales || localLocales
return _.map(remoteLocales, rl => {
let isInstalled = _.some(localLocales, ['code', rl.code])
@ -16,8 +16,8 @@ export default {
}
})
},
translations (obj, args, context, info) {
return WIKI.lang.getByNamespace(args.locale, args.namespace)
localeStrings (obj, args, context, info) {
return WIKI.db.locales.getStrings(args.locale)
}
},
Mutation: {

@ -4,7 +4,7 @@
extend type Query {
locales: [LocalizationLocale]
translations(locale: String!, namespace: String!): [Translation]
localeStrings(locale: String!): JSON
}
extend type Mutation {
@ -25,7 +25,7 @@ extend type Mutation {
# -----------------------------------------------
type LocalizationLocale {
availability: Int
completeness: Int
code: String
createdAt: Date
installDate: Date
@ -33,10 +33,7 @@ type LocalizationLocale {
isRTL: Boolean
name: String
nativeName: String
region: String
script: String
updatedAt: Date
}
type Translation {
key: String
value: String
}

@ -65,11 +65,10 @@ type Site {
sitemap: Boolean
robots: SiteRobots
features: SiteFeatures
discoverable: Boolean
defaults: SiteDefaults
uploads: SiteUploads
locale: String
localeNamespaces: [String]
localeNamespacing: Boolean
locales: SiteLocales
editors: SiteEditors
theme: SiteTheme
}
@ -98,11 +97,9 @@ type SiteUploads {
normalizeFilename: Boolean
}
type SiteLocale {
locale: String
autoUpdate: Boolean
namespacing: Boolean
namespaces: [String]
type SiteLocales {
primary: String
active: [String]
}
type SiteEditors {
@ -184,6 +181,7 @@ input SiteUpdateInput {
sitemap: Boolean
robots: SiteRobotsInput
features: SiteFeaturesInput
discoverable: Boolean
defaults: SiteDefaultsInput
uploads: SiteUploadsInput
editors: SiteEditorsInput

@ -227,6 +227,9 @@
"admin.general.defaultTocDepth": "Default ToC Depth",
"admin.general.defaultTocDepthHint": "The default minimum and maximum header levels to show in the table of contents.",
"admin.general.defaults": "Site Defaults",
"admin.general.discoverable": "Make Discoverable in the Wiki Directory",
"admin.general.discoverableHint": "Add your wiki to the Wiki Directory so that it can be discovered by other users and wikis.",
"admin.general.discovery": "Discovery",
"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.favicon": "Favicon",

@ -1,9 +0,0 @@
[
{
"code": "en",
"name": "English",
"nativeName": "English",
"rtl": false,
"completeness": 100
}
]

@ -0,0 +1,31 @@
const localazyMetadata = {
projectUrl: "https://localazy.com/p/wiki",
baseLocale: "en",
languages: [
{
language: "en",
region: "",
script: "",
isRtl: false,
name: "English",
localizedName: "English",
pluralType: (n) => { return (n===1) ? "one" : "other"; }
}
],
files: [
{
cdnHash: "54b977214afbffe2ffeb07d0ccb03558e75e4408",
file: "file.json",
path: "",
library: "",
module: "",
buildType: "",
productFlavors: [],
cdnFiles: {
"en#": "https://delivery.localazy.com/_a7797965569058078203416ae5aa/_e0/54b977214afbffe2ffeb07d0ccb03558e75e4408/en/file.json"
}
}
]
};
export default localazyMetadata;

@ -1,4 +1,8 @@
import { Model } from 'objection'
import { find } from 'lodash-es'
import { stat, readFile } from 'node:fs/promises'
import path from 'node:path'
import { DateTime } from 'luxon'
/**
* Locales model
@ -36,6 +40,62 @@ export class Locale extends Model {
this.updatedAt = new Date().toISOString()
}
static async refreshFromDisk ({ force = false } = {}) {
try {
const localesMeta = (await import(`../locales/metadata.mjs`)).default
WIKI.logger.info(`Found ${localesMeta.languages.length} locales: [ OK ]`)
const dbLocales = await WIKI.db.locales.query().select('code', 'updatedAt')
for (const lang of localesMeta.languages) {
// -> Build filename
const langFilenameParts = [lang.language]
if (lang.region) {
langFilenameParts.push(lang.region)
}
if (lang.script) {
langFilenameParts.push(lang.script)
}
const langFilename = langFilenameParts.join('-')
// -> Get DB version
const dbLang = find(dbLocales, ['code', langFilename])
// -> Get File version
const flPath = path.join(WIKI.SERVERPATH, `locales/${langFilename}.json`)
const flStat = await stat(flPath)
const flUpdatedAt = DateTime.fromJSDate(flStat.mtime)
// -> Load strings
if (!dbLang || DateTime.fromJSDate(dbLang.updatedAt) < flUpdatedAt || force) {
WIKI.logger.debug(`Loading locale ${langFilename} into DB...`)
const flStrings = JSON.parse(await readFile(flPath, 'utf8'))
await WIKI.db.locales.query().insert({
code: langFilename,
name: lang.name,
nativeName: lang.localizedName,
language: lang.language,
region: lang.region,
script: lang.script,
isRTL: lang.isRtl,
strings: flStrings
}).onConflict('code').merge(['strings', 'updatedAt'])
} else {
WIKI.logger.debug(`Locale ${langFilename} is newer in the DB. Skipping disk version.`)
}
}
} catch (err) {
WIKI.logger.warn(`Failed to load locales from disk: [ FAILED ]`)
WIKI.logger.warn(err)
return false
}
}
static async getStrings (locale) {
const { strings } = await WIKI.db.locales.query().findOne('code', locale).column('strings')
return strings
}
static async getNavLocales({ cache = false } = {}) {
return []
// if (!WIKI.config.lang.namespacing) {

@ -58,6 +58,7 @@ export class Site extends Model {
footerExtra: '',
pageExtensions: ['md', 'html', 'txt'],
pageCasing: true,
discoverable: false,
defaults: {
tocDepth: {
min: 1,
@ -79,9 +80,10 @@ export class Site extends Model {
index: true,
follow: true
},
locale: 'en',
localeNamespacing: false,
localeNamespaces: [],
locales: {
primary: 'en',
active: ['en']
},
assets: {
logo: false,
logoExt: 'svg',

@ -637,18 +637,6 @@
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/@graphql-tools/mock/node_modules/@graphql-tools/utils": {
"version": "9.2.1",
"resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz",
"integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==",
"dependencies": {
"@graphql-typed-document-node/core": "^3.1.1",
"tslib": "^2.4.0"
},
"peerDependencies": {
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/@graphql-tools/schema": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.0.tgz",
@ -1556,17 +1544,6 @@
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/apollo-server-core/node_modules/@graphql-tools/utils": {
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.9.0.tgz",
"integrity": "sha512-pjJIWH0XOVnYGXCqej8g/u/tsfV4LvLlj0eATKQu5zwnxd/TiTHq7Cg313qUPTFFHZ3PP5wJ15chYVtLDwaymg==",
"dependencies": {
"tslib": "^2.4.0"
},
"peerDependencies": {
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/apollo-server-core/node_modules/value-or-promise": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz",

@ -185,6 +185,9 @@
"eslint-plugin-standard": "4.1.0",
"nodemon": "2.0.22"
},
"overrides": {
"@graphql-tools/utils": "10.0.0"
},
"collective": {
"type": "opencollective",
"url": "https://opencollective.com/wikijs",

@ -206,7 +206,7 @@ export async function init () {
id: currentSite.id,
title: currentSite.config.title,
darkMode: currentSite.config.theme.dark,
lang: currentSite.config.locale,
lang: currentSite.config.locales.primary,
rtl: false, // TODO: handle RTL
company: currentSite.config.company,
contentLicense: currentSite.config.contentLicense

125
ux/package-lock.json generated

@ -38,7 +38,7 @@
"@tiptap/starter-kit": "2.0.3",
"@tiptap/vue-3": "2.0.3",
"apollo-upload-client": "17.0.0",
"browser-fs-access": "0.34.0",
"browser-fs-access": "0.34.1",
"clipboard": "2.0.11",
"codemirror": "5.65.11",
"codemirror-asciidoc": "1.0.4",
@ -84,7 +84,7 @@
"quasar": "2.12.0",
"slugify": "1.6.6",
"socket.io-client": "4.6.1",
"tabulator-tables": "5.4.4",
"tabulator-tables": "5.5.0",
"tippy.js": "6.3.7",
"twemoji": "14.0.2",
"uuid": "9.0.0",
@ -107,9 +107,9 @@
"eslint": "8.41.0",
"eslint-config-standard": "17.0.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-n": "16.0.0",
"eslint-plugin-n": "15.7.0",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-vue": "9.13.0"
"eslint-plugin-vue": "9.14.0"
},
"engines": {
"node": ">= 18.0",
@ -2136,9 +2136,9 @@
}
},
"node_modules/browser-fs-access": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.34.0.tgz",
"integrity": "sha512-F49NXUfjE9ZkDSF9Xr0PXPt89Sb9TIZDpxWEHOp0kYnT7RfciGLYcpHqXFFJ/+CiocOlT+5DOJ8Y7+XrKrzafw=="
"version": "0.34.1",
"resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.34.1.tgz",
"integrity": "sha512-HPaRf2yimp8kWSuWJXc8Mi78dPbDzfduA+Gyq14H4jlMvd6XNfIRm36Y2yRLaa4x0gwcGuepj4zf14oiTlxrxQ=="
},
"node_modules/browserlist": {
"version": "1.0.1",
@ -3372,23 +3372,47 @@
"ms": "^2.1.1"
}
},
"node_modules/eslint-plugin-es-x": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-6.2.1.tgz",
"integrity": "sha512-uR34zUhZ9EBoiSD2DdV5kHLpydVEvwWqjteUr9sXRgJknwbKZJZhdJ7uFnaTtd+Nr/2G3ceJHnHXrFhJ67n3Tw==",
"node_modules/eslint-plugin-es": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz",
"integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.1.2",
"@eslint-community/regexpp": "^4.5.0"
"eslint-utils": "^2.0.0",
"regexpp": "^3.0.0"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
"node": ">=8.10.0"
},
"funding": {
"url": "https://github.com/sponsors/ota-meshi"
"url": "https://github.com/sponsors/mysticatea"
},
"peerDependencies": {
"eslint": ">=8"
"eslint": ">=4.19.1"
}
},
"node_modules/eslint-plugin-es/node_modules/eslint-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
"integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
"dev": true,
"dependencies": {
"eslint-visitor-keys": "^1.1.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/mysticatea"
}
},
"node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/eslint-plugin-import": {
@ -3447,22 +3471,22 @@
}
},
"node_modules/eslint-plugin-n": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.0.0.tgz",
"integrity": "sha512-akkZTE3hsHBrq6CwmGuYCzQREbVUrA855kzcHqe6i0FLBkeY7Y/6tThCVkjUnjhvRBAlc+8lILcSe5QvvDpeZQ==",
"version": "15.7.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz",
"integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"builtins": "^5.0.1",
"eslint-plugin-es-x": "^6.1.0",
"eslint-plugin-es": "^4.1.0",
"eslint-utils": "^3.0.0",
"ignore": "^5.1.1",
"is-core-module": "^2.12.0",
"is-core-module": "^2.11.0",
"minimatch": "^3.1.2",
"resolve": "^1.22.2",
"semver": "^7.5.0"
"resolve": "^1.22.1",
"semver": "^7.3.8"
},
"engines": {
"node": ">=16.0.0"
"node": ">=12.22.0"
},
"funding": {
"url": "https://github.com/sponsors/mysticatea"
@ -3483,9 +3507,9 @@
}
},
"node_modules/eslint-plugin-vue": {
"version": "9.13.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.13.0.tgz",
"integrity": "sha512-aBz9A8WB4wmpnVv0pYUt86cmH9EkcwWzgEwecBxMoRNhQjTL5i4sqadnwShv/hOdr8Hbl8XANGV7dtX9UQIAyA==",
"version": "9.14.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.14.0.tgz",
"integrity": "sha512-4O7EuiqPGVQA1wYCzLvCzsBTv9JIPHLHhrf0k55DLzbwtmJbSw2TKS0G/l7pOwi9RWMSkjIT7ftChU5gZpgnJw==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.3.0",
@ -3519,6 +3543,33 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-utils": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
"integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
"dev": true,
"dependencies": {
"eslint-visitor-keys": "^2.0.0"
},
"engines": {
"node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
},
"funding": {
"url": "https://github.com/sponsors/mysticatea"
},
"peerDependencies": {
"eslint": ">=5"
}
},
"node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/eslint-visitor-keys": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
@ -6419,6 +6470,18 @@
"node": ">= 0.4"
}
},
"node_modules/regexpp": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
"integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
"dev": true,
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/mysticatea"
}
},
"node_modules/register-service-worker": {
"version": "1.7.2",
"dev": true,
@ -7052,9 +7115,9 @@
"license": "MIT"
},
"node_modules/tabulator-tables": {
"version": "5.4.4",
"resolved": "https://registry.npmjs.org/tabulator-tables/-/tabulator-tables-5.4.4.tgz",
"integrity": "sha512-WqPWLRwrD8UMISUjQqZyj6y9k3ajQBs7eJtRosbt9q8RRkZlYCJYGxLt3L44jEXaQCE5q5EJFbGeDUEDJa1a3w=="
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/tabulator-tables/-/tabulator-tables-5.5.0.tgz",
"integrity": "sha512-UVe26QIaGqFfaP5wfN51zF4Vy23MNTWyvM95bF7EWGsDRQm6b2MV0wiBzakVGFj28YAgz5PvEyfqLZF415PWxw=="
},
"node_modules/tar-stream": {
"version": "2.2.0",

@ -43,7 +43,7 @@
"@tiptap/starter-kit": "2.0.3",
"@tiptap/vue-3": "2.0.3",
"apollo-upload-client": "17.0.0",
"browser-fs-access": "0.34.0",
"browser-fs-access": "0.34.1",
"clipboard": "2.0.11",
"codemirror": "5.65.11",
"codemirror-asciidoc": "1.0.4",
@ -89,7 +89,7 @@
"quasar": "2.12.0",
"slugify": "1.6.6",
"socket.io-client": "4.6.1",
"tabulator-tables": "5.4.4",
"tabulator-tables": "5.5.0",
"tippy.js": "6.3.7",
"twemoji": "14.0.2",
"uuid": "9.0.0",
@ -112,9 +112,9 @@
"eslint": "8.41.0",
"eslint-config-standard": "17.0.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-n": "16.0.0",
"eslint-plugin-n": "15.7.0",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-vue": "9.13.0"
"eslint-plugin-vue": "9.14.0"
},
"engines": {
"node": ">= 18.0",

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="80px" height="80px"><path fill="#dff0fe" d="M17.537 38.5L18.966 18.5 21.034 18.5 22.463 38.5z"/><path fill="#4788c7" d="M20.569,19l1.357,19h-3.852l1.357-19H20.569 M21.5,18h-3L17,39h6L21.5,18L21.5,18z"/><path fill="none" stroke="#4788c7" stroke-linecap="round" stroke-miterlimit="10" d="M13.269,37.237 C6.38,34.545,1.5,27.843,1.5,20C1.5,9.783,9.783,1.5,20,1.5S38.5,9.783,38.5,20c0,7.845-4.883,14.55-11.776,17.24"/><path fill="none" stroke="#4788c7" stroke-linecap="round" stroke-miterlimit="10" d="M13.565,31.813 C9.357,29.527,6.5,25.068,6.5,19.942c0-7.456,6.044-13.5,13.5-13.5s13.5,6.044,13.5,13.5c0,5.083-2.81,9.511-6.961,11.813"/><path fill="none" stroke="#4788c7" stroke-linecap="round" stroke-miterlimit="10" d="M13.441,25.407 C12.228,23.937,11.5,22.054,11.5,20c0-4.694,3.806-8.5,8.5-8.5s8.5,3.806,8.5,8.5c0,2.043-0.721,3.917-1.921,5.383"/><g><path fill="#98ccfd" d="M20 16.5A3.5 3.5 0 1 0 20 23.5A3.5 3.5 0 1 0 20 16.5Z"/><path fill="#4788c7" d="M20,17c1.654,0,3,1.346,3,3s-1.346,3-3,3s-3-1.346-3-3S18.346,17,20,17 M20,16 c-2.209,0-4,1.791-4,4s1.791,4,4,4s4-1.791,4-4S22.209,16,20,16L20,16z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -10,6 +10,7 @@ import { useSiteStore } from 'src/stores/site'
import { useUserStore } from 'src/stores/user'
import { setCssVar, useQuasar } from 'quasar'
import { useI18n } from 'vue-i18n'
import gql from 'graphql-tag'
import '@mdi/font/css/materialdesignicons.css'
@ -27,7 +28,7 @@ const userStore = useUserStore()
// I18N
const { t } = useI18n()
const i18n = useI18n({ useScope: 'global' })
// ROUTER
@ -88,6 +89,35 @@ async function applyTheme () {
}
}
// LOCALE
async function fetchLocaleStrings (locale) {
try {
const resp = await APOLLO_CLIENT.query({
query: gql`
query fetchLocaleStrings (
$locale: String!
) {
localeStrings (
locale: $locale
)
}
`,
fetchPolicy: 'cache-first',
variables: {
locale
}
})
return resp?.data?.localeStrings
} catch (err) {
console.warn(err)
$q.notify({
type: 'negative',
message: 'Failed to load locale strings.'
})
}
}
// INIT SITE STORE
if (typeof siteConfig !== 'undefined') {
@ -110,6 +140,10 @@ router.beforeEach(async (to, from) => {
await siteStore.loadSite(window.location.hostname)
console.info(`Using Site ID ${siteStore.id}`)
}
// Locales
if (!i18n.availableLocales.includes('en')) {
i18n.setLocaleMessage('en', await fetchLocaleStrings('en'))
}
// User Auth
await userStore.refreshAuth()
// User Profile
@ -128,7 +162,7 @@ EVENT_BUS.on('logout', () => {
$q.notify({
type: 'positive',
icon: 'las la-sign-out-alt',
message: t('auth.logoutSuccess')
message: i18n.t('auth.logoutSuccess')
})
})
EVENT_BUS.on('applyTheme', () => {

@ -1,12 +1,13 @@
import { boot } from 'quasar/wrappers'
import { createI18n } from 'vue-i18n'
import messages from 'src/i18n'
export default boot(({ app }) => {
const i18n = createI18n({
legacy: false,
locale: 'en-US',
messages
locale: 'en',
fallbackLocale: 'en',
fallbackWarn: false,
messages: {}
})
// Set i18n instance on app

@ -449,6 +449,10 @@ function processContent (newContent) {
})
}
function openEditorSettings () {
siteStore.$patch({ overlay: 'EditorMarkdownConfig' })
}
// MOUNTED
onMounted(async () => {
@ -601,6 +605,7 @@ onMounted(async () => {
})
EVENT_BUS.on('insertAsset', insertAssetClb)
EVENT_BUS.on('openEditorSettings', openEditorSettings)
// this.$root.$on('editorInsert', opts => {
// switch (opts.kind) {
@ -637,6 +642,7 @@ onMounted(async () => {
onBeforeUnmount(() => {
EVENT_BUS.off('insertAsset', insertAssetClb)
EVENT_BUS.off('openEditorSettings', openEditorSettings)
if (editor) {
editor.dispose()
}

@ -0,0 +1,196 @@
<template lang="pug">
q-layout(view='hHh lpR fFf', container)
q-header.card-header.q-px-md.q-py-sm
q-icon(name='img:/_assets/icons/ultraviolet-markdown.svg', left, size='md')
span {{t('editor.settings.markdown')}}
q-space
q-btn.q-mr-sm(
flat
rounded
color='white'
:aria-label='t(`common.actions.refresh`)'
icon='las la-question-circle'
:href='siteStore.docsBase + `/editor/markdown`'
target='_blank'
type='a'
)
q-btn-group(push)
q-btn(
push
color='grey-6'
text-color='white'
:aria-label='t(`common.actions.refresh`)'
icon='las la-redo-alt'
@click='load'
:loading='state.loading > 0'
)
q-tooltip(anchor='center left', self='center right') {{t(`common.actions.refresh`)}}
q-btn(
push
color='white'
text-color='grey-7'
:label='t(`common.actions.cancel`)'
:aria-label='t(`common.actions.cancel`)'
icon='las la-times'
@click='close'
)
q-btn(
push
color='positive'
text-color='white'
:label='t(`common.actions.apply`)'
:aria-label='t(`common.actions.apply`)'
icon='las la-check'
@click='save'
:disabled='state.loading > 0'
)
q-page-container
q-page.q-pa-md(style='max-width: 1200px; margin: 0 auto;')
q-card.shadow-1.q-py-sm
q-item(tag='label')
blueprint-icon(icon='enter-key')
q-item-section
q-item-label {{t(`editor.settings.previewShown`)}}
q-item-label(caption) {{t(`editor.settings.previewShownHint`)}}
q-item-section(avatar)
q-toggle(
v-model='state.config.previewShown'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='t(`editor.settings.previewShown`)'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='width')
q-item-section
q-item-label {{t(`editor.settings.fontSize`)}}
q-item-label(caption) {{t(`editor.settings.fontSizeHint`)}}
q-item-section(side)
q-input(
type='number'
min='10'
max='32'
style='width: 100px;'
outlined
v-model='state.config.fontSize'
dense
:aria-label='t(`editor.settings.fontSize`)'
)
q-inner-loading(:showing='state.loading > 0')
q-spinner(color='accent', size='lg')
</template>
<script setup>
import { useI18n } from 'vue-i18n'
import { useQuasar } from 'quasar'
import { onMounted, reactive } from 'vue'
import gql from 'graphql-tag'
import { cloneDeep } from 'lodash-es'
import { useEditorStore } from 'src/stores/editor'
import { useSiteStore } from 'src/stores/site'
import { useUserStore } from 'src/stores/user'
// QUASAR
const $q = useQuasar()
// STORES
const editorStore = useEditorStore()
const siteStore = useSiteStore()
const userStore = useUserStore()
// I18N
const { t } = useI18n()
// DATA
const state = reactive({
config: {
previewShown: false,
fontSize: 16
},
loading: 0
})
// METHODS
function close () {
siteStore.$patch({ overlay: '' })
}
async function load () {
state.loading++
$q.loading.show()
try {
const resp = await APOLLO_CLIENT.query({
query: gql`
query loadEditorUserSettings (
$editor: String!
) {
editorUserSettings (editor: "markdown")
}`,
fetchPolicy: 'network-only'
})
state.config = cloneDeep(resp?.data?.editorUserSettings)
} catch (err) {
$q.notify({
type: 'negative',
message: 'Failed to fetch Markdown editor settings.'
})
}
$q.loading.hide()
state.loading--
}
async function save () {
state.loading++
try {
const respRaw = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation saveEditorUserSettings (
$config: JSON!
) {
saveEditorUserSettings (
editor: "markdown"
config: $config
) {
operation {
succeeded
slug
message
}
}
}
`,
variables: {
config: state.config
}
})
if (respRaw?.data?.saveEditorUserSettings?.operation?.succeeded) {
$q.notify({
type: 'positive',
message: t('admin.editors.markdown.saveSuccess')
})
close()
} else {
throw new Error(respRaw?.data?.saveEditorUserSettings?.operation?.message || 'An unexpected error occured.')
}
} catch (err) {
$q.notify({
type: 'negative',
message: 'Failed to save Markdown editor settings.',
caption: err.message
})
}
state.loading--
}
onMounted(() => {
load()
})
</script>

@ -19,6 +19,10 @@ import { useSiteStore } from '../stores/site'
import LoadingGeneric from './LoadingGeneric.vue'
const overlays = {
EditorMarkdownConfig: defineAsyncComponent({
loader: () => import('./EditorMarkdownUserSettingsOverlay.vue'),
loadingComponent: LoadingGeneric
}),
FileManager: defineAsyncComponent({
loader: () => import('./FileManager.vue'),
loadingComponent: LoadingGeneric

@ -32,8 +32,8 @@ q-card.page-properties-dialog
)
q-scroll-area(
ref='scrollArea'
:thumb-style='siteStore.thumbStyle'
:bar-style='siteStore.barStyle'
:thumb-style='siteStore.scrollStyle.thumb'
:bar-style='siteStore.scrollStyle.bar'
style='height: calc(100% - 50px);'
)
q-card-section(id='refCardInfo')

@ -37,7 +37,7 @@ q-dialog(ref='dialogRef', @hide='onDialogHide')
q-icon(:name='item.icon', size='sm')
q-item-section
q-item-label {{item.title}}
.page-save-dialog-path.font-robotomono {{folderPath}}
.page-save-dialog-path.font-robotomono {{currentFolderPath}}
q-list.q-py-sm
q-item
blueprint-icon(icon='new-document')
@ -202,7 +202,7 @@ const barStyle = {
// COMPUTED
const folderPath = computed(() => {
const currentFolderPath = computed(() => {
if (!state.currentFolderId) {
return '/'
} else {

@ -1,5 +0,0 @@
import en from './locales/en.json'
export default {
'en-US': en
}

File diff suppressed because it is too large Load Diff

@ -228,6 +228,31 @@ q-page.admin-general
:options='reasonForChangeModes'
)
//- -----------------------
//- Defaults
//- -----------------------
q-card.q-pb-sm.q-mt-md(v-if='state.config.defaults')
q-card-section
.text-subtitle1 {{t('admin.general.defaults')}}
q-item
blueprint-icon(icon='depth')
q-item-section
q-item-label {{t(`admin.general.defaultTocDepth`)}}
q-item-label(caption) {{t(`admin.general.defaultTocDepthHint`)}}
q-item-section.col-auto.q-pl-sm(style='min-width: 180px;')
.text-caption {{t('editor.props.tocMinMaxDepth')}} #[strong (H{{state.config.defaults.tocDepth.min}} &rarr; H{{state.config.defaults.tocDepth.max}})]
q-range(
v-model='state.config.defaults.tocDepth'
:min='1'
:max='6'
color='primary'
:left-label-value='`H` + state.config.defaults.tocDepth.min'
:right-label-value='`H` + state.config.defaults.tocDepth.max'
snap
label
markers
)
.col-12.col-lg-5
//- -----------------------
//- Logo
@ -316,29 +341,24 @@ q-page.admin-general
.text-caption.q-ml-sm Dolor sit amet...
//- -----------------------
//- Defaults
//- Discovery
//- -----------------------
q-card.q-pb-sm.q-mt-md(v-if='state.config.defaults')
q-card.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1 {{t('admin.general.defaults')}}
q-item
blueprint-icon(icon='depth')
.text-subtitle1 {{t('admin.general.discovery')}}
q-item(tag='label')
blueprint-icon(icon='cellular-network')
q-item-section
q-item-label {{t(`admin.general.defaultTocDepth`)}}
q-item-label(caption) {{t(`admin.general.defaultTocDepthHint`)}}
q-item-section.col-auto.q-pl-sm(style='min-width: 180px;')
.text-caption {{t('editor.props.tocMinMaxDepth')}} #[strong (H{{state.config.defaults.tocDepth.min}} &rarr; H{{state.config.defaults.tocDepth.max}})]
q-range(
v-model='state.config.defaults.tocDepth'
:min='1'
:max='6'
q-item-label {{t(`admin.general.discoverable`)}}
q-item-label(caption) {{t(`admin.general.discoverableHint`)}}
q-item-section(avatar)
q-toggle(
v-model='state.config.discoverable'
color='primary'
:left-label-value='`H` + state.config.defaults.tocDepth.min'
:right-label-value='`H` + state.config.defaults.tocDepth.max'
snap
label
markers
)
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='t(`admin.general.discoverable`)'
)
//- -----------------------
//- Uploads
@ -519,6 +539,7 @@ const state = reactive({
reasonForChange: 'off',
profile: false
},
discoverable: false,
defaults: {
timezone: '',
dateFormat: '',
@ -618,6 +639,7 @@ async function load () {
reasonForChange
search
}
discoverable
defaults {
tocDepth {
min
@ -687,6 +709,7 @@ async function save () {
profile: state.config.features?.profile ?? false,
search: state.config.features?.search ?? false
},
discoverable: state.config.discoverable ?? false,
defaults: {
tocDepth: {
min: state.config.defaults?.tocDepth?.min ?? 1,

@ -60,7 +60,7 @@ q-page.admin-locale
q-item-section
q-select(
outlined
v-model='state.selectedLocale'
v-model='state.primary'
:options='installedLocales'
option-value='code'
option-label='name'
@ -185,9 +185,8 @@ useMeta({
const state = reactive({
loading: 0,
locales: [],
selectedLocale: 'en',
namespacing: false,
namespaces: []
primary: 'en',
active: []
})
// COMPUTED
@ -224,7 +223,7 @@ async function load () {
query: gql`
query getLocales ($siteId: UUID!) {
locales {
availability
completeness
code
createdAt
isInstalled
@ -232,15 +231,18 @@ async function load () {
isRTL
name
nativeName
region
script
updatedAt
}
siteById(
id: $siteId
) {
id
locale
localeNamespacing
localeNamespaces
locales {
primary
active
}
}
}
`,
@ -250,11 +252,10 @@ async function load () {
fetchPolicy: 'network-only'
})
state.locales = cloneDeep(resp?.data?.locales)
state.selectedLocale = cloneDeep(resp?.data?.siteById?.locale)
state.namespacing = cloneDeep(resp?.data?.siteById?.localeNamespacing)
state.namespaces = cloneDeep(resp?.data?.siteById?.localeNamespaces)
if (!state.namespaces.includes(state.selectedLocale)) {
state.namespaces.push(state.selectedLocale)
state.primary = cloneDeep(resp?.data?.siteById?.locales?.primary)
state.active = cloneDeep(resp?.data?.siteById?.locales?.active ?? [])
if (!state.active.includes(state.primary)) {
state.active.push(state.primary)
}
$q.loading.hide()
state.loading--

@ -320,6 +320,8 @@ export const usePageStore = defineStore('page', {
await editorStore.fetchConfigs()
}
const noDefaultPath = Boolean(!path && path !== '')
// -> Init editor
editorStore.$patch({
originPageId: editorStore.isActive ? editorStore.originPageId : this.id, // Don't replace if already in edit mode
@ -352,7 +354,9 @@ export const usePageStore = defineStore('page', {
mode: 'edit'
})
this.router.push(`/_create/${editor}`)
if (noDefaultPath) {
this.router.push(`/_create/${editor}`)
}
},
/**
* PAGE - EDIT

@ -2,6 +2,8 @@ import { defineStore } from 'pinia'
import gql from 'graphql-tag'
import { clone } from 'lodash-es'
import { useUserStore } from './user'
export const useSiteStore = defineStore('site', {
state: () => ({
routerLoading: false,
@ -53,25 +55,36 @@ export const useSiteStore = defineStore('site', {
showSharingMenu: true,
showPrintBtn: true
},
thumbStyle: {
right: '2px',
borderRadius: '5px',
backgroundColor: '#000',
width: '5px',
opacity: 0.15
},
barStyle: {
backgroundColor: '#FAFAFA',
width: '9px',
opacity: 1
},
sideDialogShown: false,
sideDialogComponent: '',
docsBase: 'https://next.js.wiki/docs'
}),
getters: {
overlayIsShown: (state) => Boolean(state.overlay),
sideNavIsDisabled: (state) => Boolean(state.theme.sidebarPosition === 'off')
sideNavIsDisabled: (state) => Boolean(state.theme.sidebarPosition === 'off'),
scrollStyle: (state) => {
const userStore = useUserStore()
let isDark = false
if (userStore.appearance === 'site') {
isDark = state.theme.dark
} else if (userStore.appearance === 'dark') {
isDark = true
}
return {
thumb: {
right: '2px',
borderRadius: '5px',
backgroundColor: isDark ? '#FFF' : '#000',
width: '5px',
opacity: isDark ? 0.25 : 0.15
},
bar: {
backgroundColor: isDark ? '#000' : '#FAFAFA',
width: '9px',
opacity: isDark ? 0.25 : 1
}
}
}
},
actions: {
openFileManager (opts) {

Loading…
Cancel
Save