Commit 6920a35d authored by NGPixel's avatar NGPixel

feat: visualize pages (dendograms)

parent 4698afda
...@@ -152,12 +152,13 @@ const router = new VueRouter({ ...@@ -152,12 +152,13 @@ const router = new VueRouter({
{ path: '/locale', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-locale.vue') }, { path: '/locale', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-locale.vue') },
{ path: '/navigation', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-navigation.vue') }, { path: '/navigation', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-navigation.vue') },
{ path: '/pages', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-pages.vue') }, { path: '/pages', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-pages.vue') },
{ path: '/pages/:id', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-pages-edit.vue') }, { path: '/pages/:id(\\d+)', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-pages-edit.vue') },
{ path: '/pages/visualize', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-pages-visualize.vue') },
{ path: '/theme', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-theme.vue') }, { path: '/theme', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-theme.vue') },
{ path: '/groups', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-groups.vue') }, { path: '/groups', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-groups.vue') },
{ path: '/groups/:id', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-groups-edit.vue') }, { path: '/groups/:id(\\d+)', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-groups-edit.vue') },
{ path: '/users', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-users.vue') }, { path: '/users', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-users.vue') },
{ path: '/users/:id', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-users-edit.vue') }, { path: '/users/:id(\\d+)', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-users-edit.vue') },
{ path: '/analytics', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-analytics.vue') }, { path: '/analytics', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-analytics.vue') },
{ path: '/auth', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-auth.vue') }, { path: '/auth', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-auth.vue') },
{ path: '/rendering', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-rendering.vue') }, { path: '/rendering', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-rendering.vue') },
......
...@@ -13,9 +13,9 @@ ...@@ -13,9 +13,9 @@
v-btn.animated.fadeInDown.mx-3(color='primary', outlined, large, @click='recyclebin', disabled) v-btn.animated.fadeInDown.mx-3(color='primary', outlined, large, @click='recyclebin', disabled)
v-icon(left) mdi-delete-outline v-icon(left) mdi-delete-outline
span Recycle Bin span Recycle Bin
v-btn.animated.fadeInDown(color='primary', depressed, large, @click='newpage', disabled) v-btn.animated.fadeInDown(color='primary', depressed, large, to='pages/visualize')
v-icon(left) mdi-plus v-icon(left) mdi-graph
span New Page span Visualize
v-card.wiki-form.mt-3.animated.fadeInUp v-card.wiki-form.mt-3.animated.fadeInUp
v-toolbar(flat, :color='$vuetify.theme.dark ? `grey darken-3-d5` : `grey lighten-5`', height='80') v-toolbar(flat, :color='$vuetify.theme.dark ? `grey darken-3-d5` : `grey lighten-5`', height='80')
v-spacer v-spacer
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
v-tabs(color='white', background-color='blue darken-1', dark, centered) v-tabs(color='white', background-color='blue darken-1', dark, centered)
v-tab {{$t('editor:props.info')}} v-tab {{$t('editor:props.info')}}
v-tab {{$t('editor:props.scheduling')}} v-tab {{$t('editor:props.scheduling')}}
v-tab(disabled) {{$t('editor:props.scripts')}}
v-tab {{$t('editor:props.social')}} v-tab {{$t('editor:props.social')}}
v-tab-item v-tab-item
v-card-text.pt-5 v-card-text.pt-5
...@@ -179,6 +180,25 @@ ...@@ -179,6 +180,25 @@
v-tab-item v-tab-item
v-card-text v-card-text
.overline.pb-3 {{$t('editor:props.js')}}
v-textarea(
outlined
rows='5'
:hint='$t(`editor:props.jsHint`)'
persistent-hint
)
v-divider
v-card-text.grey.pt-5(:class='darkMode ? `darken-3-d3` : `lighten-5`')
.overline.pb-3 {{$t('editor:props.css')}}
v-textarea(
outlined
rows='5'
:hint='$t(`editor:props.cssHint`)'
persistent-hint
)
v-tab-item
v-card-text
.overline.pb-5 {{$t('editor:props.socialFeatures')}} #[v-chip.ml-3(label, color='grey', small, outlined).white--text coming soon] .overline.pb-5 {{$t('editor:props.socialFeatures')}} #[v-chip.ml-3(label, color='grey', small, outlined).white--text coming soon]
v-switch( v-switch(
:label='$t(`editor:props.allowComments`)' :label='$t(`editor:props.allowComments`)'
......
...@@ -215,6 +215,7 @@ ...@@ -215,6 +215,7 @@
"core-js": "3.6.1", "core-js": "3.6.1",
"css-loader": "3.4.0", "css-loader": "3.4.0",
"cssnano": "4.1.10", "cssnano": "4.1.10",
"d3": "5.15.0",
"duplicate-package-checker-webpack-plugin": "3.0.0", "duplicate-package-checker-webpack-plugin": "3.0.0",
"epic-spinners": "1.1.0", "epic-spinners": "1.1.0",
"eslint": "6.8.0", "eslint": "6.8.0",
......
...@@ -137,8 +137,12 @@ module.exports = { ...@@ -137,8 +137,12 @@ module.exports = {
async tree (obj, args, context, info) { async tree (obj, args, context, info) {
let results = [] let results = []
let conds = { let conds = {
localeCode: args.locale, localeCode: args.locale
parent: (args.parent < 1) ? null : args.parent }
if (args.parent) {
conds.parent = (args.parent < 1) ? null : args.parent
} else if (args.path) {
// conds.parent = (args.parent < 1) ? null : args.parent
} }
switch (args.mode) { switch (args.mode) {
case 'FOLDERS': case 'FOLDERS':
...@@ -162,6 +166,44 @@ module.exports = { ...@@ -162,6 +166,44 @@ module.exports = {
parent: r.parent || 0, parent: r.parent || 0,
locale: r.localeCode locale: r.localeCode
})) }))
},
/**
* FETCH PAGE LINKS
*/
async links (obj, args, context, info) {
let results = []
results = await WIKI.models.knex('pages')
.column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.localeCode' })
.fullOuterJoin('pageLinks', 'pages.id', 'pageLinks.pageId')
.where({
'pages.localeCode': args.locale
})
return _.reduce(results, (result, val) => {
// -> Check if user has access to source and linked page
if (
!WIKI.auth.checkAccess(context.req.user, ['read:pages'], { path: val.path, locale: args.locale }) ||
!WIKI.auth.checkAccess(context.req.user, ['read:pages'], { path: val.link, locale: val.locale })
) {
return result
}
const existingEntry = _.findIndex(result, ['id', val.id])
if (existingEntry >= 0) {
if (val.link) {
result[existingEntry].links.push(`${val.locale}/${val.link}`)
}
} else {
result.push({
id: val.id,
title: val.title,
path: `${args.locale}/${val.path}`,
links: val.link ? [`${val.locale}/${val.link}`] : []
})
}
return result
}, [])
} }
}, },
PageMutation: { PageMutation: {
......
...@@ -42,10 +42,16 @@ type PageQuery { ...@@ -42,10 +42,16 @@ type PageQuery {
tags: [PageTag]! @auth(requires: ["manage:system", "read:pages"]) tags: [PageTag]! @auth(requires: ["manage:system", "read:pages"])
tree( tree(
parent: Int! path: String
parent: Int
mode: PageTreeMode! mode: PageTreeMode!
locale: String! locale: String!
includeParents: Boolean
): [PageTreeItem] @auth(requires: ["manage:system", "read:pages"]) ): [PageTreeItem] @auth(requires: ["manage:system", "read:pages"])
links(
locale: String!
): [PageLinkItem] @auth(requires: ["manage:system", "read:pages"])
} }
# ----------------------------------------------- # -----------------------------------------------
...@@ -209,6 +215,13 @@ type PageTreeItem { ...@@ -209,6 +215,13 @@ type PageTreeItem {
locale: String! locale: String!
} }
type PageLinkItem {
id: Int!
path: String!
title: String!
links: [String]!
}
enum PageOrderBy { enum PageOrderBy {
CREATED CREATED
ID ID
......
...@@ -4,7 +4,7 @@ const crypto = require('crypto') ...@@ -4,7 +4,7 @@ const crypto = require('crypto')
const path = require('path') const path = require('path')
const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?)\/?(.*)/i const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?\/)?(.*)/i
const contentToExt = { const contentToExt = {
markdown: 'md', markdown: 'md',
...@@ -125,7 +125,7 @@ module.exports = { ...@@ -125,7 +125,7 @@ module.exports = {
const result = localeFolderRegex.exec(meta.path) const result = localeFolderRegex.exec(meta.path)
if (result[1]) { if (result[1]) {
meta = { meta = {
locale: result[1], locale: result[1].replace('/', ''),
path: result[2] path: result[2]
} }
} }
......
...@@ -72,7 +72,8 @@ module.exports = { ...@@ -72,7 +72,8 @@ module.exports = {
}, },
async processPage ({ user, fullPath, relPath, contentType, moduleName }) { async processPage ({ user, fullPath, relPath, contentType, moduleName }) {
const contentPath = pageHelper.getPagePath(relPath) const normalizedRelPath = relPath.replace(/\\/g, '/')
const contentPath = pageHelper.getPagePath(normalizedRelPath)
const itemContents = await fs.readFile(path.join(fullPath, relPath), 'utf8') const itemContents = await fs.readFile(path.join(fullPath, relPath), 'utf8')
const pageData = WIKI.models.pages.parseMetadata(itemContents, contentType) const pageData = WIKI.models.pages.parseMetadata(itemContents, contentType)
const currentPage = await WIKI.models.pages.getPageFromDb({ const currentPage = await WIKI.models.pages.getPageFromDb({
...@@ -82,7 +83,7 @@ module.exports = { ...@@ -82,7 +83,7 @@ module.exports = {
const newTags = !_.isNil(pageData.tags) ? _.get(pageData, 'tags', '').split(', ') : false const newTags = !_.isNil(pageData.tags) ? _.get(pageData, 'tags', '').split(', ') : false
if (currentPage) { if (currentPage) {
// Already in the DB, can mark as modified // Already in the DB, can mark as modified
WIKI.logger.info(`(STORAGE/${moduleName}) Page marked as modified: ${relPath}`) WIKI.logger.info(`(STORAGE/${moduleName}) Page marked as modified: ${normalizedRelPath}`)
await WIKI.models.pages.updatePage({ await WIKI.models.pages.updatePage({
id: currentPage.id, id: currentPage.id,
title: _.get(pageData, 'title', currentPage.title), title: _.get(pageData, 'title', currentPage.title),
...@@ -96,7 +97,7 @@ module.exports = { ...@@ -96,7 +97,7 @@ module.exports = {
}) })
} else { } else {
// Not in the DB, can mark as new // Not in the DB, can mark as new
WIKI.logger.info(`(STORAGE/${moduleName}) Page marked as new: ${relPath}`) WIKI.logger.info(`(STORAGE/${moduleName}) Page marked as new: ${normalizedRelPath}`)
const pageEditor = await WIKI.models.editors.getDefaultEditor(contentType) const pageEditor = await WIKI.models.editors.getDefaultEditor(contentType)
await WIKI.models.pages.createPage({ await WIKI.models.pages.createPage({
path: contentPath.path, path: contentPath.path,
......
This diff was suppressed by a .gitattributes entry.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment