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',
pictureUrl: null,
timezone: 'America/New_York',
localeCode: 'en',
locale: 'en',
permissions: _.get(WIKI.auth.groups, `${user.grp}.permissions`, []),
groups: [user.grp],
getPermissions () {

@ -178,8 +178,9 @@ export async function up (knex) {
})
// NAVIGATION ----------------------------
.createTable('navigation', table => {
table.string('key').notNullable().primary()
table.jsonb('config')
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('name').notNullable()
table.jsonb('items').notNullable().defaultTo('[]')
})
// PAGE HISTORY ------------------------
.createTable('pageHistory', table => {
@ -187,6 +188,7 @@ export async function up (knex) {
table.uuid('pageId').notNullable().index()
table.string('action').defaultTo('updated')
table.jsonb('affectedFields').notNullable().defaultTo('[]')
table.string('locale', 10).notNullable().defaultTo('en')
table.string('path').notNullable()
table.string('hash').notNullable()
table.string('alias')
@ -211,11 +213,12 @@ export async function up (knex) {
.createTable('pageLinks', table => {
table.increments('id').primary()
table.string('path').notNullable()
table.string('localeCode', 10).notNullable()
table.string('locale', 10).notNullable()
})
// PAGES -------------------------------
.createTable('pages', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('locale', 10).notNullable()
table.string('path').notNullable()
table.string('hash').notNullable()
table.string('alias')
@ -293,7 +296,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', 10).notNullable().defaultTo('en').index()
table.string('locale', 10).notNullable().defaultTo('en').index()
table.string('title').notNullable()
table.jsonb('meta').notNullable().defaultTo('{}')
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('pageHistory', table => {
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()
})
.table('pageLinks', table => {
table.uuid('pageId').notNullable().references('id').inTable('pages').onDelete('CASCADE')
table.index(['path', 'localeCode'])
table.index(['path', 'locale'])
})
.table('pages', table => {
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()
@ -392,6 +393,7 @@ export async function up (knex) {
table.unique(['siteId', 'tag'])
})
.table('tree', table => {
table.uuid('navigationId').references('id').inTable('navigation').index()
table.uuid('siteId').notNullable().references('id').inTable('sites')
})
.table('userKeys', table => {
@ -413,6 +415,7 @@ export async function up (knex) {
const groupAdminId = uuid()
const groupGuestId = '10000000-0000-4000-8000-000000000001'
const navDefaultId = uuid()
const siteId = uuid()
const authModuleId = uuid()
const userAdminId = uuid()
@ -619,6 +622,10 @@ export async function up (knex) {
config: {}
}
},
nav: {
mode: 'mixed',
defaultId: navDefaultId,
},
theme: {
dark: false,
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
await knex('storage').insert({
@ -782,11 +835,11 @@ export async function up (knex) {
cron: '5 0 * * *',
type: 'system'
},
{
task: 'refreshAutocomplete',
cron: '0 */6 * * *',
type: 'system'
},
// {
// task: 'refreshAutocomplete',
// cron: '0 */6 * * *',
// type: 'system'
// },
{
task: 'updateLocales',
cron: '0 0 * * *',

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

@ -32,7 +32,7 @@ export default {
* Fetch list of comments for a page
*/
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 (WIKI.auth.checkAccess(context.req.user, ['read:comments'], args)) {
const comments = await WIKI.db.comments.query().where('pageId', page.id).orderBy('createdAt')
@ -57,11 +57,11 @@ export default {
if (!cm || !cm.pageId) {
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 (WIKI.auth.checkAccess(context.req.user, ['read:comments'], {
path: page.path,
locale: page.localeCode
locale: page.locale
})) {
return {
...cm,

@ -2,22 +2,19 @@ import { generateError, generateSuccess } from '../../helpers/graph.mjs'
export default {
Query: {
async navigationTree (obj, args, context, info) {
return WIKI.db.navigation.getTree({ cache: false, locale: 'all', bypassAuth: true })
},
navigationConfig (obj, args, context, info) {
return WIKI.config.nav
async navigationById (obj, args, context, info) {
return WIKI.db.navigation.getNav({ id: args.id, cache: true, userGroups: context.req.user?.groups })
}
},
Mutation: {
async updateNavigationTree (obj, args, context) {
async updateNavigation (obj, args, context) {
try {
await WIKI.db.navigation.query().patch({
config: args.tree
}).where('key', 'site')
for (const tree of args.tree) {
await WIKI.cache.set(`nav:sidebar:${tree.locale}`, tree.items, 300)
}
// await WIKI.db.navigation.query().patch({
// config: args.tree
// }).where('key', 'site')
// for (const tree of args.tree) {
// await WIKI.cache.set(`nav:sidebar:${tree.locale}`, tree.items, 300)
// }
return {
responseResult: generateSuccess('Navigation updated successfully')
@ -25,20 +22,6 @@ export default {
} catch (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
*/
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'], {
path: page.path,
locale: page.localeCode
locale: page.locale
})) {
return WIKI.db.pageHistory.getHistory({
pageId: args.id,
@ -30,10 +30,10 @@ export default {
* PAGE VERSION
*/
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'], {
path: page.path,
locale: page.localeCode
locale: page.locale
})) {
return WIKI.db.pageHistory.getVersion({
pageId: args.pageId,
@ -68,7 +68,7 @@ export default {
const searchCols = [
'id',
'path',
'localeCode AS locale',
'locale',
'title',
'description',
'icon',
@ -99,7 +99,7 @@ export default {
builder.where('path', 'ILIKE', `${args.path}%`)
}
if (args.locale?.length > 0) {
builder.whereIn('localeCode', args.locale)
builder.whereIn('locale', args.locale)
}
if (args.editor) {
builder.where('editor', args.editor)
@ -143,7 +143,7 @@ export default {
let results = await WIKI.db.pages.query().column([
'pages.id',
'path',
{ locale: 'localeCode' },
'locale',
'title',
'description',
'isPublished',
@ -162,7 +162,7 @@ export default {
queryBuilder.limit(args.limit)
}
if (args.locale) {
queryBuilder.where('localeCode', args.locale)
queryBuilder.where('locale', args.locale)
}
if (args.creatorId && args.authorId && args.creatorId > 0 && args.authorId > 0) {
queryBuilder.where(function () {
@ -220,15 +220,14 @@ export default {
if (page) {
if (WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
path: page.path,
locale: page.localeCode
locale: page.locale
})) {
return {
...page,
...page.config,
scriptCss: page.scripts?.css,
scriptJsLoad: page.scripts?.jsLoad,
scriptJsUnload: page.scripts?.jsUnload,
locale: page.localeCode
scriptJsUnload: page.scripts?.jsUnload
}
} else {
throw new Error('ERR_FORBIDDEN')
@ -253,8 +252,7 @@ export default {
...page.config,
scriptCss: page.scripts?.css,
scriptJsLoad: page.scripts?.jsLoad,
scriptJsUnload: page.scripts?.jsUnload,
locale: page.localeCode
scriptJsUnload: page.scripts?.jsUnload
}
} else {
throw new Error('ERR_PAGE_NOT_FOUND')
@ -275,13 +273,13 @@ export default {
const page = await WIKI.db.pages.query().findOne({
alias: args.alias,
siteId: args.siteId
}).select('id', 'path', 'localeCode')
}).select('id', 'path', 'locale')
if (!page) {
throw new Error('ERR_ALIAS_NOT_FOUND')
}
return {
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) {
curPage = await WIKI.db.knex('pageTree').first('parent', 'ancestors').where({
path: args.path,
localeCode: args.locale
locale: args.locale
})
if (curPage) {
args.parent = curPage.parent || 0
@ -315,7 +313,7 @@ export default {
}
const results = await WIKI.db.knex('pageTree').where(builder => {
builder.where('localeCode', args.locale)
builder.where('locale', args.locale)
switch (args.mode) {
case 'FOLDERS':
builder.andWhere('isFolder', true)
@ -336,12 +334,12 @@ export default {
return results.filter(r => {
return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
path: r.path,
locale: r.localeCode
locale: r.locale
})
}).map(r => ({
...r,
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') {
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')
.where({
'pages.localeCode': args.locale
'pages.locale': args.locale
})
.unionAll(
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')
.where({
'pages.localeCode': args.locale
'pages.locale': args.locale
})
)
} else {
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')
.where({
'pages.localeCode': args.locale
'pages.locale': args.locale
})
}
@ -403,11 +401,11 @@ export default {
* CHECK FOR EDITING CONFLICT
*/
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 (WIKI.auth.checkAccess(context.req.user, ['write:pages', 'manage:pages'], {
path: page.path,
locale: page.localeCode
locale: page.locale
})) {
return page.updatedAt > args.checkoutDate
} else {
@ -425,12 +423,12 @@ export default {
if (page) {
if (WIKI.auth.checkAccess(context.req.user, ['write:pages', 'manage:pages'], {
path: page.path,
locale: page.localeCode
locale: page.locale
})) {
return {
...page,
tags: page.tags.map(t => t.tag),
locale: page.localeCode
locale: page.locale
}
} else {
throw new WIKI.Error.PageViewForbidden()
@ -626,14 +624,14 @@ export default {
*/
async restorePage (obj, args, context) {
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) {
throw new WIKI.Error.PageNotFound()
}
if (!WIKI.auth.checkAccess(context.req.user, ['write:pages'], {
path: page.path,
locale: page.localeCode
locale: page.locale
})) {
throw new WIKI.Error.PageRestoreForbidden()
}

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

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

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

@ -122,10 +122,10 @@ export function parseModuleProps (props) {
}
export function getDictNameFromLocale (locale) {
const localeCode = locale.length > 2 ? locale.substring(0, 2) : locale
if (localeCode in WIKI.config.search.dictOverrides) {
return WIKI.config.search.dictOverrides[localeCode]
const loc = locale.length > 2 ? locale.substring(0, 2) : locale
if (loc in WIKI.config.search.dictOverrides) {
return WIKI.config.search.dictOverrides[loc]
} 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.confirmTitle": "Restore page version?",
"history.restore.success": "Page version restored succesfully!",
"navEdit.clearItems": "Clear All Items",
"navEdit.editMenuItems": "Edit Menu Items",
"navEdit.emptyMenuText": "Click the Add button to add your first menu item.",
"navEdit.header": "Header",
"navEdit.icon": "Icon",
"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.link": "Link",
"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.openInNewWindowHint": "Whether the link should open in a new window / tab.",
"navEdit.selectGroups": "Group(s):",
"navEdit.separator": "Separator",
"navEdit.target": "Target",
"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')
.where(id ? { 'tree.id': id } : {
'tree.hash': generateHash(path),
'tree.localeCode': locale,
'tree.locale': locale,
'tree.siteId': siteId
})
.first()

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

@ -1,25 +1,38 @@
import { Model } from 'objection'
import { has, intersection } from 'lodash-es'
import { has, intersection, templateSettings } from 'lodash-es'
/**
* Navigation model
*/
export class Navigation extends Model {
static get tableName() { return 'navigation' }
static get idColumn() { return 'key' }
static get jsonSchema () {
return {
type: 'object',
required: ['key'],
properties: {
key: {type: 'string'},
config: {type: 'array', items: {type: 'object'}}
name: {type: 'string'},
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 } = {}) {
if (cache) {
const navTreeCached = await WIKI.cache.get(`nav:sidebar:${locale}`)

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

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

@ -99,14 +99,6 @@ export class Page extends Model {
from: 'pages.creatorId',
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
const dupCheck = await WIKI.db.pages.query().findOne({
siteId: opts.siteId,
localeCode: opts.locale,
locale: opts.locale,
path: opts.path
}).select('id')
if (dupCheck) {
@ -345,7 +337,7 @@ export class Page extends Model {
icon: opts.icon,
isBrowsable: opts.isBrowsable ?? true,
isSearchable: opts.isSearchable ?? true,
localeCode: opts.locale,
locale: opts.locale,
ownerId: opts.user.id,
path: opts.path,
publishState: opts.publishState,
@ -372,7 +364,7 @@ export class Page extends Model {
id: page.id,
parentPath: initial(pathParts).join('/'),
fileName: last(pathParts),
locale: page.localeCode,
locale: page.locale,
title: page.title,
meta: {
authorId: page.authorId,
@ -401,7 +393,7 @@ export class Page extends Model {
// // -> Reconnect Links
// await WIKI.db.pages.reconnectLinks({
// locale: page.localeCode,
// locale: page.locale,
// path: page.path,
// mode: 'create'
// })
@ -427,7 +419,7 @@ export class Page extends Model {
// -> Check for page access
if (!WIKI.auth.checkAccess(opts.user, ['write:pages'], {
locale: ogPage.localeCode,
locale: ogPage.locale,
path: ogPage.path
})) {
throw new Error('ERR_PAGE_UPDATE_FORBIDDEN')
@ -596,7 +588,7 @@ export class Page extends Model {
// -> Format CSS Scripts
if (opts.patch.scriptCss) {
if (WIKI.auth.checkAccess(opts.user, ['write:styles'], {
locale: ogPage.localeCode,
locale: ogPage.locale,
path: ogPage.path
})) {
patch.scripts = {
@ -610,7 +602,7 @@ export class Page extends Model {
// -> Format JS Scripts
if (opts.patch.scriptJsLoad) {
if (WIKI.auth.checkAccess(opts.user, ['write:scripts'], {
locale: ogPage.localeCode,
locale: ogPage.locale,
path: ogPage.path
})) {
patch.scripts = {
@ -622,7 +614,7 @@ export class Page extends Model {
}
if (opts.patch.scriptJsUnload) {
if (WIKI.auth.checkAccess(opts.user, ['write:scripts'], {
locale: ogPage.localeCode,
locale: ogPage.locale,
path: ogPage.path
})) {
patch.scripts = {
@ -701,11 +693,11 @@ export class Page extends Model {
if (!id) {
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
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({
searchContent: safeContent,
ts: WIKI.db.knex.raw(`
@ -747,7 +739,7 @@ export class Page extends Model {
// -> Check for page access
if (!WIKI.auth.checkAccess(opts.user, ['write:pages'], {
locale: ogPage.localeCode,
locale: ogPage.locale,
path: ogPage.path
})) {
throw new WIKI.Error.PageUpdateForbidden()
@ -908,7 +900,7 @@ export class Page extends Model {
} else {
page = await WIKI.db.pages.query().findOne({
path: opts.path,
localeCode: opts.locale
locale: opts.locale
})
}
if (!page) {
@ -932,7 +924,7 @@ export class Page extends Model {
// -> Check for source page access
if (!WIKI.auth.checkAccess(opts.user, ['manage:pages'], {
locale: page.localeCode,
locale: page.locale,
path: page.path
})) {
throw new WIKI.Error.PageMoveForbidden()
@ -948,7 +940,7 @@ export class Page extends Model {
// -> Check for existing page at destination path
const destPage = await WIKI.db.pages.query().findOne({
path: opts.destinationPath,
localeCode: opts.destinationLocale
locale: opts.destinationLocale
})
if (destPage) {
throw new WIKI.Error.PagePathCollision()
@ -967,7 +959,7 @@ export class Page extends Model {
const destinationTitle = (page.title === page.path ? opts.destinationPath : page.title)
await WIKI.db.pages.query().patch({
path: opts.destinationPath,
localeCode: opts.destinationLocale,
locale: opts.destinationLocale,
title: destinationTitle,
hash: destinationHash
}).findById(page.id)
@ -983,7 +975,7 @@ export class Page extends Model {
await WIKI.data.searchEngine.renamed({
...page,
destinationPath: opts.destinationPath,
destinationLocaleCode: opts.destinationLocale,
destinationLocale: opts.destinationLocale,
destinationHash
})
@ -994,7 +986,7 @@ export class Page extends Model {
page: {
...page,
destinationPath: opts.destinationPath,
destinationLocaleCode: opts.destinationLocale,
destinationLocale: opts.destinationLocale,
destinationHash,
moveAuthorId: opts.user.id,
moveAuthorName: opts.user.name,
@ -1005,7 +997,7 @@ export class Page extends Model {
// -> Reconnect Links : Changing old links to the new path
await WIKI.db.pages.reconnectLinks({
sourceLocale: page.localeCode,
sourceLocale: page.locale,
sourcePath: page.path,
locale: opts.destinationLocale,
path: opts.destinationPath,
@ -1066,7 +1058,7 @@ export class Page extends Model {
// -> Reconnect Links
await WIKI.db.pages.reconnectLinks({
locale: page.localeCode,
locale: page.locale,
path: page.path,
mode: 'delete'
})
@ -1118,7 +1110,7 @@ export class Page extends Model {
.whereIn('pages.id', function () {
this.select('pageLinks.pageId').from('pageLinks').where({
'pageLinks.path': opts.path,
'pageLinks.localeCode': opts.locale
'pageLinks.locale': opts.locale
})
})
affectedHashes = qryHashes.map(h => h.hash)
@ -1131,7 +1123,7 @@ export class Page extends Model {
.whereIn('pages.id', function () {
this.select('pageLinks.pageId').from('pageLinks').where({
'pageLinks.path': opts.path,
'pageLinks.localeCode': opts.locale
'pageLinks.locale': opts.locale
})
})
const qryHashes = await WIKI.db.pages.query()
@ -1139,7 +1131,7 @@ export class Page extends Model {
.whereIn('pages.id', function () {
this.select('pageLinks.pageId').from('pageLinks').where({
'pageLinks.path': opts.path,
'pageLinks.localeCode': opts.locale
'pageLinks.locale': opts.locale
})
})
affectedHashes = qryHashes.map(h => h.hash)
@ -1227,20 +1219,18 @@ export class Page extends Model {
authorEmail: 'author.email',
creatorName: 'creator.name',
creatorEmail: 'creator.email'
}
},
'tree.navigationId'
])
.joinRelated('author')
.joinRelated('creator')
// .withGraphJoined('tags')
// .modifyGraph('tags', builder => {
// builder.select('tag')
// })
.leftJoin('tree', 'pages.id', 'tree.id')
.where(queryModeID ? {
'pages.id': opts
} : {
'pages.siteId': opts.siteId,
'pages.path': opts.path,
'pages.localeCode': opts.locale
'pages.locale': opts.locale
})
// .andWhere(builder => {
// if (queryModeID) return
@ -1307,7 +1297,7 @@ export class Page extends Model {
return {
...page,
path: opts.path,
localeCode: opts.locale
locale: opts.locale
}
} catch (err) {
if (err.code === 'ENOENT') {
@ -1346,13 +1336,13 @@ export class Page extends Model {
static async migrateToLocale({ sourceLocale, targetLocale }) {
return WIKI.db.pages.query()
.patch({
localeCode: targetLocale
locale: targetLocale
})
.where({
localeCode: sourceLocale
locale: sourceLocale
})
.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 { defaultsDeep, keyBy } from 'lodash-es'
import { v4 as uuid } from 'uuid'
/**
* Site model
@ -47,7 +48,16 @@ export class Site extends Model {
}
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({
id: newSiteId,
hostname,
isEnabled: true,
config: defaultsDeep(config, {
@ -138,6 +148,10 @@ export class Site extends Model {
config: {}
}
},
nav: {
mode: 'mixed',
defaultId: newDefaultNav.id,
},
uploads: {
conflictBehavior: 'overwrite',
normalizeFilename: true

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

@ -53,14 +53,6 @@ export class User extends Model {
},
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,
name: displayName,
pictureUrl: pictureUrl,
localeCode: WIKI.config.lang.code,
locale: WIKI.config.lang.code,
defaultEditor: 'markdown',
tfaIsActive: false,
isSystem: false,

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

@ -68,12 +68,12 @@ export async function render () {
// -> Reformat paths
if (href.indexOf('/') !== 0) {
if (this.config.absoluteLinks) {
href = `/${this.page.localeCode}/${href}`
href = `/${this.page.locale}/${href}`
} 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) !== '/') {
href = `/${this.page.localeCode}${href}`
href = `/${this.page.locale}${href}`
}
try {
@ -101,7 +101,7 @@ export async function render () {
}
// -> Save internal references
internalRefs.push({
localeCode: pagePath.locale,
locale: pagePath.locale,
path: pagePath.path
})
@ -127,7 +127,7 @@ export async function render () {
if (internalRefs.length > 0) {
// -> 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) => {
if (idx < 1) {
builder.where(ref)
@ -148,7 +148,7 @@ export async function render () {
return
}
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`)
} else {
@ -158,21 +158,21 @@ export async function render () {
// -> Add missing links
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 (WIKI.config.db.type === 'postgres') {
await WIKI.db.pageLinks.query().insert(missingLinks.map(lnk => ({
pageId: this.page.id,
path: lnk.path,
localeCode: lnk.localeCode
locale: lnk.locale
})))
} else {
for (const lnk of missingLinks) {
await WIKI.db.pageLinks.query().insert({
pageId: this.page.id,
path: lnk.path,
localeCode: lnk.localeCode
locale: lnk.locale
})
}
}
@ -182,7 +182,7 @@ export async function render () {
// -> Remove outdated links
if (pastLinks) {
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) {
await WIKI.db.pageLinks.query().delete().whereIn('id', _.map(outdatedLinks, 'id'))

@ -9,7 +9,7 @@ export async function task ({ payload }) {
let idx = 0
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({
objectMode: true,
transform: async (page, enc, cb) => {

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

@ -16,19 +16,19 @@ q-card(style='min-width: 350px')
q-list(padding)
q-item(tag='label')
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-label Inherit
q-item-label(caption) Use the menu items and settings from the parent path.
q-item(tag='label')
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-label Override Current + Descendants
q-item-label(caption) Set menu items and settings for this path and all children.
q-item(tag='label')
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-label Override Current Only
q-item-label(caption) Set menu items and settings only for this path.
@ -50,9 +50,10 @@ q-card(style='min-width: 350px')
</template>
<script setup>
import { onMounted, reactive, ref, watch } from 'vue'
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { usePageStore } from 'src/stores/page'
import { useSiteStore } from 'src/stores/site'
// PROPS
@ -66,6 +67,7 @@ const props = defineProps({
// STORES
const pageStore = usePageStore()
const siteStore = useSiteStore()
// 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')
span {{t(`navEdit.editMenuItems`)}}
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(
flat
rounded
@ -81,39 +87,65 @@ q-layout(view='hHh lpR fFf', container)
q-item-section(side)
q-icon.handle(name='mdi-drag-horizontal', size='sm')
.q-pa-md
q-btn.full-width.acrylic-btn(
.q-pa-md.flex
q-btn.acrylic-btn(
style='flex: 1;'
flat
color='positive'
:label='t(`common.actions.add`)'
:aria-label='t(`common.actions.add`)'
icon='las la-plus-circle'
)
q-menu(fit, :offset='[0, 10]')
q-menu(fit, :offset='[0, 10]', auto-close)
q-list(separator)
q-item(clickable)
q-item(clickable, @click='addItem(`header`)')
q-item-section(side)
q-icon(name='las la-heading')
q-item-section
q-item-label Header
q-item(clickable)
q-item-label {{t('navEdit.header')}}
q-item(clickable, @click='addItem(`link`)')
q-item-section(side)
q-icon(name='las la-link')
q-item-section
q-item-label {{t('navEdit.link')}}
q-item(clickable)
q-item(clickable, @click='addItem(`separator`)')
q-item-section(side)
q-icon(name='las la-minus')
q-item-section
q-item-label Separator
q-item(clickable, style='border-top-width: 5px;')
q-item-label {{t('navEdit.separator')}}
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-icon(name='mdi-import')
q-icon(name='las la-trash-alt', color='negative')
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.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`')
q-card.q-pb-sm
q-card-section
@ -135,10 +167,11 @@ q-layout(view='hHh lpR fFf', container)
q-space
q-btn.acrylic-btn(
flat
icon='las la-trash-alt'
:label='t(`common.actions.delete`)'
color='negative'
padding='xs md'
@click=''
@click='removeItem(state.current.id)'
)
template(v-if='state.current.type === `link`')
@ -176,6 +209,8 @@ q-layout(view='hHh lpR fFf', container)
name='las la-icons'
color='primary'
)
q-menu(content-class='shadow-7')
icon-picker-dialog(v-model='state.current.icon')
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='link')
@ -219,24 +254,25 @@ q-layout(view='hHh lpR fFf', container)
toggle-color='primary'
:options='visibilityOptions'
)
q-item(v-if='state.current.visibilityLimited')
q-item-section
q-item-section
q-item.items-center(v-if='state.current.visibilityLimited')
q-space
.text-caption.q-mr-md {{ t('navEdit.selectGroups') }}
q-select(
style='width: 100%; max-width: calc(50% - 34px);'
outlined
v-model='state.current.visibility'
v-model='state.current.visibilityGroups'
:options='state.groups'
option-value='value'
option-label='label'
option-value='id'
option-label='name'
emit-value
map-options
dense
options-dense
:virtual-scroll-slice-size='1000'
:aria-label='t(`admin.general.uploadConflictBehavior`)'
multiple
:aria-label='t(`navEdit.selectGroups`)'
)
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(
v-if='state.current.isNested'
flat
@ -255,13 +291,15 @@ q-layout(view='hHh lpR fFf', container)
padding='xs md'
@click='state.current.isNested = true'
)
.text-caption.q-mt-md.text-grey-7 {{ t('navEdit.nestingWarn') }}
q-space
q-btn.acrylic-btn(
flat
icon='las la-trash-alt'
:label='t(`common.actions.delete`)'
color='negative'
padding='xs md'
@click=''
@click='removeItem(state.current.id)'
)
template(v-if='state.current.type === `separator`')
@ -272,10 +310,11 @@ q-layout(view='hHh lpR fFf', container)
q-space
q-btn.acrylic-btn(
flat
icon='las la-trash-alt'
:label='t(`common.actions.delete`)'
color='negative'
padding='xs md'
@click=''
@click='removeItem(state.current.id)'
)
</template>
@ -284,6 +323,7 @@ q-layout(view='hHh lpR fFf', container)
import { useI18n } from 'vue-i18n'
import { useQuasar } from 'quasar'
import { onMounted, reactive, ref } from 'vue'
import { v4 as uuid } from 'uuid'
import gql from 'graphql-tag'
import { cloneDeep } from 'lodash-es'
@ -291,6 +331,8 @@ import { useSiteStore } from 'src/stores/site'
import { Sortable } from 'sortablejs-vue3'
import IconPickerDialog from 'src/components/IconPickerDialog.vue'
// QUASAR
const $q = useQuasar()
@ -307,66 +349,8 @@ const { t } = useI18n()
const state = reactive({
loading: 0,
selected: '3',
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'
}
],
selected: null,
items: [],
current: {
label: '',
icon: '',
@ -409,12 +393,67 @@ function setItem (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 () {
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>

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

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

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

@ -65,7 +65,10 @@ export const useSiteStore = defineStore('site', {
sideDialogShown: false,
sideDialogComponent: '',
docsBase: 'https://next.js.wiki/docs',
nav: {}
nav: {
currentId: null,
items: []
}
}),
getters: {
overlayIsShown: (state) => Boolean(state.overlay),
@ -236,6 +239,44 @@ export const useSiteStore = defineStore('site', {
console.warn(err.networkError?.result ?? err.message)
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