feat: remove localeCode ref + new nav items + fetch current

pull/6775/head
NGPixel 1 year ago
parent 269040ed7f
commit 5eaba2cbb0
No known key found for this signature in database
GPG Key ID: B755FB6870B30F63

@ -188,7 +188,7 @@ export default {
name: 'API', name: 'API',
pictureUrl: null, pictureUrl: null,
timezone: 'America/New_York', timezone: 'America/New_York',
localeCode: 'en', locale: 'en',
permissions: _.get(WIKI.auth.groups, `${user.grp}.permissions`, []), permissions: _.get(WIKI.auth.groups, `${user.grp}.permissions`, []),
groups: [user.grp], groups: [user.grp],
getPermissions () { getPermissions () {

@ -178,8 +178,9 @@ export async function up (knex) {
}) })
// NAVIGATION ---------------------------- // NAVIGATION ----------------------------
.createTable('navigation', table => { .createTable('navigation', table => {
table.string('key').notNullable().primary() table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.jsonb('config') table.string('name').notNullable()
table.jsonb('items').notNullable().defaultTo('[]')
}) })
// PAGE HISTORY ------------------------ // PAGE HISTORY ------------------------
.createTable('pageHistory', table => { .createTable('pageHistory', table => {
@ -187,6 +188,7 @@ export async function up (knex) {
table.uuid('pageId').notNullable().index() table.uuid('pageId').notNullable().index()
table.string('action').defaultTo('updated') table.string('action').defaultTo('updated')
table.jsonb('affectedFields').notNullable().defaultTo('[]') table.jsonb('affectedFields').notNullable().defaultTo('[]')
table.string('locale', 10).notNullable().defaultTo('en')
table.string('path').notNullable() table.string('path').notNullable()
table.string('hash').notNullable() table.string('hash').notNullable()
table.string('alias') table.string('alias')
@ -211,11 +213,12 @@ export async function up (knex) {
.createTable('pageLinks', table => { .createTable('pageLinks', table => {
table.increments('id').primary() table.increments('id').primary()
table.string('path').notNullable() table.string('path').notNullable()
table.string('localeCode', 10).notNullable() table.string('locale', 10).notNullable()
}) })
// PAGES ------------------------------- // PAGES -------------------------------
.createTable('pages', table => { .createTable('pages', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()')) table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('locale', 10).notNullable()
table.string('path').notNullable() table.string('path').notNullable()
table.string('hash').notNullable() table.string('hash').notNullable()
table.string('alias') table.string('alias')
@ -293,7 +296,7 @@ export async function up (knex) {
table.string('fileName').notNullable().index() table.string('fileName').notNullable().index()
table.string('hash').notNullable().index() table.string('hash').notNullable().index()
table.enu('type', ['folder', 'page', 'asset']).notNullable().index() table.enu('type', ['folder', 'page', 'asset']).notNullable().index()
table.string('localeCode', 10).notNullable().defaultTo('en').index() table.string('locale', 10).notNullable().defaultTo('en').index()
table.string('title').notNullable() table.string('title').notNullable()
table.jsonb('meta').notNullable().defaultTo('{}') table.jsonb('meta').notNullable().defaultTo('{}')
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now()) table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
@ -369,16 +372,14 @@ export async function up (knex) {
table.uuid('siteId').notNullable().references('id').inTable('sites').index() table.uuid('siteId').notNullable().references('id').inTable('sites').index()
}) })
.table('pageHistory', table => { .table('pageHistory', table => {
table.string('localeCode', 10).references('code').inTable('locales')
table.uuid('authorId').notNullable().references('id').inTable('users') table.uuid('authorId').notNullable().references('id').inTable('users')
table.uuid('siteId').notNullable().references('id').inTable('sites').index() table.uuid('siteId').notNullable().references('id').inTable('sites').index()
}) })
.table('pageLinks', table => { .table('pageLinks', table => {
table.uuid('pageId').notNullable().references('id').inTable('pages').onDelete('CASCADE') table.uuid('pageId').notNullable().references('id').inTable('pages').onDelete('CASCADE')
table.index(['path', 'localeCode']) table.index(['path', 'locale'])
}) })
.table('pages', table => { .table('pages', table => {
table.string('localeCode', 10).references('code').inTable('locales').index()
table.uuid('authorId').notNullable().references('id').inTable('users').index() table.uuid('authorId').notNullable().references('id').inTable('users').index()
table.uuid('creatorId').notNullable().references('id').inTable('users').index() table.uuid('creatorId').notNullable().references('id').inTable('users').index()
table.uuid('ownerId').notNullable().references('id').inTable('users').index() table.uuid('ownerId').notNullable().references('id').inTable('users').index()
@ -392,6 +393,7 @@ export async function up (knex) {
table.unique(['siteId', 'tag']) table.unique(['siteId', 'tag'])
}) })
.table('tree', table => { .table('tree', table => {
table.uuid('navigationId').references('id').inTable('navigation').index()
table.uuid('siteId').notNullable().references('id').inTable('sites') table.uuid('siteId').notNullable().references('id').inTable('sites')
}) })
.table('userKeys', table => { .table('userKeys', table => {
@ -413,6 +415,7 @@ export async function up (knex) {
const groupAdminId = uuid() const groupAdminId = uuid()
const groupGuestId = '10000000-0000-4000-8000-000000000001' const groupGuestId = '10000000-0000-4000-8000-000000000001'
const navDefaultId = uuid()
const siteId = uuid() const siteId = uuid()
const authModuleId = uuid() const authModuleId = uuid()
const userAdminId = uuid() const userAdminId = uuid()
@ -619,6 +622,10 @@ export async function up (knex) {
config: {} config: {}
} }
}, },
nav: {
mode: 'mixed',
defaultId: navDefaultId,
},
theme: { theme: {
dark: false, dark: false,
codeBlocksTheme: 'github-dark', codeBlocksTheme: 'github-dark',
@ -747,6 +754,52 @@ export async function up (knex) {
} }
]) ])
// -> NAVIGATION
await knex('navigation').insert({
id: navDefaultId,
name: 'Default',
items: JSON.stringify([
{
id: uuid(),
type: 'header',
label: 'Sample Header'
},
{
id: uuid(),
type: 'link',
icon: 'mdi-file-document-outline',
label: 'Sample Link 1',
target: '/',
openInNewWindow: false,
children: []
},
{
id: uuid(),
type: 'link',
icon: 'mdi-book-open-variant',
label: 'Sample Link 2',
target: '/',
openInNewWindow: false,
children: []
},
{
id: uuid(),
type: 'separator',
},
{
id: uuid(),
type: 'link',
icon: 'mdi-airballoon',
label: 'Sample Link 3',
target: '/',
openInNewWindow: false,
children: []
}
]),
siteId: siteId
})
// -> STORAGE MODULE // -> STORAGE MODULE
await knex('storage').insert({ await knex('storage').insert({
@ -782,11 +835,11 @@ export async function up (knex) {
cron: '5 0 * * *', cron: '5 0 * * *',
type: 'system' type: 'system'
}, },
{ // {
task: 'refreshAutocomplete', // task: 'refreshAutocomplete',
cron: '0 */6 * * *', // cron: '0 */6 * * *',
type: 'system' // type: 'system'
}, // },
{ {
task: 'updateLocales', task: 'updateLocales',
cron: '0 0 * * *', cron: '0 0 * * *',

@ -170,7 +170,7 @@ export default {
folder = { folder = {
folderPath: '', folderPath: '',
fileName: '', fileName: '',
localeCode: args.locale, locale: args.locale,
siteId: args.siteId siteId: args.siteId
} }
} }
@ -263,7 +263,7 @@ export default {
parentPath: folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName, parentPath: folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName,
fileName: formattedFilename, fileName: formattedFilename,
title: formattedFilename, title: formattedFilename,
locale: folder.localeCode, locale: folder.locale,
siteId: folder.siteId, siteId: folder.siteId,
meta: { meta: {
authorId: asset.authorId, authorId: asset.authorId,

@ -32,7 +32,7 @@ export default {
* Fetch list of comments for a page * Fetch list of comments for a page
*/ */
async comments (obj, args, context) { async comments (obj, args, context) {
const page = await WIKI.db.pages.query().select('id').findOne({ localeCode: args.locale, path: args.path }) const page = await WIKI.db.pages.query().select('id').findOne({ locale: args.locale, path: args.path })
if (page) { if (page) {
if (WIKI.auth.checkAccess(context.req.user, ['read:comments'], args)) { if (WIKI.auth.checkAccess(context.req.user, ['read:comments'], args)) {
const comments = await WIKI.db.comments.query().where('pageId', page.id).orderBy('createdAt') const comments = await WIKI.db.comments.query().where('pageId', page.id).orderBy('createdAt')
@ -57,11 +57,11 @@ export default {
if (!cm || !cm.pageId) { if (!cm || !cm.pageId) {
throw new WIKI.Error.CommentNotFound() throw new WIKI.Error.CommentNotFound()
} }
const page = await WIKI.db.pages.query().select('localeCode', 'path').findById(cm.pageId) const page = await WIKI.db.pages.query().select('locale', 'path').findById(cm.pageId)
if (page) { if (page) {
if (WIKI.auth.checkAccess(context.req.user, ['read:comments'], { if (WIKI.auth.checkAccess(context.req.user, ['read:comments'], {
path: page.path, path: page.path,
locale: page.localeCode locale: page.locale
})) { })) {
return { return {
...cm, ...cm,

@ -2,22 +2,19 @@ import { generateError, generateSuccess } from '../../helpers/graph.mjs'
export default { export default {
Query: { Query: {
async navigationTree (obj, args, context, info) { async navigationById (obj, args, context, info) {
return WIKI.db.navigation.getTree({ cache: false, locale: 'all', bypassAuth: true }) return WIKI.db.navigation.getNav({ id: args.id, cache: true, userGroups: context.req.user?.groups })
},
navigationConfig (obj, args, context, info) {
return WIKI.config.nav
} }
}, },
Mutation: { Mutation: {
async updateNavigationTree (obj, args, context) { async updateNavigation (obj, args, context) {
try { try {
await WIKI.db.navigation.query().patch({ // await WIKI.db.navigation.query().patch({
config: args.tree // config: args.tree
}).where('key', 'site') // }).where('key', 'site')
for (const tree of args.tree) { // for (const tree of args.tree) {
await WIKI.cache.set(`nav:sidebar:${tree.locale}`, tree.items, 300) // await WIKI.cache.set(`nav:sidebar:${tree.locale}`, tree.items, 300)
} // }
return { return {
responseResult: generateSuccess('Navigation updated successfully') responseResult: generateSuccess('Navigation updated successfully')
@ -25,20 +22,6 @@ export default {
} catch (err) { } catch (err) {
return generateError(err) return generateError(err)
} }
},
async updateNavigationConfig (obj, args, context) {
try {
WIKI.config.nav = {
mode: args.mode
}
await WIKI.configSvc.saveToDb(['nav'])
return {
responseResult: generateSuccess('Navigation config updated successfully')
}
} catch (err) {
return generateError(err)
}
} }
} }
} }

@ -12,10 +12,10 @@ export default {
* PAGE HISTORY * PAGE HISTORY
*/ */
async pageHistoryById (obj, args, context, info) { async pageHistoryById (obj, args, context, info) {
const page = await WIKI.db.pages.query().select('path', 'localeCode').findById(args.id) const page = await WIKI.db.pages.query().select('path', 'locale').findById(args.id)
if (WIKI.auth.checkAccess(context.req.user, ['read:history'], { if (WIKI.auth.checkAccess(context.req.user, ['read:history'], {
path: page.path, path: page.path,
locale: page.localeCode locale: page.locale
})) { })) {
return WIKI.db.pageHistory.getHistory({ return WIKI.db.pageHistory.getHistory({
pageId: args.id, pageId: args.id,
@ -30,10 +30,10 @@ export default {
* PAGE VERSION * PAGE VERSION
*/ */
async pageVersionById (obj, args, context, info) { async pageVersionById (obj, args, context, info) {
const page = await WIKI.db.pages.query().select('path', 'localeCode').findById(args.pageId) const page = await WIKI.db.pages.query().select('path', 'locale').findById(args.pageId)
if (WIKI.auth.checkAccess(context.req.user, ['read:history'], { if (WIKI.auth.checkAccess(context.req.user, ['read:history'], {
path: page.path, path: page.path,
locale: page.localeCode locale: page.locale
})) { })) {
return WIKI.db.pageHistory.getVersion({ return WIKI.db.pageHistory.getVersion({
pageId: args.pageId, pageId: args.pageId,
@ -68,7 +68,7 @@ export default {
const searchCols = [ const searchCols = [
'id', 'id',
'path', 'path',
'localeCode AS locale', 'locale',
'title', 'title',
'description', 'description',
'icon', 'icon',
@ -99,7 +99,7 @@ export default {
builder.where('path', 'ILIKE', `${args.path}%`) builder.where('path', 'ILIKE', `${args.path}%`)
} }
if (args.locale?.length > 0) { if (args.locale?.length > 0) {
builder.whereIn('localeCode', args.locale) builder.whereIn('locale', args.locale)
} }
if (args.editor) { if (args.editor) {
builder.where('editor', args.editor) builder.where('editor', args.editor)
@ -143,7 +143,7 @@ export default {
let results = await WIKI.db.pages.query().column([ let results = await WIKI.db.pages.query().column([
'pages.id', 'pages.id',
'path', 'path',
{ locale: 'localeCode' }, 'locale',
'title', 'title',
'description', 'description',
'isPublished', 'isPublished',
@ -162,7 +162,7 @@ export default {
queryBuilder.limit(args.limit) queryBuilder.limit(args.limit)
} }
if (args.locale) { if (args.locale) {
queryBuilder.where('localeCode', args.locale) queryBuilder.where('locale', args.locale)
} }
if (args.creatorId && args.authorId && args.creatorId > 0 && args.authorId > 0) { if (args.creatorId && args.authorId && args.creatorId > 0 && args.authorId > 0) {
queryBuilder.where(function () { queryBuilder.where(function () {
@ -220,15 +220,14 @@ export default {
if (page) { if (page) {
if (WIKI.auth.checkAccess(context.req.user, ['read:pages'], { if (WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
path: page.path, path: page.path,
locale: page.localeCode locale: page.locale
})) { })) {
return { return {
...page, ...page,
...page.config, ...page.config,
scriptCss: page.scripts?.css, scriptCss: page.scripts?.css,
scriptJsLoad: page.scripts?.jsLoad, scriptJsLoad: page.scripts?.jsLoad,
scriptJsUnload: page.scripts?.jsUnload, scriptJsUnload: page.scripts?.jsUnload
locale: page.localeCode
} }
} else { } else {
throw new Error('ERR_FORBIDDEN') throw new Error('ERR_FORBIDDEN')
@ -253,8 +252,7 @@ export default {
...page.config, ...page.config,
scriptCss: page.scripts?.css, scriptCss: page.scripts?.css,
scriptJsLoad: page.scripts?.jsLoad, scriptJsLoad: page.scripts?.jsLoad,
scriptJsUnload: page.scripts?.jsUnload, scriptJsUnload: page.scripts?.jsUnload
locale: page.localeCode
} }
} else { } else {
throw new Error('ERR_PAGE_NOT_FOUND') throw new Error('ERR_PAGE_NOT_FOUND')
@ -275,13 +273,13 @@ export default {
const page = await WIKI.db.pages.query().findOne({ const page = await WIKI.db.pages.query().findOne({
alias: args.alias, alias: args.alias,
siteId: args.siteId siteId: args.siteId
}).select('id', 'path', 'localeCode') }).select('id', 'path', 'locale')
if (!page) { if (!page) {
throw new Error('ERR_ALIAS_NOT_FOUND') throw new Error('ERR_ALIAS_NOT_FOUND')
} }
return { return {
id: page.id, id: page.id,
path: WIKI.sites[args.siteId].config.localeNamespacing ? `${page.localeCode}/${page.path}` : page.path path: WIKI.sites[args.siteId].config.localeNamespacing ? `${page.locale}/${page.path}` : page.path
} }
}, },
@ -305,7 +303,7 @@ export default {
if (args.path && !args.parent) { if (args.path && !args.parent) {
curPage = await WIKI.db.knex('pageTree').first('parent', 'ancestors').where({ curPage = await WIKI.db.knex('pageTree').first('parent', 'ancestors').where({
path: args.path, path: args.path,
localeCode: args.locale locale: args.locale
}) })
if (curPage) { if (curPage) {
args.parent = curPage.parent || 0 args.parent = curPage.parent || 0
@ -315,7 +313,7 @@ export default {
} }
const results = await WIKI.db.knex('pageTree').where(builder => { const results = await WIKI.db.knex('pageTree').where(builder => {
builder.where('localeCode', args.locale) builder.where('locale', args.locale)
switch (args.mode) { switch (args.mode) {
case 'FOLDERS': case 'FOLDERS':
builder.andWhere('isFolder', true) builder.andWhere('isFolder', true)
@ -336,12 +334,12 @@ export default {
return results.filter(r => { return results.filter(r => {
return WIKI.auth.checkAccess(context.req.user, ['read:pages'], { return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
path: r.path, path: r.path,
locale: r.localeCode locale: r.locale
}) })
}).map(r => ({ }).map(r => ({
...r, ...r,
parent: r.parent || 0, parent: r.parent || 0,
locale: r.localeCode locale: r.locale
})) }))
}, },
/** /**
@ -352,25 +350,25 @@ export default {
if (WIKI.config.db.type === 'mysql' || WIKI.config.db.type === 'mariadb' || WIKI.config.db.type === 'sqlite') { if (WIKI.config.db.type === 'mysql' || WIKI.config.db.type === 'mariadb' || WIKI.config.db.type === 'sqlite') {
results = await WIKI.db.knex('pages') results = await WIKI.db.knex('pages')
.column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.localeCode' }) .column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.locale' })
.leftJoin('pageLinks', 'pages.id', 'pageLinks.pageId') .leftJoin('pageLinks', 'pages.id', 'pageLinks.pageId')
.where({ .where({
'pages.localeCode': args.locale 'pages.locale': args.locale
}) })
.unionAll( .unionAll(
WIKI.db.knex('pageLinks') WIKI.db.knex('pageLinks')
.column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.localeCode' }) .column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.locale' })
.leftJoin('pages', 'pageLinks.pageId', 'pages.id') .leftJoin('pages', 'pageLinks.pageId', 'pages.id')
.where({ .where({
'pages.localeCode': args.locale 'pages.locale': args.locale
}) })
) )
} else { } else {
results = await WIKI.db.knex('pages') results = await WIKI.db.knex('pages')
.column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.localeCode' }) .column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.locale' })
.fullOuterJoin('pageLinks', 'pages.id', 'pageLinks.pageId') .fullOuterJoin('pageLinks', 'pages.id', 'pageLinks.pageId')
.where({ .where({
'pages.localeCode': args.locale 'pages.locale': args.locale
}) })
} }
@ -403,11 +401,11 @@ export default {
* CHECK FOR EDITING CONFLICT * CHECK FOR EDITING CONFLICT
*/ */
async checkConflicts (obj, args, context, info) { async checkConflicts (obj, args, context, info) {
let page = await WIKI.db.pages.query().select('path', 'localeCode', 'updatedAt').findById(args.id) let page = await WIKI.db.pages.query().select('path', 'locale', 'updatedAt').findById(args.id)
if (page) { if (page) {
if (WIKI.auth.checkAccess(context.req.user, ['write:pages', 'manage:pages'], { if (WIKI.auth.checkAccess(context.req.user, ['write:pages', 'manage:pages'], {
path: page.path, path: page.path,
locale: page.localeCode locale: page.locale
})) { })) {
return page.updatedAt > args.checkoutDate return page.updatedAt > args.checkoutDate
} else { } else {
@ -425,12 +423,12 @@ export default {
if (page) { if (page) {
if (WIKI.auth.checkAccess(context.req.user, ['write:pages', 'manage:pages'], { if (WIKI.auth.checkAccess(context.req.user, ['write:pages', 'manage:pages'], {
path: page.path, path: page.path,
locale: page.localeCode locale: page.locale
})) { })) {
return { return {
...page, ...page,
tags: page.tags.map(t => t.tag), tags: page.tags.map(t => t.tag),
locale: page.localeCode locale: page.locale
} }
} else { } else {
throw new WIKI.Error.PageViewForbidden() throw new WIKI.Error.PageViewForbidden()
@ -626,14 +624,14 @@ export default {
*/ */
async restorePage (obj, args, context) { async restorePage (obj, args, context) {
try { try {
const page = await WIKI.db.pages.query().select('path', 'localeCode').findById(args.pageId) const page = await WIKI.db.pages.query().select('path', 'locale').findById(args.pageId)
if (!page) { if (!page) {
throw new WIKI.Error.PageNotFound() throw new WIKI.Error.PageNotFound()
} }
if (!WIKI.auth.checkAccess(context.req.user, ['write:pages'], { if (!WIKI.auth.checkAccess(context.req.user, ['write:pages'], {
path: page.path, path: page.path,
locale: page.localeCode locale: page.locale
})) { })) {
throw new WIKI.Error.PageRestoreForbidden() throw new WIKI.Error.PageRestoreForbidden()
} }

@ -142,7 +142,7 @@ export default {
.select(WIKI.db.knex.raw('tree.*, nlevel(tree."folderPath") AS depth')) .select(WIKI.db.knex.raw('tree.*, nlevel(tree."folderPath") AS depth'))
.where({ .where({
siteId: args.siteId, siteId: args.siteId,
localeCode: args.locale, locale: args.locale,
folderPath: _.dropRight(parentPathParts).join('.'), folderPath: _.dropRight(parentPathParts).join('.'),
fileName: _.last(parentPathParts) fileName: _.last(parentPathParts)
}) })

@ -3,8 +3,9 @@
# =============================================== # ===============================================
extend type Query { extend type Query {
navigationTree: [NavigationTree] navigationById(
navigationConfig: NavigationConfig id: UUID!
): [NavigationItem]
} }
# ----------------------------------------------- # -----------------------------------------------
@ -12,11 +13,10 @@ extend type Query {
# ----------------------------------------------- # -----------------------------------------------
extend type Mutation { extend type Mutation {
updateNavigationTree( updateNavigation(
tree: [NavigationTreeInput]! id: UUID!
): DefaultResponse name: String!
updateNavigationConfig( items: [JSON]!
mode: NavigationMode!
): DefaultResponse ): DefaultResponse
} }
@ -24,45 +24,12 @@ extend type Mutation {
# TYPES # TYPES
# ----------------------------------------------- # -----------------------------------------------
type NavigationTree {
locale: String
items: [NavigationItem]
}
input NavigationTreeInput {
locale: String!
items: [NavigationItemInput]!
}
type NavigationItem { type NavigationItem {
id: String id: UUID
kind: String type: String
label: String
icon: String
targetType: String
target: String
visibilityMode: String
visibilityGroups: [Int]
}
input NavigationItemInput {
id: String!
kind: String!
label: String label: String
icon: String icon: String
targetType: String
target: String target: String
visibilityMode: String openInNewWindow: Boolean
visibilityGroups: [Int] children: [NavigationItem]
}
type NavigationConfig {
mode: NavigationMode
}
enum NavigationMode {
NONE
TREE
MIXED
STATIC
} }

@ -237,6 +237,7 @@ type Page {
isBrowsable: Boolean isBrowsable: Boolean
isSearchable: Boolean isSearchable: Boolean
locale: String locale: String
navigationId: UUID
password: String password: String
path: String path: String
publishEndDate: Date publishEndDate: Date

@ -122,10 +122,10 @@ export function parseModuleProps (props) {
} }
export function getDictNameFromLocale (locale) { export function getDictNameFromLocale (locale) {
const localeCode = locale.length > 2 ? locale.substring(0, 2) : locale const loc = locale.length > 2 ? locale.substring(0, 2) : locale
if (localeCode in WIKI.config.search.dictOverrides) { if (loc in WIKI.config.search.dictOverrides) {
return WIKI.config.search.dictOverrides[localeCode] return WIKI.config.search.dictOverrides[loc]
} else { } else {
return WIKI.data.tsDictMappings[localeCode] ?? 'simple' return WIKI.data.tsDictMappings[loc] ?? 'simple'
} }
} }

@ -1683,7 +1683,9 @@
"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!",
"navEdit.clearItems": "Clear All Items",
"navEdit.editMenuItems": "Edit Menu Items", "navEdit.editMenuItems": "Edit Menu Items",
"navEdit.emptyMenuText": "Click the Add button to add your first menu item.",
"navEdit.header": "Header", "navEdit.header": "Header",
"navEdit.icon": "Icon", "navEdit.icon": "Icon",
"navEdit.iconHint": "Icon to display to the left of the menu item.", "navEdit.iconHint": "Icon to display to the left of the menu item.",
@ -1691,8 +1693,11 @@
"navEdit.labelHint": "Text to display on the menu item.", "navEdit.labelHint": "Text to display on the menu item.",
"navEdit.link": "Link", "navEdit.link": "Link",
"navEdit.nestItem": "Nest Item", "navEdit.nestItem": "Nest Item",
"navEdit.nestingWarn": "The previous menu item must be a normal link or another nested link. Invalid nested items will be shown in red.",
"navEdit.noSelection": "Select a menu item from the left to start editing.",
"navEdit.openInNewWindow": "Open in New Window", "navEdit.openInNewWindow": "Open in New Window",
"navEdit.openInNewWindowHint": "Whether the link should open in a new window / tab.", "navEdit.openInNewWindowHint": "Whether the link should open in a new window / tab.",
"navEdit.selectGroups": "Group(s):",
"navEdit.separator": "Separator", "navEdit.separator": "Separator",
"navEdit.target": "Target", "navEdit.target": "Target",
"navEdit.targetHint": "Target path or external link to point to.", "navEdit.targetHint": "Target path or external link to point to.",

@ -160,7 +160,7 @@ export class Asset extends Model {
.innerJoin('assets', 'tree.id', 'assets.id') .innerJoin('assets', 'tree.id', 'assets.id')
.where(id ? { 'tree.id': id } : { .where(id ? { 'tree.id': id } : {
'tree.hash': generateHash(path), 'tree.hash': generateHash(path),
'tree.localeCode': locale, 'tree.locale': locale,
'tree.siteId': siteId 'tree.siteId': siteId
}) })
.first() .first()

@ -99,7 +99,7 @@ export class Comment extends Model {
if (page) { if (page) {
if (!WIKI.auth.checkAccess(user, ['write:comments'], { if (!WIKI.auth.checkAccess(user, ['write:comments'], {
path: page.path, path: page.path,
locale: page.localeCode locale: page.locale
})) { })) {
throw new WIKI.Error.CommentPostForbidden() throw new WIKI.Error.CommentPostForbidden()
} }
@ -136,7 +136,7 @@ export class Comment extends Model {
if (page) { if (page) {
if (!WIKI.auth.checkAccess(user, ['manage:comments'], { if (!WIKI.auth.checkAccess(user, ['manage:comments'], {
path: page.path, path: page.path,
locale: page.localeCode locale: page.locale
})) { })) {
throw new WIKI.Error.CommentManageForbidden() throw new WIKI.Error.CommentManageForbidden()
} }
@ -169,7 +169,7 @@ export class Comment extends Model {
if (page) { if (page) {
if (!WIKI.auth.checkAccess(user, ['manage:comments'], { if (!WIKI.auth.checkAccess(user, ['manage:comments'], {
path: page.path, path: page.path,
locale: page.localeCode locale: page.locale
})) { })) {
throw new WIKI.Error.CommentManageForbidden() throw new WIKI.Error.CommentManageForbidden()
} }

@ -1,25 +1,38 @@
import { Model } from 'objection' import { Model } from 'objection'
import { has, intersection } from 'lodash-es' import { has, intersection, templateSettings } from 'lodash-es'
/** /**
* Navigation model * Navigation model
*/ */
export class Navigation extends Model { export class Navigation extends Model {
static get tableName() { return 'navigation' } static get tableName() { return 'navigation' }
static get idColumn() { return 'key' }
static get jsonSchema () { static get jsonSchema () {
return { return {
type: 'object', type: 'object',
required: ['key'],
properties: { properties: {
key: {type: 'string'}, name: {type: 'string'},
config: {type: 'array', items: {type: 'object'}} items: {type: 'array', items: {type: 'object'}}
} }
} }
} }
static async getNav ({ id, cache = false, userGroups = [] }) {
const result = await WIKI.db.navigation.query().findById(id).select('items')
return result.items.filter(item => {
return !item.visibilityGroups?.length || intersection(item.visibilityGroups, userGroups).length > 0
}).map(item => {
if (!item.children || item.children?.length < 1) { return item }
return {
...item,
children: item.children.filter(child => {
return !child.visibilityGroups?.length || intersection(child.visibilityGroups, userGroups).length > 0
})
}
})
}
static async getTree({ cache = false, locale = 'en', groups = [], bypassAuth = false } = {}) { static async getTree({ cache = false, locale = 'en', groups = [], bypassAuth = false } = {}) {
if (cache) { if (cache) {
const navTreeCached = await WIKI.cache.get(`nav:sidebar:${locale}`) const navTreeCached = await WIKI.cache.get(`nav:sidebar:${locale}`)

@ -69,7 +69,7 @@ export class PageHistory extends Model {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Locale, modelClass: Locale,
join: { join: {
from: 'pageHistory.localeCode', from: 'pageHistory.locale',
to: 'locales.code' to: 'locales.code'
} }
} }
@ -94,7 +94,7 @@ export class PageHistory extends Model {
editor: opts.editor, editor: opts.editor,
hash: opts.hash, hash: opts.hash,
publishState: opts.publishState, publishState: opts.publishState,
localeCode: opts.localeCode, locale: opts.locale,
path: opts.path, path: opts.path,
publishEndDate: opts.publishEndDate?.toISO(), publishEndDate: opts.publishEndDate?.toISO(),
publishStartDate: opts.publishStartDate?.toISO(), publishStartDate: opts.publishStartDate?.toISO(),
@ -126,7 +126,7 @@ export class PageHistory extends Model {
{ {
versionId: 'pageHistory.id', versionId: 'pageHistory.id',
editor: 'pageHistory.editorKey', editor: 'pageHistory.editorKey',
locale: 'pageHistory.localeCode', locale: 'pageHistory.locale',
authorName: 'author.name' authorName: 'author.name'
} }
]) ])

@ -11,12 +11,12 @@ export class PageLink extends Model {
static get jsonSchema () { static get jsonSchema () {
return { return {
type: 'object', type: 'object',
required: ['path', 'localeCode'], required: ['path', 'locale'],
properties: { properties: {
id: {type: 'integer'}, id: {type: 'integer'},
path: {type: 'string'}, path: {type: 'string'},
localeCode: {type: 'string'} locale: {type: 'string'}
} }
} }
} }

@ -99,14 +99,6 @@ export class Page extends Model {
from: 'pages.creatorId', from: 'pages.creatorId',
to: 'users.id' to: 'users.id'
} }
},
locale: {
relation: Model.BelongsToOneRelation,
modelClass: Locale,
join: {
from: 'pages.localeCode',
to: 'locales.code'
}
} }
} }
} }
@ -267,7 +259,7 @@ export class Page extends Model {
// -> Check for duplicate // -> Check for duplicate
const dupCheck = await WIKI.db.pages.query().findOne({ const dupCheck = await WIKI.db.pages.query().findOne({
siteId: opts.siteId, siteId: opts.siteId,
localeCode: opts.locale, locale: opts.locale,
path: opts.path path: opts.path
}).select('id') }).select('id')
if (dupCheck) { if (dupCheck) {
@ -345,7 +337,7 @@ export class Page extends Model {
icon: opts.icon, icon: opts.icon,
isBrowsable: opts.isBrowsable ?? true, isBrowsable: opts.isBrowsable ?? true,
isSearchable: opts.isSearchable ?? true, isSearchable: opts.isSearchable ?? true,
localeCode: opts.locale, locale: opts.locale,
ownerId: opts.user.id, ownerId: opts.user.id,
path: opts.path, path: opts.path,
publishState: opts.publishState, publishState: opts.publishState,
@ -372,7 +364,7 @@ export class Page extends Model {
id: page.id, id: page.id,
parentPath: initial(pathParts).join('/'), parentPath: initial(pathParts).join('/'),
fileName: last(pathParts), fileName: last(pathParts),
locale: page.localeCode, locale: page.locale,
title: page.title, title: page.title,
meta: { meta: {
authorId: page.authorId, authorId: page.authorId,
@ -401,7 +393,7 @@ export class Page extends Model {
// // -> Reconnect Links // // -> Reconnect Links
// await WIKI.db.pages.reconnectLinks({ // await WIKI.db.pages.reconnectLinks({
// locale: page.localeCode, // locale: page.locale,
// path: page.path, // path: page.path,
// mode: 'create' // mode: 'create'
// }) // })
@ -427,7 +419,7 @@ export class Page extends Model {
// -> Check for page access // -> Check for page access
if (!WIKI.auth.checkAccess(opts.user, ['write:pages'], { if (!WIKI.auth.checkAccess(opts.user, ['write:pages'], {
locale: ogPage.localeCode, locale: ogPage.locale,
path: ogPage.path path: ogPage.path
})) { })) {
throw new Error('ERR_PAGE_UPDATE_FORBIDDEN') throw new Error('ERR_PAGE_UPDATE_FORBIDDEN')
@ -596,7 +588,7 @@ export class Page extends Model {
// -> Format CSS Scripts // -> Format CSS Scripts
if (opts.patch.scriptCss) { if (opts.patch.scriptCss) {
if (WIKI.auth.checkAccess(opts.user, ['write:styles'], { if (WIKI.auth.checkAccess(opts.user, ['write:styles'], {
locale: ogPage.localeCode, locale: ogPage.locale,
path: ogPage.path path: ogPage.path
})) { })) {
patch.scripts = { patch.scripts = {
@ -610,7 +602,7 @@ export class Page extends Model {
// -> Format JS Scripts // -> Format JS Scripts
if (opts.patch.scriptJsLoad) { if (opts.patch.scriptJsLoad) {
if (WIKI.auth.checkAccess(opts.user, ['write:scripts'], { if (WIKI.auth.checkAccess(opts.user, ['write:scripts'], {
locale: ogPage.localeCode, locale: ogPage.locale,
path: ogPage.path path: ogPage.path
})) { })) {
patch.scripts = { patch.scripts = {
@ -622,7 +614,7 @@ export class Page extends Model {
} }
if (opts.patch.scriptJsUnload) { if (opts.patch.scriptJsUnload) {
if (WIKI.auth.checkAccess(opts.user, ['write:scripts'], { if (WIKI.auth.checkAccess(opts.user, ['write:scripts'], {
locale: ogPage.localeCode, locale: ogPage.locale,
path: ogPage.path path: ogPage.path
})) { })) {
patch.scripts = { patch.scripts = {
@ -701,11 +693,11 @@ export class Page extends Model {
if (!id) { if (!id) {
throw new Error('Must provide either the page ID or the page object.') throw new Error('Must provide either the page ID or the page object.')
} }
page = await WIKI.db.pages.query().findById(id).select('id', 'localeCode', 'render', 'password') page = await WIKI.db.pages.query().findById(id).select('id', 'locale', 'render', 'password')
} }
// -> Exclude password-protected content from being indexed // -> Exclude password-protected content from being indexed
const safeContent = page.password ? '' : WIKI.db.pages.cleanHTML(page.render) const safeContent = page.password ? '' : WIKI.db.pages.cleanHTML(page.render)
const dictName = getDictNameFromLocale(page.localeCode) const dictName = getDictNameFromLocale(page.locale)
return WIKI.db.knex('pages').where('id', page.id).update({ return WIKI.db.knex('pages').where('id', page.id).update({
searchContent: safeContent, searchContent: safeContent,
ts: WIKI.db.knex.raw(` ts: WIKI.db.knex.raw(`
@ -747,7 +739,7 @@ export class Page extends Model {
// -> Check for page access // -> Check for page access
if (!WIKI.auth.checkAccess(opts.user, ['write:pages'], { if (!WIKI.auth.checkAccess(opts.user, ['write:pages'], {
locale: ogPage.localeCode, locale: ogPage.locale,
path: ogPage.path path: ogPage.path
})) { })) {
throw new WIKI.Error.PageUpdateForbidden() throw new WIKI.Error.PageUpdateForbidden()
@ -908,7 +900,7 @@ export class Page extends Model {
} else { } else {
page = await WIKI.db.pages.query().findOne({ page = await WIKI.db.pages.query().findOne({
path: opts.path, path: opts.path,
localeCode: opts.locale locale: opts.locale
}) })
} }
if (!page) { if (!page) {
@ -932,7 +924,7 @@ export class Page extends Model {
// -> Check for source page access // -> Check for source page access
if (!WIKI.auth.checkAccess(opts.user, ['manage:pages'], { if (!WIKI.auth.checkAccess(opts.user, ['manage:pages'], {
locale: page.localeCode, locale: page.locale,
path: page.path path: page.path
})) { })) {
throw new WIKI.Error.PageMoveForbidden() throw new WIKI.Error.PageMoveForbidden()
@ -948,7 +940,7 @@ export class Page extends Model {
// -> Check for existing page at destination path // -> Check for existing page at destination path
const destPage = await WIKI.db.pages.query().findOne({ const destPage = await WIKI.db.pages.query().findOne({
path: opts.destinationPath, path: opts.destinationPath,
localeCode: opts.destinationLocale locale: opts.destinationLocale
}) })
if (destPage) { if (destPage) {
throw new WIKI.Error.PagePathCollision() throw new WIKI.Error.PagePathCollision()
@ -967,7 +959,7 @@ export class Page extends Model {
const destinationTitle = (page.title === page.path ? opts.destinationPath : page.title) const destinationTitle = (page.title === page.path ? opts.destinationPath : page.title)
await WIKI.db.pages.query().patch({ await WIKI.db.pages.query().patch({
path: opts.destinationPath, path: opts.destinationPath,
localeCode: opts.destinationLocale, locale: opts.destinationLocale,
title: destinationTitle, title: destinationTitle,
hash: destinationHash hash: destinationHash
}).findById(page.id) }).findById(page.id)
@ -983,7 +975,7 @@ export class Page extends Model {
await WIKI.data.searchEngine.renamed({ await WIKI.data.searchEngine.renamed({
...page, ...page,
destinationPath: opts.destinationPath, destinationPath: opts.destinationPath,
destinationLocaleCode: opts.destinationLocale, destinationLocale: opts.destinationLocale,
destinationHash destinationHash
}) })
@ -994,7 +986,7 @@ export class Page extends Model {
page: { page: {
...page, ...page,
destinationPath: opts.destinationPath, destinationPath: opts.destinationPath,
destinationLocaleCode: opts.destinationLocale, destinationLocale: opts.destinationLocale,
destinationHash, destinationHash,
moveAuthorId: opts.user.id, moveAuthorId: opts.user.id,
moveAuthorName: opts.user.name, moveAuthorName: opts.user.name,
@ -1005,7 +997,7 @@ export class Page extends Model {
// -> Reconnect Links : Changing old links to the new path // -> Reconnect Links : Changing old links to the new path
await WIKI.db.pages.reconnectLinks({ await WIKI.db.pages.reconnectLinks({
sourceLocale: page.localeCode, sourceLocale: page.locale,
sourcePath: page.path, sourcePath: page.path,
locale: opts.destinationLocale, locale: opts.destinationLocale,
path: opts.destinationPath, path: opts.destinationPath,
@ -1066,7 +1058,7 @@ export class Page extends Model {
// -> Reconnect Links // -> Reconnect Links
await WIKI.db.pages.reconnectLinks({ await WIKI.db.pages.reconnectLinks({
locale: page.localeCode, locale: page.locale,
path: page.path, path: page.path,
mode: 'delete' mode: 'delete'
}) })
@ -1118,7 +1110,7 @@ export class Page extends Model {
.whereIn('pages.id', function () { .whereIn('pages.id', function () {
this.select('pageLinks.pageId').from('pageLinks').where({ this.select('pageLinks.pageId').from('pageLinks').where({
'pageLinks.path': opts.path, 'pageLinks.path': opts.path,
'pageLinks.localeCode': opts.locale 'pageLinks.locale': opts.locale
}) })
}) })
affectedHashes = qryHashes.map(h => h.hash) affectedHashes = qryHashes.map(h => h.hash)
@ -1131,7 +1123,7 @@ export class Page extends Model {
.whereIn('pages.id', function () { .whereIn('pages.id', function () {
this.select('pageLinks.pageId').from('pageLinks').where({ this.select('pageLinks.pageId').from('pageLinks').where({
'pageLinks.path': opts.path, 'pageLinks.path': opts.path,
'pageLinks.localeCode': opts.locale 'pageLinks.locale': opts.locale
}) })
}) })
const qryHashes = await WIKI.db.pages.query() const qryHashes = await WIKI.db.pages.query()
@ -1139,7 +1131,7 @@ export class Page extends Model {
.whereIn('pages.id', function () { .whereIn('pages.id', function () {
this.select('pageLinks.pageId').from('pageLinks').where({ this.select('pageLinks.pageId').from('pageLinks').where({
'pageLinks.path': opts.path, 'pageLinks.path': opts.path,
'pageLinks.localeCode': opts.locale 'pageLinks.locale': opts.locale
}) })
}) })
affectedHashes = qryHashes.map(h => h.hash) affectedHashes = qryHashes.map(h => h.hash)
@ -1227,20 +1219,18 @@ export class Page extends Model {
authorEmail: 'author.email', authorEmail: 'author.email',
creatorName: 'creator.name', creatorName: 'creator.name',
creatorEmail: 'creator.email' creatorEmail: 'creator.email'
} },
'tree.navigationId'
]) ])
.joinRelated('author') .joinRelated('author')
.joinRelated('creator') .joinRelated('creator')
// .withGraphJoined('tags') .leftJoin('tree', 'pages.id', 'tree.id')
// .modifyGraph('tags', builder => {
// builder.select('tag')
// })
.where(queryModeID ? { .where(queryModeID ? {
'pages.id': opts 'pages.id': opts
} : { } : {
'pages.siteId': opts.siteId, 'pages.siteId': opts.siteId,
'pages.path': opts.path, 'pages.path': opts.path,
'pages.localeCode': opts.locale 'pages.locale': opts.locale
}) })
// .andWhere(builder => { // .andWhere(builder => {
// if (queryModeID) return // if (queryModeID) return
@ -1307,7 +1297,7 @@ export class Page extends Model {
return { return {
...page, ...page,
path: opts.path, path: opts.path,
localeCode: opts.locale locale: opts.locale
} }
} catch (err) { } catch (err) {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
@ -1346,13 +1336,13 @@ export class Page extends Model {
static async migrateToLocale({ sourceLocale, targetLocale }) { static async migrateToLocale({ sourceLocale, targetLocale }) {
return WIKI.db.pages.query() return WIKI.db.pages.query()
.patch({ .patch({
localeCode: targetLocale locale: targetLocale
}) })
.where({ .where({
localeCode: sourceLocale locale: sourceLocale
}) })
.whereNotExists(function() { .whereNotExists(function() {
this.select('id').from('pages AS pagesm').where('pagesm.localeCode', targetLocale).andWhereRaw('pagesm.path = pages.path') this.select('id').from('pages AS pagesm').where('pagesm.locale', targetLocale).andWhereRaw('pagesm.path = pages.path')
}) })
} }

@ -1,5 +1,6 @@
import { Model } from 'objection' import { Model } from 'objection'
import { defaultsDeep, keyBy } from 'lodash-es' import { defaultsDeep, keyBy } from 'lodash-es'
import { v4 as uuid } from 'uuid'
/** /**
* Site model * Site model
@ -47,7 +48,16 @@ export class Site extends Model {
} }
static async createSite (hostname, config) { static async createSite (hostname, config) {
const newSiteId = uuid
const newDefaultNav = await WIKI.db.navigation.query().insertAndFetch({
name: 'Default',
siteId: newSiteId,
items: JSON.stringify([])
})
const newSite = await WIKI.db.sites.query().insertAndFetch({ const newSite = await WIKI.db.sites.query().insertAndFetch({
id: newSiteId,
hostname, hostname,
isEnabled: true, isEnabled: true,
config: defaultsDeep(config, { config: defaultsDeep(config, {
@ -138,6 +148,10 @@ export class Site extends Model {
config: {} config: {}
} }
}, },
nav: {
mode: 'mixed',
defaultId: newDefaultNav.id,
},
uploads: { uploads: {
conflictBehavior: 'overwrite', conflictBehavior: 'overwrite',
normalizeFilename: true normalizeFilename: true

@ -43,14 +43,6 @@ export class Tree extends Model {
static get relationMappings() { static get relationMappings() {
return { return {
locale: {
relation: Model.BelongsToOneRelation,
modelClass: Locale,
join: {
from: 'tree.localeCode',
to: 'locales.code'
}
},
site: { site: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Site, modelClass: Site,
@ -99,7 +91,7 @@ export class Tree extends Model {
const parent = await WIKI.db.knex('tree').where({ const parent = await WIKI.db.knex('tree').where({
...parentFilter, ...parentFilter,
type: 'folder', type: 'folder',
localeCode: locale, locale: locale,
siteId siteId
}).first() }).first()
if (parent) { if (parent) {
@ -153,7 +145,7 @@ export class Tree extends Model {
type: 'page', type: 'page',
title: title, title: title,
hash: generateHash(fullPath), hash: generateHash(fullPath),
localeCode: locale, locale: locale,
siteId, siteId,
meta meta
}).returning('*') }).returning('*')
@ -196,7 +188,7 @@ export class Tree extends Model {
type: 'asset', type: 'asset',
title: title, title: title,
hash: generateHash(fullPath), hash: generateHash(fullPath),
localeCode: locale, locale: locale,
siteId, siteId,
meta meta
}).returning('*') }).returning('*')
@ -251,7 +243,7 @@ export class Tree extends Model {
// Check for collision // Check for collision
const existingFolder = await WIKI.db.knex('tree').select('id').where({ const existingFolder = await WIKI.db.knex('tree').select('id').where({
siteId: siteId, siteId: siteId,
localeCode: locale, locale: locale,
folderPath: encodeFolderPath(parentPath), folderPath: encodeFolderPath(parentPath),
fileName: pathName fileName: pathName
}).first() }).first()
@ -284,7 +276,7 @@ export class Tree extends Model {
type: 'folder', type: 'folder',
title: ancestor.fileName, title: ancestor.fileName,
hash: generateHash(newAncestorFullPath), hash: generateHash(newAncestorFullPath),
localeCode: locale, locale: locale,
siteId: siteId, siteId: siteId,
meta: { meta: {
children: 1 children: 1
@ -306,7 +298,7 @@ export class Tree extends Model {
type: 'folder', type: 'folder',
title: title, title: title,
hash: generateHash(fullPath), hash: generateHash(fullPath),
localeCode: locale, locale: locale,
siteId: siteId, siteId: siteId,
meta: { meta: {
children: 0 children: 0

@ -53,14 +53,6 @@ export class User extends Model {
}, },
to: 'groups.id' to: 'groups.id'
} }
},
locale: {
relation: Model.BelongsToOneRelation,
modelClass: Locale,
join: {
from: 'users.localeCode',
to: 'locales.code'
}
} }
} }
} }
@ -232,7 +224,7 @@ export class User extends Model {
email: primaryEmail, email: primaryEmail,
name: displayName, name: displayName,
pictureUrl: pictureUrl, pictureUrl: pictureUrl,
localeCode: WIKI.config.lang.code, locale: WIKI.config.lang.code,
defaultEditor: 'markdown', defaultEditor: 'markdown',
tfaIsActive: false, tfaIsActive: false,
isSystem: false, isSystem: false,

@ -89,7 +89,7 @@ module.exports = {
content, content,
name: user.name, name: user.name,
email: user.email, email: user.email,
permalink: `${WIKI.config.host}/${page.localeCode}/${page.path}`, permalink: `${WIKI.config.host}/${page.locale}/${page.path}`,
permalinkDate: page.updatedAt, permalinkDate: page.updatedAt,
type: (replyTo > 0) ? 'reply' : 'comment', type: (replyTo > 0) ? 'reply' : 'comment',
role: userRole role: userRole

@ -68,12 +68,12 @@ export async function render () {
// -> Reformat paths // -> Reformat paths
if (href.indexOf('/') !== 0) { if (href.indexOf('/') !== 0) {
if (this.config.absoluteLinks) { if (this.config.absoluteLinks) {
href = `/${this.page.localeCode}/${href}` href = `/${this.page.locale}/${href}`
} else { } else {
href = (this.page.path === 'home') ? `/${this.page.localeCode}/${href}` : `/${this.page.localeCode}/${this.page.path}/${href}` href = (this.page.path === 'home') ? `/${this.page.locale}/${href}` : `/${this.page.locale}/${this.page.path}/${href}`
} }
} else if (href.charAt(3) !== '/') { } else if (href.charAt(3) !== '/') {
href = `/${this.page.localeCode}${href}` href = `/${this.page.locale}${href}`
} }
try { try {
@ -101,7 +101,7 @@ export async function render () {
} }
// -> Save internal references // -> Save internal references
internalRefs.push({ internalRefs.push({
localeCode: pagePath.locale, locale: pagePath.locale,
path: pagePath.path path: pagePath.path
}) })
@ -127,7 +127,7 @@ export async function render () {
if (internalRefs.length > 0) { if (internalRefs.length > 0) {
// -> Find matching pages // -> Find matching pages
const results = await WIKI.db.pages.query().column('id', 'path', 'localeCode').where(builder => { const results = await WIKI.db.pages.query().column('id', 'path', 'locale').where(builder => {
internalRefs.forEach((ref, idx) => { internalRefs.forEach((ref, idx) => {
if (idx < 1) { if (idx < 1) {
builder.where(ref) builder.where(ref)
@ -148,7 +148,7 @@ export async function render () {
return return
} }
if (_.some(results, r => { if (_.some(results, r => {
return r.localeCode === hrefObj.locale && r.path === hrefObj.path return r.locale === hrefObj.locale && r.path === hrefObj.path
})) { })) {
$(elm).addClass(`is-valid-page`) $(elm).addClass(`is-valid-page`)
} else { } else {
@ -158,21 +158,21 @@ export async function render () {
// -> Add missing links // -> Add missing links
const missingLinks = _.differenceWith(internalRefs, pastLinks, (nLink, pLink) => { const missingLinks = _.differenceWith(internalRefs, pastLinks, (nLink, pLink) => {
return nLink.localeCode === pLink.localeCode && nLink.path === pLink.path return nLink.locale === pLink.locale && nLink.path === pLink.path
}) })
if (missingLinks.length > 0) { if (missingLinks.length > 0) {
if (WIKI.config.db.type === 'postgres') { if (WIKI.config.db.type === 'postgres') {
await WIKI.db.pageLinks.query().insert(missingLinks.map(lnk => ({ await WIKI.db.pageLinks.query().insert(missingLinks.map(lnk => ({
pageId: this.page.id, pageId: this.page.id,
path: lnk.path, path: lnk.path,
localeCode: lnk.localeCode locale: lnk.locale
}))) })))
} else { } else {
for (const lnk of missingLinks) { for (const lnk of missingLinks) {
await WIKI.db.pageLinks.query().insert({ await WIKI.db.pageLinks.query().insert({
pageId: this.page.id, pageId: this.page.id,
path: lnk.path, path: lnk.path,
localeCode: lnk.localeCode locale: lnk.locale
}) })
} }
} }
@ -182,7 +182,7 @@ export async function render () {
// -> Remove outdated links // -> Remove outdated links
if (pastLinks) { if (pastLinks) {
const outdatedLinks = _.differenceWith(pastLinks, internalRefs, (nLink, pLink) => { const outdatedLinks = _.differenceWith(pastLinks, internalRefs, (nLink, pLink) => {
return nLink.localeCode === pLink.localeCode && nLink.path === pLink.path return nLink.locale === pLink.locale && nLink.path === pLink.path
}) })
if (outdatedLinks.length > 0) { if (outdatedLinks.length > 0) {
await WIKI.db.pageLinks.query().delete().whereIn('id', _.map(outdatedLinks, 'id')) await WIKI.db.pageLinks.query().delete().whereIn('id', _.map(outdatedLinks, 'id'))

@ -9,7 +9,7 @@ export async function task ({ payload }) {
let idx = 0 let idx = 0
await pipeline( await pipeline(
WIKI.db.knex.select('id', 'title', 'description', 'localeCode', 'render', 'password').from('pages').stream(), WIKI.db.knex.select('id', 'title', 'description', 'locale', 'render', 'password').from('pages').stream(),
new Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (page, enc, cb) => { transform: async (page, enc, cb) => {

@ -125,7 +125,7 @@ import { computed, onMounted, reactive } from 'vue'
// PROPS // PROPS
const props = defineProps({ const props = defineProps({
value: { modelValue: {
type: String, type: String,
required: true required: true
} }
@ -133,7 +133,7 @@ const props = defineProps({
// EMITS // EMITS
const emit = defineEmits(['input']) const emit = defineEmits(['update:modelValue'])
// DATA // DATA
@ -169,24 +169,24 @@ const iconPackRefWebsite = computed(() => {
function apply () { function apply () {
if (state.currentTab === 'img') { if (state.currentTab === 'img') {
emit('input', `img:${state.imgPath}`) emit('update:modelValue', `img:${state.imgPath}`)
} else { } else {
emit('input', state.iconName) emit('update:modelValue', state.iconName)
} }
} }
// MOUNTED // MOUNTED
onMounted(() => { onMounted(() => {
if (props.value?.startsWith('img:')) { if (props.modelValue?.startsWith('img:')) {
state.currentTab = 'img' state.currentTab = 'img'
state.imgPath = props.value.substring(4) state.imgPath = props.modelValue.substring(4)
} else { } else {
state.currentTab = 'icon' state.currentTab = 'icon'
for (const pack of iconPacks) { for (const pack of iconPacks) {
if (props.value?.startsWith(pack.prefix)) { if (props.value?.startsWith(pack.prefix)) {
state.selPack = pack.value state.selPack = pack.value
state.selIcon = props.value.substring(pack.prefix.length) state.selIcon = props.modelValue.substring(pack.prefix.length)
break break
} }
} }

@ -16,19 +16,19 @@ q-card(style='min-width: 350px')
q-list(padding) q-list(padding)
q-item(tag='label') q-item(tag='label')
q-item-section(side) q-item-section(side)
q-radio(v-model='state.mode', val='inherit') q-radio(v-model='state.mode', val='inherit', :disable='isRoot')
q-item-section q-item-section
q-item-label Inherit q-item-label Inherit
q-item-label(caption) Use the menu items and settings from the parent path. q-item-label(caption) Use the menu items and settings from the parent path.
q-item(tag='label') q-item(tag='label')
q-item-section(side) q-item-section(side)
q-radio(v-model='state.mode', val='starting') q-radio(v-model='state.mode', val='starting', :disable='isRoot')
q-item-section q-item-section
q-item-label Override Current + Descendants q-item-label Override Current + Descendants
q-item-label(caption) Set menu items and settings for this path and all children. q-item-label(caption) Set menu items and settings for this path and all children.
q-item(tag='label') q-item(tag='label')
q-item-section(side) q-item-section(side)
q-radio(v-model='state.mode', val='exact') q-radio(v-model='state.mode', val='exact', :disable='isRoot')
q-item-section q-item-section
q-item-label Override Current Only q-item-label Override Current Only
q-item-label(caption) Set menu items and settings only for this path. q-item-label(caption) Set menu items and settings only for this path.
@ -50,9 +50,10 @@ q-card(style='min-width: 350px')
</template> </template>
<script setup> <script setup>
import { onMounted, reactive, ref, watch } from 'vue' import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { usePageStore } from 'src/stores/page'
import { useSiteStore } from 'src/stores/site' import { useSiteStore } from 'src/stores/site'
// PROPS // PROPS
@ -66,6 +67,7 @@ const props = defineProps({
// STORES // STORES
const pageStore = usePageStore()
const siteStore = useSiteStore() const siteStore = useSiteStore()
// I18N // I18N

@ -4,6 +4,12 @@ q-layout(view='hHh lpR fFf', container)
q-icon(name='img:/_assets/icons/fluent-sidebar-menu.svg', left, size='md') q-icon(name='img:/_assets/icons/fluent-sidebar-menu.svg', left, size='md')
span {{t(`navEdit.editMenuItems`)}} span {{t(`navEdit.editMenuItems`)}}
q-space q-space
transition(name='syncing')
q-spinner-tail.q-mr-sm(
v-show='state.loading > 0'
color='accent'
size='24px'
)
q-btn.q-mr-sm( q-btn.q-mr-sm(
flat flat
rounded rounded
@ -81,39 +87,65 @@ q-layout(view='hHh lpR fFf', container)
q-item-section(side) q-item-section(side)
q-icon.handle(name='mdi-drag-horizontal', size='sm') q-icon.handle(name='mdi-drag-horizontal', size='sm')
.q-pa-md .q-pa-md.flex
q-btn.full-width.acrylic-btn( q-btn.acrylic-btn(
style='flex: 1;'
flat flat
color='positive' color='positive'
:label='t(`common.actions.add`)' :label='t(`common.actions.add`)'
:aria-label='t(`common.actions.add`)' :aria-label='t(`common.actions.add`)'
icon='las la-plus-circle' icon='las la-plus-circle'
) )
q-menu(fit, :offset='[0, 10]') q-menu(fit, :offset='[0, 10]', auto-close)
q-list(separator) q-list(separator)
q-item(clickable) q-item(clickable, @click='addItem(`header`)')
q-item-section(side) q-item-section(side)
q-icon(name='las la-heading') q-icon(name='las la-heading')
q-item-section q-item-section
q-item-label Header q-item-label {{t('navEdit.header')}}
q-item(clickable) q-item(clickable, @click='addItem(`link`)')
q-item-section(side) q-item-section(side)
q-icon(name='las la-link') q-icon(name='las la-link')
q-item-section q-item-section
q-item-label {{t('navEdit.link')}} q-item-label {{t('navEdit.link')}}
q-item(clickable) q-item(clickable, @click='addItem(`separator`)')
q-item-section(side) q-item-section(side)
q-icon(name='las la-minus') q-icon(name='las la-minus')
q-item-section q-item-section
q-item-label Separator q-item-label {{t('navEdit.separator')}}
q-item(clickable, style='border-top-width: 5px;') q-btn.q-ml-sm.acrylic-btn(
flat
color='grey'
:aria-label='t(`common.actions.add`)'
icon='las la-ellipsis-v'
padding='xs sm'
)
q-menu(:offset='[0, 10]' anchor='bottom right' self='top right' auto-close)
q-list(separator)
q-item(clickable, @click='clearItems', :disable='state.items.length < 1')
q-item-section(side) q-item-section(side)
q-icon(name='mdi-import') q-icon(name='las la-trash-alt', color='negative')
q-item-section q-item-section
q-item-label Copy from... q-item-label {{t('navEdit.clearItems')}}
//- q-item(clickable)
//- q-item-section(side)
//- q-icon(name='mdi-import')
//- q-item-section
//- q-item-label Copy from...
q-page-container q-page-container
q-page.q-pa-md q-page.q-pa-md
template(v-if='state.items.length < 1')
q-card
q-card-section
q-icon.q-mr-sm(name='las la-arrow-left', size='xs')
span {{ t('navEdit.emptyMenuText') }}
template(v-else-if='!state.selected')
q-card
q-card-section
q-icon.q-mr-sm(name='las la-arrow-left', size='xs')
span {{ t('navEdit.noSelection') }}
template(v-if='state.current.type === `header`') template(v-if='state.current.type === `header`')
q-card.q-pb-sm q-card.q-pb-sm
q-card-section q-card-section
@ -135,10 +167,11 @@ q-layout(view='hHh lpR fFf', container)
q-space q-space
q-btn.acrylic-btn( q-btn.acrylic-btn(
flat flat
icon='las la-trash-alt'
:label='t(`common.actions.delete`)' :label='t(`common.actions.delete`)'
color='negative' color='negative'
padding='xs md' padding='xs md'
@click='' @click='removeItem(state.current.id)'
) )
template(v-if='state.current.type === `link`') template(v-if='state.current.type === `link`')
@ -176,6 +209,8 @@ q-layout(view='hHh lpR fFf', container)
name='las la-icons' name='las la-icons'
color='primary' color='primary'
) )
q-menu(content-class='shadow-7')
icon-picker-dialog(v-model='state.current.icon')
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='link') blueprint-icon(icon='link')
@ -219,24 +254,25 @@ q-layout(view='hHh lpR fFf', container)
toggle-color='primary' toggle-color='primary'
:options='visibilityOptions' :options='visibilityOptions'
) )
q-item(v-if='state.current.visibilityLimited') q-item.items-center(v-if='state.current.visibilityLimited')
q-item-section q-space
q-item-section .text-caption.q-mr-md {{ t('navEdit.selectGroups') }}
q-select( q-select(
style='width: 100%; max-width: calc(50% - 34px);'
outlined outlined
v-model='state.current.visibility' v-model='state.current.visibilityGroups'
:options='state.groups' :options='state.groups'
option-value='value' option-value='id'
option-label='label' option-label='name'
emit-value emit-value
map-options map-options
dense dense
options-dense multiple
:virtual-scroll-slice-size='1000' :aria-label='t(`navEdit.selectGroups`)'
:aria-label='t(`admin.general.uploadConflictBehavior`)'
) )
q-card.q-pa-md.q-mt-md.flex q-card.q-pa-md.q-mt-md.flex.items-start
div
q-btn.acrylic-btn( q-btn.acrylic-btn(
v-if='state.current.isNested' v-if='state.current.isNested'
flat flat
@ -255,13 +291,15 @@ q-layout(view='hHh lpR fFf', container)
padding='xs md' padding='xs md'
@click='state.current.isNested = true' @click='state.current.isNested = true'
) )
.text-caption.q-mt-md.text-grey-7 {{ t('navEdit.nestingWarn') }}
q-space q-space
q-btn.acrylic-btn( q-btn.acrylic-btn(
flat flat
icon='las la-trash-alt'
:label='t(`common.actions.delete`)' :label='t(`common.actions.delete`)'
color='negative' color='negative'
padding='xs md' padding='xs md'
@click='' @click='removeItem(state.current.id)'
) )
template(v-if='state.current.type === `separator`') template(v-if='state.current.type === `separator`')
@ -272,10 +310,11 @@ q-layout(view='hHh lpR fFf', container)
q-space q-space
q-btn.acrylic-btn( q-btn.acrylic-btn(
flat flat
icon='las la-trash-alt'
:label='t(`common.actions.delete`)' :label='t(`common.actions.delete`)'
color='negative' color='negative'
padding='xs md' padding='xs md'
@click='' @click='removeItem(state.current.id)'
) )
</template> </template>
@ -284,6 +323,7 @@ q-layout(view='hHh lpR fFf', container)
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useQuasar } from 'quasar' import { useQuasar } from 'quasar'
import { onMounted, reactive, ref } from 'vue' import { onMounted, reactive, ref } from 'vue'
import { v4 as uuid } from 'uuid'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
@ -291,6 +331,8 @@ import { useSiteStore } from 'src/stores/site'
import { Sortable } from 'sortablejs-vue3' import { Sortable } from 'sortablejs-vue3'
import IconPickerDialog from 'src/components/IconPickerDialog.vue'
// QUASAR // QUASAR
const $q = useQuasar() const $q = useQuasar()
@ -307,66 +349,8 @@ const { t } = useI18n()
const state = reactive({ const state = reactive({
loading: 0, loading: 0,
selected: '3', selected: null,
items: [ items: [],
{
id: '1',
type: 'header',
label: 'General'
},
{
id: '2',
type: 'link',
label: 'Dogs',
icon: 'las la-dog'
},
{
id: '3',
type: 'link',
label: 'Cats',
icon: 'las la-cat'
},
{
id: '4',
type: 'separator'
},
{
id: '5',
type: 'header',
label: 'User Guide'
},
{
id: '6',
type: 'link',
label: 'Editing Pages',
icon: 'las la-file-alt'
},
{
id: '7',
type: 'link',
label: 'Permissions',
icon: 'las la-key',
isNested: true
},
{
id: '8',
type: 'link',
label: 'Supersuperlongtitleveryveryversupersupersupersupersuper long word',
icon: 'las la-key'
},
{
id: '9',
type: 'link',
label: 'Users',
icon: 'las la-users'
},
{
id: '10',
type: 'link',
label: 'Locales',
icon: 'las la-globe'
}
],
current: { current: {
label: '', label: '',
icon: '', icon: '',
@ -409,12 +393,67 @@ function setItem (item) {
state.current = item state.current = item
} }
function addItem (type) {
const newItem = {
id: uuid(),
type
}
switch (type) {
case 'header': {
newItem.label = t('navEdit.header')
break
}
case 'link': {
newItem.label = t('navEdit.link')
newItem.icon = 'mdi-text-box-outline'
newItem.target = '/'
newItem.openInNewWindow = false
newItem.visibilityGroups = []
newItem.visibilityLimited = false
newItem.isNested = false
break
}
}
state.items.push(newItem)
state.selected = newItem.id
state.current = newItem
}
function removeItem (id) {
state.items = state.items.filter(item => item.id !== id)
state.selected = null
state.current = {}
}
function clearItems () {
state.items = []
state.selected = null
state.current = {}
}
function close () { function close () {
siteStore.$patch({ overlay: '' }) siteStore.$patch({ overlay: '' })
} }
onMounted(() => { async function loadGroups () {
state.loading++
const resp = await APOLLO_CLIENT.query({
query: gql`
query getGroupsForEditNavMenu {
groups {
id
name
}
}
`,
fetchPolicy: 'network-only'
})
state.groups = cloneDeep(resp?.data?.groups ?? [])
state.loading--
}
onMounted(() => {
loadGroups()
}) })
</script> </script>

@ -8,20 +8,22 @@ q-scroll-area.sidebar-nav(
dense dense
dark dark
) )
q-item-label.text-blue-2.text-caption(header) Header template(v-for='item of siteStore.nav.items')
q-item(to='/install') q-item-label.text-blue-2.text-caption.text-wordbreak-all(
q-item-section(side) v-if='item.type === `header`'
q-icon(name='las la-dog', color='white') header
q-item-section Link 1 ) {{ item.label }}
q-item(to='/install') q-item(
q-item-section(side) v-else-if='item.type === `link`'
q-icon(name='las la-cat', color='white') :to='item.target'
q-item-section Link 2 )
q-separator.q-my-sm(dark)
q-item(to='/install')
q-item-section(side) q-item-section(side)
q-icon(name='mdi-fruit-grapes', color='white') q-icon(:name='item.icon', color='white')
q-item-section.text-wordbreak-all Link 3 q-item-section.text-wordbreak-all.text-white {{ item.label }}
q-separator.q-my-sm(
v-else-if='item.type === `separator`'
dark
)
</template> </template>
<script setup> <script setup>
@ -30,6 +32,7 @@ import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { usePageStore } from 'src/stores/page'
import { useSiteStore } from 'src/stores/site' import { useSiteStore } from 'src/stores/site'
// QUASAR // QUASAR
@ -38,6 +41,7 @@ const $q = useQuasar()
// STORES // STORES
const pageStore = usePageStore()
const siteStore = useSiteStore() const siteStore = useSiteStore()
// ROUTER // ROUTER
@ -63,6 +67,15 @@ const barStyle = {
width: '9px', width: '9px',
opacity: 0.1 opacity: 0.1
} }
// WATCHERS
watch(() => pageStore.navigationId, (newValue) => {
if (newValue !== siteStore.nav.currentId) {
siteStore.fetchNavigation(newValue)
}
}, { immediate: true })
</script> </script>
<style lang="scss"> <style lang="scss">

@ -33,6 +33,14 @@ q-layout(view='hHh Lpr lff')
nav-sidebar nav-sidebar
q-bar.bg-blue-9.text-white(dense, v-if='userStore.authenticated') q-bar.bg-blue-9.text-white(dense, v-if='userStore.authenticated')
q-btn.col( q-btn.col(
v-if='isRoot'
icon='las la-dharmachakra'
label='Edit Nav'
flat
@click='siteStore.$patch({ overlay: `NavEdit` })'
)
q-btn.col(
v-else
icon='las la-dharmachakra' icon='las la-dharmachakra'
label='Edit Nav' label='Edit Nav'
flat flat
@ -44,7 +52,6 @@ q-layout(view='hHh Lpr lff')
:offset='[0, 10]' :offset='[0, 10]'
) )
nav-edit-menu(:menu-hide-handler='navEditMenu.hide') nav-edit-menu(:menu-hide-handler='navEditMenu.hide')
q-separator(vertical) q-separator(vertical)
q-btn.col( q-btn.col(
icon='las la-bookmark' icon='las la-bookmark'
@ -78,6 +85,7 @@ import { useI18n } from 'vue-i18n'
import { useCommonStore } from 'src/stores/common' import { useCommonStore } from 'src/stores/common'
import { useEditorStore } from 'src/stores/editor' import { useEditorStore } from 'src/stores/editor'
import { useFlagsStore } from 'src/stores/flags' import { useFlagsStore } from 'src/stores/flags'
import { usePageStore } from 'src/stores/page'
import { useSiteStore } from 'src/stores/site' import { useSiteStore } from 'src/stores/site'
import { useUserStore } from 'src/stores/user' import { useUserStore } from 'src/stores/user'
@ -99,6 +107,7 @@ const $q = useQuasar()
const commonStore = useCommonStore() const commonStore = useCommonStore()
const editorStore = useEditorStore() const editorStore = useEditorStore()
const flagsStore = useFlagsStore() const flagsStore = useFlagsStore()
const pageStore = usePageStore()
const siteStore = useSiteStore() const siteStore = useSiteStore()
const userStore = useUserStore() const userStore = useUserStore()
@ -127,6 +136,10 @@ const isSidebarShown = computed(() => {
return siteStore.showSideNav && !siteStore.sideNavIsDisabled && !(editorStore.isActive && editorStore.hideSideNav) return siteStore.showSideNav && !siteStore.sideNavIsDisabled && !(editorStore.isActive && editorStore.hideSideNav)
}) })
const isRoot = computed(() => {
return pageStore.path === '' || pageStore.path === 'home'
})
</script> </script>
<style lang="scss"> <style lang="scss">

@ -21,6 +21,7 @@ const pagePropsFragment = gql`
isBrowsable isBrowsable
isSearchable isSearchable
locale locale
navigationId
password password
path path
publishEndDate publishEndDate
@ -197,6 +198,7 @@ export const usePageStore = defineStore('page', {
isBrowsable: true, isBrowsable: true,
isSearchable: true, isSearchable: true,
locale: 'en', locale: 'en',
navigationId: null,
password: '', password: '',
path: '', path: '',
publishEndDate: '', publishEndDate: '',

@ -65,7 +65,10 @@ export const useSiteStore = defineStore('site', {
sideDialogShown: false, sideDialogShown: false,
sideDialogComponent: '', sideDialogComponent: '',
docsBase: 'https://next.js.wiki/docs', docsBase: 'https://next.js.wiki/docs',
nav: {} nav: {
currentId: null,
items: []
}
}), }),
getters: { getters: {
overlayIsShown: (state) => Boolean(state.overlay), overlayIsShown: (state) => Boolean(state.overlay),
@ -236,6 +239,44 @@ export const useSiteStore = defineStore('site', {
console.warn(err.networkError?.result ?? err.message) console.warn(err.networkError?.result ?? err.message)
throw err throw err
} }
},
async fetchNavigation (id) {
try {
const resp = await APOLLO_CLIENT.query({
query: gql`
query getNavigationItems ($id: UUID!) {
navigationById (
id: $id
) {
id
type
label
icon
target
openInNewWindow
children {
id
type
label
icon
target
openInNewWindow
}
}
}
`,
variables: { id }
})
this.$patch({
nav: {
currentId: id,
items: resp?.data?.navigationById ?? []
}
})
} catch (err) {
console.warn(err.networkError?.result ?? err.message)
throw err
}
} }
} }
}) })

Loading…
Cancel
Save