From 5eaba2cbb03d7004a50ec060a769daa0df09fcff Mon Sep 17 00:00:00 2001 From: NGPixel Date: Mon, 17 Jul 2023 08:42:16 +0000 Subject: [PATCH] feat: remove localeCode ref + new nav items + fetch current --- server/core/auth.mjs | 2 +- server/db/migrations/3.0.0.mjs | 77 +++++- server/graph/resolvers/asset.mjs | 4 +- server/graph/resolvers/comment.mjs | 6 +- server/graph/resolvers/navigation.mjs | 35 +-- server/graph/resolvers/page.mjs | 60 ++-- server/graph/resolvers/tree.mjs | 2 +- server/graph/schemas/navigation.graphql | 55 +--- server/graph/schemas/page.graphql | 1 + server/helpers/common.mjs | 8 +- server/locales/en.json | 5 + server/models/assets.mjs | 2 +- server/models/comments.mjs | 6 +- server/models/navigation.mjs | 23 +- server/models/pageHistory.mjs | 6 +- server/models/pageLinks.mjs | 4 +- server/models/pages.mjs | 70 ++--- server/models/sites.mjs | 14 + server/models/tree.mjs | 20 +- server/models/users.mjs | 10 +- server/modules/comments/default/comment.js | 2 +- server/modules/rendering/core/renderer.mjs | 20 +- server/tasks/workers/rebuild-search-index.mjs | 2 +- ux/src/components/IconPickerDialog.vue | 14 +- ux/src/components/NavEditMenu.vue | 10 +- ux/src/components/NavEditOverlay.vue | 261 ++++++++++-------- ux/src/components/NavSidebar.vue | 41 ++- ux/src/layouts/MainLayout.vue | 15 +- ux/src/stores/page.js | 2 + ux/src/stores/site.js | 43 ++- 30 files changed, 469 insertions(+), 351 deletions(-) diff --git a/server/core/auth.mjs b/server/core/auth.mjs index 95c6d17a..a5dac737 100644 --- a/server/core/auth.mjs +++ b/server/core/auth.mjs @@ -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 () { diff --git a/server/db/migrations/3.0.0.mjs b/server/db/migrations/3.0.0.mjs index 32fb4efc..e3da7726 100644 --- a/server/db/migrations/3.0.0.mjs +++ b/server/db/migrations/3.0.0.mjs @@ -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 * * *', diff --git a/server/graph/resolvers/asset.mjs b/server/graph/resolvers/asset.mjs index 408d498a..c10412ba 100644 --- a/server/graph/resolvers/asset.mjs +++ b/server/graph/resolvers/asset.mjs @@ -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, diff --git a/server/graph/resolvers/comment.mjs b/server/graph/resolvers/comment.mjs index 5dd0d008..968ece8f 100644 --- a/server/graph/resolvers/comment.mjs +++ b/server/graph/resolvers/comment.mjs @@ -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, diff --git a/server/graph/resolvers/navigation.mjs b/server/graph/resolvers/navigation.mjs index 169c51f8..523b1a14 100644 --- a/server/graph/resolvers/navigation.mjs +++ b/server/graph/resolvers/navigation.mjs @@ -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) - } } } } diff --git a/server/graph/resolvers/page.mjs b/server/graph/resolvers/page.mjs index 8ab68e23..fbd4ce73 100644 --- a/server/graph/resolvers/page.mjs +++ b/server/graph/resolvers/page.mjs @@ -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() } diff --git a/server/graph/resolvers/tree.mjs b/server/graph/resolvers/tree.mjs index 9473d05a..bef2228a 100644 --- a/server/graph/resolvers/tree.mjs +++ b/server/graph/resolvers/tree.mjs @@ -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) }) diff --git a/server/graph/schemas/navigation.graphql b/server/graph/schemas/navigation.graphql index 602928e2..42d49bac 100644 --- a/server/graph/schemas/navigation.graphql +++ b/server/graph/schemas/navigation.graphql @@ -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] } diff --git a/server/graph/schemas/page.graphql b/server/graph/schemas/page.graphql index 42c486e2..da34ec89 100644 --- a/server/graph/schemas/page.graphql +++ b/server/graph/schemas/page.graphql @@ -237,6 +237,7 @@ type Page { isBrowsable: Boolean isSearchable: Boolean locale: String + navigationId: UUID password: String path: String publishEndDate: Date diff --git a/server/helpers/common.mjs b/server/helpers/common.mjs index f8d4de83..51f5996a 100644 --- a/server/helpers/common.mjs +++ b/server/helpers/common.mjs @@ -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' } } diff --git a/server/locales/en.json b/server/locales/en.json index e82cfe26..252d3fcd 100644 --- a/server/locales/en.json +++ b/server/locales/en.json @@ -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.", diff --git a/server/models/assets.mjs b/server/models/assets.mjs index 0b75e306..b3d65879 100644 --- a/server/models/assets.mjs +++ b/server/models/assets.mjs @@ -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() diff --git a/server/models/comments.mjs b/server/models/comments.mjs index 70870ad0..aac0b617 100644 --- a/server/models/comments.mjs +++ b/server/models/comments.mjs @@ -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() } diff --git a/server/models/navigation.mjs b/server/models/navigation.mjs index b78e879d..a1398998 100644 --- a/server/models/navigation.mjs +++ b/server/models/navigation.mjs @@ -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}`) diff --git a/server/models/pageHistory.mjs b/server/models/pageHistory.mjs index 3d502561..5b01f610 100644 --- a/server/models/pageHistory.mjs +++ b/server/models/pageHistory.mjs @@ -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' } ]) diff --git a/server/models/pageLinks.mjs b/server/models/pageLinks.mjs index a0d42cb4..55d5fe3d 100644 --- a/server/models/pageLinks.mjs +++ b/server/models/pageLinks.mjs @@ -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'} } } } diff --git a/server/models/pages.mjs b/server/models/pages.mjs index 44ba2daf..338684fd 100644 --- a/server/models/pages.mjs +++ b/server/models/pages.mjs @@ -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') }) } diff --git a/server/models/sites.mjs b/server/models/sites.mjs index 537ffda5..00326b22 100644 --- a/server/models/sites.mjs +++ b/server/models/sites.mjs @@ -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 diff --git a/server/models/tree.mjs b/server/models/tree.mjs index c8e7ff10..b74e39e4 100644 --- a/server/models/tree.mjs +++ b/server/models/tree.mjs @@ -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 diff --git a/server/models/users.mjs b/server/models/users.mjs index b79b6e0a..d63f71c5 100644 --- a/server/models/users.mjs +++ b/server/models/users.mjs @@ -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, diff --git a/server/modules/comments/default/comment.js b/server/modules/comments/default/comment.js index d84dcaa1..4f878b98 100644 --- a/server/modules/comments/default/comment.js +++ b/server/modules/comments/default/comment.js @@ -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 diff --git a/server/modules/rendering/core/renderer.mjs b/server/modules/rendering/core/renderer.mjs index 87f4d834..12f8b22e 100644 --- a/server/modules/rendering/core/renderer.mjs +++ b/server/modules/rendering/core/renderer.mjs @@ -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')) diff --git a/server/tasks/workers/rebuild-search-index.mjs b/server/tasks/workers/rebuild-search-index.mjs index b8d8075b..7b642d55 100644 --- a/server/tasks/workers/rebuild-search-index.mjs +++ b/server/tasks/workers/rebuild-search-index.mjs @@ -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) => { diff --git a/ux/src/components/IconPickerDialog.vue b/ux/src/components/IconPickerDialog.vue index 6686e3bb..2c3337a6 100644 --- a/ux/src/components/IconPickerDialog.vue +++ b/ux/src/components/IconPickerDialog.vue @@ -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 } } diff --git a/ux/src/components/NavEditMenu.vue b/ux/src/components/NavEditMenu.vue index 7e2d79b7..2c8e6ff9 100644 --- a/ux/src/components/NavEditMenu.vue +++ b/ux/src/components/NavEditMenu.vue @@ -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') diff --git a/ux/src/components/NavSidebar.vue b/ux/src/components/NavSidebar.vue index d4aef31d..ac7251e2 100644 --- a/ux/src/components/NavSidebar.vue +++ b/ux/src/components/NavSidebar.vue @@ -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') - q-item-section(side) - q-icon(name='mdi-fruit-grapes', color='white') - q-item-section.text-wordbreak-all Link 3 + 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='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 + )