feat: tags UI (wip) + save tags from page
This commit is contained in:
parent
8e80b7471d
commit
5a7fd2d73e
@ -163,9 +163,10 @@ Vue.component('not-found', () => import(/* webpackChunkName: "not-found" */ './c
|
||||
Vue.component('page-selector', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/page-selector.vue'))
|
||||
Vue.component('profile', () => import(/* webpackChunkName: "profile" */ './components/profile.vue'))
|
||||
Vue.component('register', () => import(/* webpackChunkName: "register" */ './components/register.vue'))
|
||||
Vue.component('v-card-chin', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/v-card-chin.vue'))
|
||||
Vue.component('search-results', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/search-results.vue'))
|
||||
Vue.component('tags', () => import(/* webpackChunkName: "tags" */ './components/tags.vue'))
|
||||
Vue.component('unauthorized', () => import(/* webpackChunkName: "unauthorized" */ './components/unauthorized.vue'))
|
||||
Vue.component('v-card-chin', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/v-card-chin.vue'))
|
||||
Vue.component('welcome', () => import(/* webpackChunkName: "welcome" */ './components/welcome.vue'))
|
||||
|
||||
Vue.component('nav-footer', () => import(/* webpackChunkName: "theme-page" */ './themes/' + process.env.CURRENT_THEME + '/components/nav-footer.vue'))
|
||||
|
@ -37,6 +37,15 @@
|
||||
v-card.animated.fadeInUp.wait-p2s
|
||||
v-toolbar(color='primary', dense, flat, dark)
|
||||
.subtitle-1 {{provider.title}}
|
||||
v-spacer
|
||||
v-switch(
|
||||
dark
|
||||
color='blue lighten-5'
|
||||
label='Active'
|
||||
v-model='provider.isEnabled'
|
||||
hide-details
|
||||
inset
|
||||
)
|
||||
v-card-text
|
||||
v-form
|
||||
.analytic-provider-logo
|
||||
@ -68,6 +77,7 @@
|
||||
prepend-icon='mdi-settings-box'
|
||||
:hint='cfg.value.hint ? cfg.value.hint : ""'
|
||||
persistent-hint
|
||||
inset
|
||||
)
|
||||
v-textarea(
|
||||
v-else-if='cfg.value.type === "string" && cfg.value.multiline'
|
||||
|
@ -63,10 +63,19 @@
|
||||
)
|
||||
|
||||
v-flex(xs12, lg9)
|
||||
|
||||
v-card.animated.fadeInUp.wait-p2s
|
||||
v-toolbar(color='primary', dense, flat, dark)
|
||||
.subtitle-1 {{strategy.title}}
|
||||
v-spacer
|
||||
v-switch(
|
||||
dark
|
||||
color='blue lighten-5'
|
||||
label='Active'
|
||||
v-model='strategy.isEnabled'
|
||||
hide-details
|
||||
inset
|
||||
:disabled='strategy.key === `local`'
|
||||
)
|
||||
v-card-text
|
||||
v-form
|
||||
.authlogo
|
||||
@ -104,6 +113,7 @@
|
||||
prepend-icon='mdi-settings-box'
|
||||
:hint='cfg.value.hint ? cfg.value.hint : ""'
|
||||
persistent-hint
|
||||
inset
|
||||
)
|
||||
v-textarea(
|
||||
v-else-if='cfg.value.type === "string" && cfg.value.multiline'
|
||||
@ -136,6 +146,7 @@
|
||||
color='primary'
|
||||
:hint='$t(`admin:auth.selfRegistrationHint`)'
|
||||
persistent-hint
|
||||
inset
|
||||
)
|
||||
v-switch.ml-3(
|
||||
v-if='strategy.key === `local`'
|
||||
@ -145,6 +156,7 @@
|
||||
color='primary'
|
||||
hint='Protects against spam robots and malicious registrations.'
|
||||
persistent-hint
|
||||
inset
|
||||
)
|
||||
v-combobox.ml-3.mt-3(
|
||||
:label='$t(`admin:auth.domainsWhitelist`)'
|
||||
@ -187,6 +199,7 @@
|
||||
color='primary'
|
||||
:hint='$t(`admin:auth.force2faHint`)'
|
||||
persistent-hint
|
||||
inset
|
||||
)
|
||||
|
||||
v-card.mt-4.wiki-form.animated.fadeInUp.wait-p4s
|
||||
|
@ -19,7 +19,7 @@
|
||||
easing='easeOutQuint'
|
||||
)
|
||||
v-flex(xs12 md6 lg4 xl3 d-flex)
|
||||
v-card.indigo.lighten-1.dashboard-card.animated.fadeInUp.wait-p2s(dark)
|
||||
v-card.green.lighten-1.dashboard-card.animated.fadeInUp.wait-p2s(dark)
|
||||
v-card-text
|
||||
v-icon.dashboard-icon mdi-account
|
||||
.overline {{$t('admin:dashboard.users')}}
|
||||
@ -30,7 +30,7 @@
|
||||
easing='easeOutQuint'
|
||||
)
|
||||
v-flex(xs12 md6 lg4 xl3 d-flex)
|
||||
v-card.indigo.lighten-2.dashboard-card.animated.fadeInUp.wait-p4s(dark)
|
||||
v-card.indigo.lighten-1.dashboard-card.animated.fadeInUp.wait-p4s(dark)
|
||||
v-card-text
|
||||
v-icon.dashboard-icon mdi-account-group
|
||||
.overline {{$t('admin:dashboard.groups')}}
|
||||
|
@ -23,6 +23,7 @@
|
||||
persistent-hint
|
||||
label='LDAP Debug'
|
||||
v-model='flags.ldapdebug'
|
||||
inset
|
||||
)
|
||||
v-divider.mt-3
|
||||
v-switch.mt-3(
|
||||
@ -31,6 +32,7 @@
|
||||
persistent-hint
|
||||
label='SQL Query Logging'
|
||||
v-model='flags.sqllog'
|
||||
inset
|
||||
)
|
||||
</template>
|
||||
|
||||
|
@ -98,6 +98,7 @@
|
||||
v-chip(label, color='white', small).indigo--text coming soon
|
||||
v-card-text
|
||||
v-switch(
|
||||
inset
|
||||
label='Asset Image Optimization'
|
||||
color='indigo'
|
||||
v-model='config.featureTinyPNG'
|
||||
@ -118,6 +119,7 @@
|
||||
|
||||
v-divider.mt-3
|
||||
v-switch(
|
||||
inset
|
||||
label='Page Ratings'
|
||||
color='indigo'
|
||||
v-model='config.featurePageRatings'
|
||||
@ -128,6 +130,7 @@
|
||||
|
||||
v-divider.mt-3
|
||||
v-switch(
|
||||
inset
|
||||
label='Page Comments'
|
||||
color='indigo'
|
||||
v-model='config.featurePageComments'
|
||||
@ -138,6 +141,7 @@
|
||||
|
||||
v-divider.mt-3
|
||||
v-switch(
|
||||
inset
|
||||
label='Personal Wikis'
|
||||
color='indigo'
|
||||
v-model='config.featurePersonalWikis'
|
||||
@ -152,6 +156,7 @@
|
||||
v-card-text
|
||||
v-alert(outlined, color='red darken-2', icon='mdi-information-outline').body-2 Make sure to understand the implications before turning on / off a security feature.
|
||||
v-switch.mt-3(
|
||||
inset
|
||||
label='Block IFrame Embedding'
|
||||
color='red darken-2'
|
||||
v-model='config.securityIframe'
|
||||
@ -160,6 +165,7 @@
|
||||
)
|
||||
v-divider.mt-3
|
||||
v-switch(
|
||||
inset
|
||||
label='Same Origin Referrer Policy'
|
||||
color='red darken-2'
|
||||
v-model='config.securityReferrerPolicy'
|
||||
@ -169,6 +175,7 @@
|
||||
|
||||
v-divider.mt-3
|
||||
v-switch(
|
||||
inset
|
||||
label='Enforce HSTS'
|
||||
color='red darken-2'
|
||||
v-model='config.securityHSTS'
|
||||
@ -191,6 +198,7 @@
|
||||
|
||||
v-divider.mt-3
|
||||
v-switch(
|
||||
inset
|
||||
label='Enforce CSP'
|
||||
color='red darken-2'
|
||||
v-model='config.securityCSP'
|
||||
|
@ -40,6 +40,7 @@
|
||||
v-list-item-subtitle(v-html='data.item.nativeName')
|
||||
v-divider.mt-3
|
||||
v-switch(
|
||||
inset
|
||||
v-model='autoUpdate'
|
||||
:label='$t("admin:locale.autoUpdate.label")'
|
||||
color='primary'
|
||||
@ -52,6 +53,7 @@
|
||||
v-toolbar-title.subtitle-1 {{ $t('admin:locale.namespacing') }}
|
||||
v-card-text
|
||||
v-switch(
|
||||
inset
|
||||
v-model='namespacing'
|
||||
:label='$t("admin:locale.namespaces.label")'
|
||||
color='primary'
|
||||
|
@ -64,6 +64,7 @@
|
||||
persistent-hint
|
||||
:hint='$t(`admin:mail.smtpTLSHint`)'
|
||||
prepend-icon='mdi-security-network'
|
||||
inset
|
||||
)
|
||||
v-text-field.mt-3(
|
||||
outlined
|
||||
@ -94,6 +95,7 @@
|
||||
:label='$t(`admin:mail.dkimUse`)'
|
||||
color='primary'
|
||||
prepend-icon='mdi-key'
|
||||
inset
|
||||
)
|
||||
v-text-field(
|
||||
outlined
|
||||
|
@ -82,6 +82,7 @@
|
||||
label='Enabled'
|
||||
v-model='currentRenderer.isEnabled'
|
||||
hide-details
|
||||
inset
|
||||
)
|
||||
v-card-text.pb-4.pt-2.pl-4
|
||||
.overline.my-5 Rendering Module Configuration
|
||||
@ -106,6 +107,7 @@
|
||||
color='primary'
|
||||
:hint='cfg.value.hint ? cfg.value.hint : ""'
|
||||
persistent-hint
|
||||
inset
|
||||
)
|
||||
v-text-field(
|
||||
v-else
|
||||
|
@ -69,6 +69,7 @@
|
||||
prepend-icon='mdi-settings-box'
|
||||
:hint='cfg.value.hint ? cfg.value.hint : ""'
|
||||
persistent-hint
|
||||
inset
|
||||
)
|
||||
v-textarea(
|
||||
v-else-if='cfg.value.type === "string" && cfg.value.multiline'
|
||||
|
@ -80,6 +80,15 @@
|
||||
v-card.wiki-form.animated.fadeInUp.wait-p2s
|
||||
v-toolbar(color='primary', dense, flat, dark)
|
||||
.subtitle-1 {{target.title}}
|
||||
v-spacer
|
||||
v-switch(
|
||||
dark
|
||||
color='blue lighten-5'
|
||||
label='Active'
|
||||
v-model='target.isEnabled'
|
||||
hide-details
|
||||
inset
|
||||
)
|
||||
v-card-text
|
||||
v-form
|
||||
.targetlogo
|
||||
@ -115,6 +124,7 @@
|
||||
prepend-icon='mdi-settings-box'
|
||||
:hint='cfg.value.hint ? cfg.value.hint : ""'
|
||||
persistent-hint
|
||||
inset
|
||||
)
|
||||
v-textarea(
|
||||
v-else-if='cfg.value.type === "string" && cfg.value.multiline'
|
||||
|
@ -44,6 +44,7 @@
|
||||
)
|
||||
v-divider.mt-3
|
||||
v-switch(
|
||||
inset
|
||||
v-model='darkMode'
|
||||
:label='$t(`admin:theme.darkMode`)'
|
||||
color='primary'
|
||||
|
@ -125,6 +125,11 @@
|
||||
//- v-btn(depressed, color='grey darken-3', block)
|
||||
//- v-icon(left) mdi-cached
|
||||
//- span Reset
|
||||
v-tooltip(bottom, v-if='isAuthenticated && isAdmin')
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn.ml-2.mr-0(icon, v-on='on', href='/t')
|
||||
v-icon(color='grey') mdi-tag-multiple
|
||||
span Browse Tags
|
||||
v-flex(xs6, md4)
|
||||
v-toolbar.nav-header-inner.pr-4(color='black', dark, flat)
|
||||
v-spacer
|
||||
|
@ -90,6 +90,7 @@
|
||||
:hint='$t(`editor:props.publishToggleHint`)'
|
||||
persistent-hint
|
||||
disabled
|
||||
inset
|
||||
)
|
||||
v-divider
|
||||
v-card-text.grey.pt-5(:class='darkMode ? `darken-3-d3` : `lighten-5`')
|
||||
@ -190,6 +191,7 @@
|
||||
:hint='$t(`editor:props.allowCommentsHint`)'
|
||||
persistent-hint
|
||||
disabled
|
||||
inset
|
||||
)
|
||||
v-switch(
|
||||
:label='$t(`editor:props.allowRatings`)'
|
||||
@ -198,6 +200,7 @@
|
||||
:hint='$t(`editor:props.allowRatingsHint`)'
|
||||
persistent-hint
|
||||
disabled
|
||||
inset
|
||||
)
|
||||
v-switch(
|
||||
:label='$t(`editor:props.displayAuthor`)'
|
||||
@ -206,6 +209,7 @@
|
||||
:hint='$t(`editor:props.displayAuthorHint`)'
|
||||
persistent-hint
|
||||
disabled
|
||||
inset
|
||||
)
|
||||
v-switch(
|
||||
:label='$t(`editor:props.displaySharingBar`)'
|
||||
@ -214,6 +218,7 @@
|
||||
:hint='$t(`editor:props.displaySharingBarHint`)'
|
||||
persistent-hint
|
||||
disabled
|
||||
inset
|
||||
)
|
||||
|
||||
page-selector(mode='create', v-model='pageSelectorShown', :path='path', :locale='locale', :open-handler='setPath')
|
||||
|
167
client/components/tags.vue
Normal file
167
client/components/tags.vue
Normal file
@ -0,0 +1,167 @@
|
||||
<template lang='pug'>
|
||||
v-app(:dark='darkMode').tags
|
||||
nav-header
|
||||
v-navigation-drawer.pb-0.elevation-1(app, fixed, clipped, :right='$vuetify.rtl', permanent, width='300')
|
||||
vue-scroll(:ops='scrollStyle')
|
||||
v-list(dense, nav)
|
||||
v-list-item(href='/')
|
||||
v-list-item-icon: v-icon mdi-home
|
||||
v-list-item-title {{$t('common:header.home')}}
|
||||
template(v-for='(tags, groupName) in tagsGrouped')
|
||||
v-divider.my-2
|
||||
v-subheader.pl-4(:key='`tagGroup-` + groupName') {{groupName}}
|
||||
v-list-item(v-for='tag of tags', @click='toggleTag(tag.tag)', :key='`tag-` + tag.tag')
|
||||
v-list-item-icon
|
||||
v-icon(v-if='isSelected(tag.tag)', color='primary') mdi-checkbox-intermediate
|
||||
v-icon(v-else) mdi-checkbox-blank-outline
|
||||
v-list-item-title {{tag.title}}
|
||||
v-content
|
||||
v-toolbar(color='primary', dark, flat, height='58')
|
||||
template(v-if='selection.length > 0')
|
||||
.overline.mr-3.animated.fadeInLeft Current Selection
|
||||
v-chip.mr-3.primary--text(
|
||||
v-for='tag of tagsSelected'
|
||||
color='white'
|
||||
close
|
||||
@click:close='toggleTag(tag.tag)'
|
||||
) {{tag.title}}
|
||||
v-spacer
|
||||
v-btn.animated.fadeIn(
|
||||
small
|
||||
outlined
|
||||
color='blue lighten-4'
|
||||
rounded
|
||||
@click='selection = []'
|
||||
)
|
||||
v-icon(left) mdi-close
|
||||
span Clear Selection
|
||||
template(v-else)
|
||||
v-icon.mr-3.animated.fadeInRight mdi-arrow-left
|
||||
.overline.animated.fadeInRight Select one or more tags
|
||||
v-toolbar(color='grey lighten-4', flat, height='58')
|
||||
v-text-field.tags-search(
|
||||
label='Search within results...'
|
||||
solo
|
||||
hide-details
|
||||
flat
|
||||
rounded
|
||||
single-line
|
||||
height='40'
|
||||
prepend-icon='mdi-file-document-box-search-outline'
|
||||
append-icon='mdi-arrow-right'
|
||||
)
|
||||
v-divider.mx-3(vertical)
|
||||
.overline Order By
|
||||
v-select.ml-2(
|
||||
:items='orderByItems'
|
||||
v-model='orderBy'
|
||||
background-color='white'
|
||||
hide-details
|
||||
label='Order By'
|
||||
rounded
|
||||
single-line
|
||||
dense
|
||||
height='40'
|
||||
style='max-width: 250px;'
|
||||
)
|
||||
v-divider.mx-3(vertical)
|
||||
v-btn-toggle(v-model='displayStyle', rounded, mandatory)
|
||||
v-btn(text, height='40'): v-icon(small) mdi-view-list
|
||||
v-btn(text, height='40'): v-icon(small) mdi-cards-variant
|
||||
v-btn(text, height='40'): v-icon(small) mdi-format-align-justify
|
||||
v-divider
|
||||
nav-footer
|
||||
notify
|
||||
search-results
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { get } from 'vuex-pathify'
|
||||
import _ from 'lodash'
|
||||
|
||||
import tagsQuery from 'gql/common/common-pages-query-tags.gql'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tags: [],
|
||||
selection: [],
|
||||
displayStyle: 0,
|
||||
orderBy: 'TITLE',
|
||||
orderByItems: [
|
||||
{ text: 'Creation Date', value: 'CREATED' },
|
||||
{ text: 'ID', value: 'ID' },
|
||||
{ text: 'Last Modified', value: 'UPDATED' },
|
||||
{ text: 'Path', value: 'PATH' },
|
||||
{ text: 'Title', value: 'TITLE' }
|
||||
],
|
||||
scrollStyle: {
|
||||
vuescroll: {},
|
||||
scrollPanel: {
|
||||
initialScrollY: 0,
|
||||
initialScrollX: 0,
|
||||
scrollingX: false,
|
||||
easing: 'easeOutQuad',
|
||||
speed: 1000,
|
||||
verticalNativeBarPos: this.$vuetify.rtl ? `left` : `right`
|
||||
},
|
||||
rail: {
|
||||
gutterOfEnds: '2px'
|
||||
},
|
||||
bar: {
|
||||
onlyShowBarOnScroll: false,
|
||||
background: '#CCC',
|
||||
hoverStyle: {
|
||||
background: '#999'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
darkMode: get('site/dark'),
|
||||
tagsGrouped () {
|
||||
return _.groupBy(this.tags, t => t.title.charAt(0).toUpperCase())
|
||||
},
|
||||
tagsSelected () {
|
||||
return _.filter(this.tags, t => _.includes(this.selection, t.tag))
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$store.commit('page/SET_MODE', 'tags')
|
||||
},
|
||||
methods: {
|
||||
toggleTag (tag) {
|
||||
if (_.includes(this.selection, tag)) {
|
||||
this.selection = _.without(this.selection, tag)
|
||||
} else {
|
||||
this.selection.push(tag)
|
||||
}
|
||||
},
|
||||
isSelected (tag) {
|
||||
return _.includes(this.selection, tag)
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
tags: {
|
||||
query: tagsQuery,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
update: (data) => _.cloneDeep(data.pages.tags),
|
||||
watchLoading (isLoading) {
|
||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'tags-refresh')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
.tags-search {
|
||||
.v-input__control {
|
||||
min-height: initial !important;
|
||||
}
|
||||
.v-input__prepend-outer {
|
||||
margin-top: 8px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
8
client/graph/common/common-pages-query-tags.gql
Normal file
8
client/graph/common/common-pages-query-tags.gql
Normal file
@ -0,0 +1,8 @@
|
||||
query {
|
||||
pages {
|
||||
tags {
|
||||
tag
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
@ -165,6 +165,14 @@ router.get(['/s', '/s/*'], async (req, res, next) => {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Tags
|
||||
*/
|
||||
router.get(['/t', '/t/*'], (req, res, next) => {
|
||||
_.set(res.locals, 'pageMeta.title', 'Tags')
|
||||
res.render('tags')
|
||||
})
|
||||
|
||||
/**
|
||||
* View document / asset
|
||||
*/
|
||||
|
@ -76,6 +76,9 @@ module.exports = {
|
||||
} else {
|
||||
throw new WIKI.Error.PageNotFound()
|
||||
}
|
||||
},
|
||||
async tags (obj, args, context, info) {
|
||||
return WIKI.models.tags.query().orderBy('tag', 'asc')
|
||||
}
|
||||
},
|
||||
PageMutation: {
|
||||
|
@ -36,6 +36,8 @@ type PageQuery {
|
||||
single(
|
||||
id: Int!
|
||||
): Page @auth(requires: ["manage:pages", "delete:pages", "manage:system"])
|
||||
|
||||
tags: [PageTag]! @auth(requires: ["manage:system", "read:pages"])
|
||||
}
|
||||
|
||||
# -----------------------------------------------
|
||||
@ -109,6 +111,7 @@ type Page {
|
||||
privateNS: String
|
||||
publishStartDate: Date!
|
||||
publishEndDate: String!
|
||||
tags: [PageTag]!
|
||||
content: String!
|
||||
render: String
|
||||
toc: String
|
||||
@ -125,6 +128,14 @@ type Page {
|
||||
creatorEmail: String!
|
||||
}
|
||||
|
||||
type PageTag {
|
||||
id: Int!
|
||||
tag: String!
|
||||
title: String
|
||||
createdAt: Date!
|
||||
updatedAt: Date!
|
||||
}
|
||||
|
||||
type PageHistory {
|
||||
versionId: Int!
|
||||
authorId: Int!
|
||||
|
@ -210,6 +210,11 @@ module.exports = class Page extends Model {
|
||||
isPrivate: opts.isPrivate
|
||||
})
|
||||
|
||||
// -> Save Tags
|
||||
if (opts.tags.length > 0) {
|
||||
await WIKI.models.tags.associateTags({ tags: opts.tags, page })
|
||||
}
|
||||
|
||||
// -> Render page to HTML
|
||||
await WIKI.models.pages.renderPage(page)
|
||||
|
||||
@ -260,6 +265,9 @@ module.exports = class Page extends Model {
|
||||
isPrivate: ogPage.isPrivate
|
||||
})
|
||||
|
||||
// -> Save Tags
|
||||
await WIKI.models.tags.associateTags({ tags: opts.tags, page })
|
||||
|
||||
// -> Render page to HTML
|
||||
await WIKI.models.pages.renderPage(page)
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
const Model = require('objection').Model
|
||||
const _ = require('lodash')
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
/**
|
||||
* Tags model
|
||||
@ -46,4 +49,51 @@ module.exports = class Tag extends Model {
|
||||
this.createdAt = new Date().toISOString()
|
||||
this.updatedAt = new Date().toISOString()
|
||||
}
|
||||
|
||||
static async associateTags ({ tags, page }) {
|
||||
let existingTags = await WIKI.models.tags.query().column('id', 'tag')
|
||||
|
||||
// Create missing tags
|
||||
|
||||
const newTags = _.filter(tags, t => !_.some(existingTags, ['tag', t])).map(t => ({
|
||||
tag: t,
|
||||
title: t
|
||||
}))
|
||||
if (newTags.length > 0) {
|
||||
if (WIKI.config.db.type === 'postgres') {
|
||||
const createdTags = await WIKI.models.tags.query().insert(newTags)
|
||||
existingTags = _.concat(existingTags, createdTags)
|
||||
} else {
|
||||
for (const newTag of newTags) {
|
||||
const createdTag = await WIKI.models.tags.query().insert(newTag)
|
||||
existingTags.push(createdTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch current page tags
|
||||
|
||||
const targetTags = _.filter(existingTags, t => _.includes(tags, t.tag))
|
||||
const currentTags = await page.$relatedQuery('tags')
|
||||
|
||||
// Tags to relate
|
||||
|
||||
const tagsToRelate = _.differenceBy(targetTags, currentTags, 'id')
|
||||
if (tagsToRelate.length > 0) {
|
||||
if (WIKI.config.db.type === 'postgres') {
|
||||
await page.$relatedQuery('tags').relate(tagsToRelate)
|
||||
} else {
|
||||
for (const tag of tagsToRelate) {
|
||||
await page.$relatedQuery('tags').relate(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tags to unrelate
|
||||
|
||||
const tagsToUnrelate = _.differenceBy(currentTags, targetTags, 'id')
|
||||
if (tagsToUnrelate.length > 0) {
|
||||
await page.$relatedQuery('tags').unrelate().whereIn('tags.id', _.map(tagsToUnrelate, 'id'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5
server/views/tags.pug
Normal file
5
server/views/tags.pug
Normal file
@ -0,0 +1,5 @@
|
||||
extends master.pug
|
||||
|
||||
block body
|
||||
#root
|
||||
tags
|
Loading…
Reference in New Issue
Block a user