mirror of https://github.com/requarks/wiki
parent
bb21f6ed3e
commit
1def5289af
@ -0,0 +1,205 @@
|
||||
<template lang='pug'>
|
||||
v-container(fluid, grid-list-lg)
|
||||
v-layout(row, wrap)
|
||||
v-flex(xs12)
|
||||
.admin-header
|
||||
img.animated.fadeInUp(src='/svg/icon-chat-bubble.svg', alt='Comments', style='width: 80px;')
|
||||
.admin-header-title
|
||||
.headline.primary--text.animated.fadeInLeft {{$t('admin:comments.title')}}
|
||||
.subtitle-1.grey--text.animated.fadeInLeft.wait-p2s {{$t('admin:comments.subtitle')}}
|
||||
v-spacer
|
||||
v-btn.mx-1.animated.fadeInDown.wait-p2s(outlined, color='grey', @click='refresh', large)
|
||||
v-icon mdi-refresh
|
||||
v-btn.ml-1.animated.fadeInDown(color='success', @click='save', depressed, large)
|
||||
v-icon(left) mdi-check
|
||||
span {{$t('common:actions.apply')}}
|
||||
|
||||
v-flex(lg3, xs12)
|
||||
v-card.animated.fadeInUp
|
||||
v-toolbar(flat, color='primary', dark, dense)
|
||||
.subtitle-1 {{$t('admin:comments.providers')}}
|
||||
v-list.py-0(two-line, dense)
|
||||
template(v-for='(provider, idx) in providers')
|
||||
v-list-item(:key='provider.key', @click='selectedProvider = provider.key', :disabled='!provider.isAvailable')
|
||||
v-list-item-avatar(size='24')
|
||||
v-icon(color='grey', v-if='!provider.isAvailable') mdi-minus-box-outline
|
||||
v-icon(color='primary', v-else-if='provider.key === selectedProvider') mdi-checkbox-marked-circle-outline
|
||||
v-icon(color='grey', v-else) mdi-checkbox-blank-circle-outline
|
||||
v-list-item-content
|
||||
v-list-item-title.body-2(:class='!provider.isAvailable ? `grey--text` : (selectedProvider === provider.key ? `primary--text` : ``)') {{ provider.title }}
|
||||
v-list-item-subtitle: .caption(:class='!provider.isAvailable ? `grey--text text--lighten-1` : (selectedProvider === provider.key ? `blue--text ` : ``)') {{ provider.description }}
|
||||
v-list-item-avatar(v-if='selectedProvider === provider.key', size='24')
|
||||
v-icon.animated.fadeInLeft(color='primary', large) mdi-chevron-right
|
||||
v-divider(v-if='idx < providers.length - 1')
|
||||
|
||||
v-flex(lg9, xs12)
|
||||
v-card.animated.fadeInUp.wait-p2s
|
||||
v-toolbar(color='primary', dense, flat, dark)
|
||||
.subtitle-1 {{provider.title}}
|
||||
v-card-text
|
||||
.providerlogo
|
||||
img(:src='provider.logo', :alt='provider.title')
|
||||
.caption.pt-3 {{provider.description}}
|
||||
.caption.pb-3: a(:href='provider.website') {{provider.website}}
|
||||
v-divider.mt-3
|
||||
.overline.my-5 {{$t('admin:comments.providerConfig')}}
|
||||
.body-2.ml-3(v-if='!provider.config || provider.config.length < 1'): em {{$t('admin:comments.providerNoConfig')}}
|
||||
template(v-else, v-for='cfg in provider.config')
|
||||
v-select(
|
||||
v-if='cfg.value.type === "string" && cfg.value.enum'
|
||||
outlined
|
||||
:items='cfg.value.enum'
|
||||
:key='cfg.key'
|
||||
:label='cfg.value.title'
|
||||
v-model='cfg.value.value'
|
||||
prepend-icon='mdi-cog-box'
|
||||
:hint='cfg.value.hint ? cfg.value.hint : ""'
|
||||
persistent-hint
|
||||
:class='cfg.value.hint ? "mb-2" : ""'
|
||||
)
|
||||
v-switch.mb-3(
|
||||
v-else-if='cfg.value.type === "boolean"'
|
||||
:key='cfg.key'
|
||||
:label='cfg.value.title'
|
||||
v-model='cfg.value.value'
|
||||
color='primary'
|
||||
prepend-icon='mdi-cog-box'
|
||||
:hint='cfg.value.hint ? cfg.value.hint : ""'
|
||||
persistent-hint
|
||||
inset
|
||||
)
|
||||
v-textarea(
|
||||
v-else-if='cfg.value.type === "string" && cfg.value.multiline'
|
||||
outlined
|
||||
:key='cfg.key'
|
||||
:label='cfg.value.title'
|
||||
v-model='cfg.value.value'
|
||||
prepend-icon='mdi-cog-box'
|
||||
:hint='cfg.value.hint ? cfg.value.hint : ""'
|
||||
persistent-hint
|
||||
:class='cfg.value.hint ? "mb-2" : ""'
|
||||
)
|
||||
v-text-field(
|
||||
v-else
|
||||
outlined
|
||||
:key='cfg.key'
|
||||
:label='cfg.value.title'
|
||||
v-model='cfg.value.value'
|
||||
prepend-icon='mdi-cog-box'
|
||||
:hint='cfg.value.hint ? cfg.value.hint : ""'
|
||||
persistent-hint
|
||||
:class='cfg.value.hint ? "mb-2" : ""'
|
||||
)
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
providers: [],
|
||||
selectedProvider: '',
|
||||
provider: {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selectedProvider(newValue, oldValue) {
|
||||
this.provider = _.find(this.providers, ['key', newValue]) || {}
|
||||
},
|
||||
providers(newValue, oldValue) {
|
||||
this.selectedProvider = _.get(_.find(this.providers, 'isEnabled'), 'key', 'db')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async refresh() {
|
||||
await this.$apollo.queries.providers.refetch()
|
||||
this.$store.commit('showNotification', {
|
||||
message: this.$t('admin:comments.listRefreshSuccess'),
|
||||
style: 'success',
|
||||
icon: 'cached'
|
||||
})
|
||||
},
|
||||
async save() {
|
||||
this.$store.commit(`loadingStart`, 'admin-comments-saveproviders')
|
||||
try {
|
||||
const resp = await this.$apollo.mutate({
|
||||
mutation: gql``,
|
||||
variables: {
|
||||
providers: this.providers.map(tgt => ({
|
||||
isEnabled: tgt.key === this.selectedProvider,
|
||||
key: tgt.key,
|
||||
config: tgt.config.map(cfg => ({...cfg, value: JSON.stringify({ v: cfg.value.value })}))
|
||||
}))
|
||||
}
|
||||
})
|
||||
if (_.get(resp, 'data.comments.updateEngines.responseResult.succeeded', false)) {
|
||||
this.$store.commit('showNotification', {
|
||||
message: this.$t('admin:comments.configSaveSuccess'),
|
||||
style: 'success',
|
||||
icon: 'check'
|
||||
})
|
||||
} else {
|
||||
throw new Error(_.get(resp, 'data.comments.updateEngines.responseResult.message', this.$t('common:error.unexpected')))
|
||||
}
|
||||
} catch (err) {
|
||||
this.$store.commit('pushGraphError', err)
|
||||
}
|
||||
this.$store.commit(`loadingStop`, 'admin-comments-saveengines')
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
providers: {
|
||||
query: gql`
|
||||
query {
|
||||
comments {
|
||||
providers {
|
||||
isEnabled
|
||||
key
|
||||
title
|
||||
description
|
||||
logo
|
||||
website
|
||||
isAvailable
|
||||
config {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
fetchPolicy: 'network-only',
|
||||
update: (data) => _.cloneDeep(data.comments.providers).map(str => ({
|
||||
...str,
|
||||
config: _.sortBy(str.config.map(cfg => ({
|
||||
...cfg,
|
||||
value: JSON.parse(cfg.value)
|
||||
})), [t => t.value.order])
|
||||
})),
|
||||
watchLoading (isLoading) {
|
||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-comments-refresh')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
.providerlogo {
|
||||
width: 250px;
|
||||
height: 85px;
|
||||
float:right;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
After Width: | Height: | Size: 2.2 KiB |
@ -0,0 +1,10 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
.createTable('commentProviders', table => {
|
||||
table.string('key').notNullable().primary()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.json('config').notNullable()
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -0,0 +1,16 @@
|
||||
/* global WIKI */
|
||||
|
||||
exports.up = knex => {
|
||||
const dbCompat = {
|
||||
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
|
||||
}
|
||||
return knex.schema
|
||||
.createTable('commentProviders', table => {
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.string('key').notNullable().primary()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.json('config').notNullable()
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => { }
|
@ -1,39 +1,57 @@
|
||||
const _ = require('lodash')
|
||||
const graphHelper = require('../../helpers/graph')
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
module.exports = {
|
||||
// Query: {
|
||||
// comments(obj, args, context, info) {
|
||||
// return WIKI.models.Comment.findAll({ where: args })
|
||||
// }
|
||||
// },
|
||||
// Mutation: {
|
||||
// createComment(obj, args) {
|
||||
// return WIKI.models.Comment.create({
|
||||
// content: args.content,
|
||||
// author: args.userId,
|
||||
// document: args.documentId
|
||||
// })
|
||||
// },
|
||||
// deleteComment(obj, args) {
|
||||
// return WIKI.models.Comment.destroy({
|
||||
// where: {
|
||||
// id: args.id
|
||||
// },
|
||||
// limit: 1
|
||||
// })
|
||||
// },
|
||||
// modifyComment(obj, args) {
|
||||
// return WIKI.models.Comment.update({
|
||||
// content: args.content
|
||||
// }, {
|
||||
// where: { id: args.id }
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
// Comment: {
|
||||
// author(cm) {
|
||||
// return cm.getAuthor()
|
||||
// },
|
||||
// document(cm) {
|
||||
// return cm.getDocument()
|
||||
// }
|
||||
// }
|
||||
Query: {
|
||||
async comments() { return {} }
|
||||
},
|
||||
Mutation: {
|
||||
async comments() { return {} }
|
||||
},
|
||||
CommentQuery: {
|
||||
async providers(obj, args, context, info) {
|
||||
const providers = await WIKI.models.commentProviders.getProviders()
|
||||
return providers.map(provider => {
|
||||
const providerInfo = _.find(WIKI.data.commentProviders, ['key', provider.key]) || {}
|
||||
return {
|
||||
...providerInfo,
|
||||
...provider,
|
||||
config: _.sortBy(_.transform(provider.config, (res, value, key) => {
|
||||
const configData = _.get(providerInfo.props, key, false)
|
||||
if (configData) {
|
||||
res.push({
|
||||
key,
|
||||
value: JSON.stringify({
|
||||
...configData,
|
||||
value
|
||||
})
|
||||
})
|
||||
}
|
||||
}, []), 'key')
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
CommentMutation: {
|
||||
async updateProviders(obj, args, context) {
|
||||
try {
|
||||
for (let provider of args.providers) {
|
||||
await WIKI.models.providers.query().patch({
|
||||
isEnabled: provider.isEnabled,
|
||||
config: _.reduce(provider.config, (result, value, key) => {
|
||||
_.set(result, `${value.key}`, _.get(JSON.parse(value.value), 'v', null))
|
||||
return result
|
||||
}, {})
|
||||
}).where('key', provider.key)
|
||||
}
|
||||
return {
|
||||
responseResult: graphHelper.generateSuccess('Comment Providers updated successfully')
|
||||
}
|
||||
} catch (err) {
|
||||
return graphHelper.generateError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
# ===============================================
|
||||
# COMMENT
|
||||
# ===============================================
|
||||
|
||||
extend type Query {
|
||||
comments: CommentQuery
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
comments: CommentMutation
|
||||
}
|
||||
|
||||
# -----------------------------------------------
|
||||
# QUERIES
|
||||
# -----------------------------------------------
|
||||
|
||||
type CommentQuery {
|
||||
providers: [CommentProvider] @auth(requires: ["manage:system"])
|
||||
}
|
||||
|
||||
# -----------------------------------------------
|
||||
# MUTATIONS
|
||||
# -----------------------------------------------
|
||||
|
||||
type CommentMutation {
|
||||
updateProviders(
|
||||
providers: [CommentProviderInput]
|
||||
): DefaultResponse @auth(requires: ["manage:system"])
|
||||
|
||||
rebuildIndex: DefaultResponse @auth(requires: ["manage:system"])
|
||||
}
|
||||
|
||||
# -----------------------------------------------
|
||||
# TYPES
|
||||
# -----------------------------------------------
|
||||
|
||||
type CommentProvider {
|
||||
isEnabled: Boolean!
|
||||
key: String!
|
||||
title: String!
|
||||
description: String
|
||||
logo: String
|
||||
website: String
|
||||
isAvailable: Boolean
|
||||
config: [KeyValuePair]
|
||||
}
|
||||
|
||||
input CommentProviderInput {
|
||||
isEnabled: Boolean!
|
||||
key: String!
|
||||
config: [KeyValuePairInput]
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
const Model = require('objection').Model
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const _ = require('lodash')
|
||||
const yaml = require('js-yaml')
|
||||
const commonHelper = require('../helpers/common')
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
/**
|
||||
* CommentProvider model
|
||||
*/
|
||||
module.exports = class CommentProvider extends Model {
|
||||
static get tableName() { return 'commentProviders' }
|
||||
static get idColumn() { return 'key' }
|
||||
|
||||
static get jsonSchema () {
|
||||
return {
|
||||
type: 'object',
|
||||
required: ['key', 'isEnabled'],
|
||||
|
||||
properties: {
|
||||
key: {type: 'string'},
|
||||
isEnabled: {type: 'boolean'}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static get jsonAttributes() {
|
||||
return ['config']
|
||||
}
|
||||
|
||||
static async getProvider(key) {
|
||||
return WIKI.models.commentProviders.query().findOne({ key })
|
||||
}
|
||||
|
||||
static async getProviders(isEnabled) {
|
||||
const providers = await WIKI.models.commentProviders.query().where(_.isBoolean(isEnabled) ? { isEnabled } : {})
|
||||
return _.sortBy(providers, ['key'])
|
||||
}
|
||||
|
||||
static async refreshProvidersFromDisk() {
|
||||
let trx
|
||||
try {
|
||||
const dbProviders = await WIKI.models.commentProviders.query()
|
||||
|
||||
// -> Fetch definitions from disk
|
||||
const authDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/comments'))
|
||||
let diskProviders = []
|
||||
for (let dir of authDirs) {
|
||||
const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/comments', dir, 'definition.yml'), 'utf8')
|
||||
diskProviders.push(yaml.safeLoad(def))
|
||||
}
|
||||
WIKI.data.commentProviders = diskProviders.map(engine => ({
|
||||
...engine,
|
||||
props: commonHelper.parseModuleProps(engine.props)
|
||||
}))
|
||||
|
||||
let newProviders = []
|
||||
for (let engine of WIKI.data.commentProviders) {
|
||||
if (!_.some(dbProviders, ['key', engine.key])) {
|
||||
newProviders.push({
|
||||
key: engine.key,
|
||||
isEnabled: engine.key === 'default',
|
||||
config: _.transform(engine.props, (result, value, key) => {
|
||||
_.set(result, key, value.default)
|
||||
return result
|
||||
}, {})
|
||||
})
|
||||
} else {
|
||||
const engineConfig = _.get(_.find(dbProviders, ['key', engine.key]), 'config', {})
|
||||
await WIKI.models.commentProviders.query().patch({
|
||||
config: _.transform(engine.props, (result, value, key) => {
|
||||
if (!_.has(result, key)) {
|
||||
_.set(result, key, value.default)
|
||||
}
|
||||
return result
|
||||
}, engineConfig)
|
||||
}).where('key', engine.key)
|
||||
}
|
||||
}
|
||||
if (newProviders.length > 0) {
|
||||
trx = await WIKI.models.Objection.transaction.start(WIKI.models.knex)
|
||||
for (let engine of newProviders) {
|
||||
await WIKI.models.commentProviders.query(trx).insert(engine)
|
||||
}
|
||||
await trx.commit()
|
||||
WIKI.logger.info(`Loaded ${newProviders.length} new comment providers: [ OK ]`)
|
||||
} else {
|
||||
WIKI.logger.info(`No new comment providers found: [ SKIPPED ]`)
|
||||
}
|
||||
} catch (err) {
|
||||
WIKI.logger.error(`Failed to scan or load new comment providers: [ FAILED ]`)
|
||||
WIKI.logger.error(err)
|
||||
if (trx) {
|
||||
trx.rollback()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
main: |
|
||||
<div id="commento"></div>
|
||||
bodyEnd: |
|
||||
<script defer src="{{instanceUrl}}/js/commento.js"></script>
|
@ -0,0 +1,17 @@
|
||||
key: commento
|
||||
title: Commento
|
||||
description: A fast, privacy-focused commenting platform.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/commento.svg
|
||||
website: https://commento.io/
|
||||
displayMode: footer
|
||||
codeTemplate: true
|
||||
isAvailable: true
|
||||
props:
|
||||
instanceUrl:
|
||||
type: String
|
||||
title: Instance URL
|
||||
default: 'https://cdn.commento.io'
|
||||
hint: The URL (without a trailing slash) to the Commento instance. Leave the default https://cdn.commento.io if using the cloud-hosted version.
|
||||
order: 1
|
||||
|
@ -0,0 +1,19 @@
|
||||
key: default
|
||||
title: Default
|
||||
description: Built-in advanced comments tool.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/wikijs-butterfly.svg
|
||||
website: https://wiki.js.org
|
||||
displayMode: dynamic
|
||||
codeTemplate: false
|
||||
isAvailable: true
|
||||
props:
|
||||
displayMode:
|
||||
type: String
|
||||
title: Display mode
|
||||
default: 'page'
|
||||
enum:
|
||||
- inline
|
||||
- page
|
||||
hint: Whether to display the comments under the content (inline) or on a dedicated page (page).
|
||||
order: 1
|
@ -0,0 +1,15 @@
|
||||
main: |
|
||||
<div id="disqus_thread"></div>
|
||||
bodyEnd: |
|
||||
<script>
|
||||
var disqus_config = function () {
|
||||
this.page.url = {{pageUrl}};
|
||||
this.page.identifier = {{pageId}};
|
||||
};
|
||||
(function() {
|
||||
var d = document, s = d.createElement('script');
|
||||
s.src = 'https://{{shortName}}.disqus.com/embed.js';
|
||||
s.setAttribute('data-timestamp', +new Date());
|
||||
(d.head || d.body).appendChild(s);
|
||||
})();
|
||||
</script>
|
@ -0,0 +1,16 @@
|
||||
key: disqus
|
||||
title: Disqus
|
||||
description: Disqus help publishers power online discussions with comments.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/disqus.svg
|
||||
website: https://disqus.com/
|
||||
displayMode: footer
|
||||
codeTemplate: true
|
||||
isAvailable: true
|
||||
props:
|
||||
accountName:
|
||||
type: String
|
||||
title: Shortname
|
||||
default: ''
|
||||
hint: Unique identifier from Disqus to identify your website
|
||||
order: 1
|
Loading…
Reference in new issue