From 4cb9f3d9c658aaeef9e9a219f3868798e747eadb Mon Sep 17 00:00:00 2001 From: Nicolas Giard Date: Sun, 26 Aug 2018 22:38:08 -0400 Subject: [PATCH] feat: rendering modules + admin rendering --- client/components/admin/admin-rendering.vue | 201 ++++++------------ client/components/setup.vue | 194 ++--------------- .../rendering-mutation-save-renderers.gql | 12 ++ .../rendering/rendering-query-renderers.gql | 18 ++ server/core/kernel.js | 1 + server/db/migrations/2.0.0.js | 7 +- server/graph/resolvers/rendering.js | 58 +++++ server/graph/schemas/rendering.graphql | 55 +++++ server/models/renderers.js | 108 ++++++++++ .../renderer/html-asciinema/definition.yml | 11 +- .../renderer/html-blockquotes/definition.yml | 11 +- .../modules/renderer/html-core/definition.yml | 8 + server/modules/renderer/html-core/renderer.js | 1 + .../renderer/html-mathjax/definition.yml | 11 +- .../renderer/html-mediaplayers/definition.yml | 11 +- .../renderer/html-security/definition.yml | 11 +- .../renderer/markdown-abbr/definition.yml | 5 +- .../renderer/markdown-core/definition.yml | 4 +- .../renderer/markdown-emoji/definition.yml | 5 +- .../markdown-expandtabs/definition.yml | 5 +- .../markdown-footnotes/definition.yml | 5 +- .../renderer/markdown-mathjax/definition.yml | 5 +- .../renderer/markdown-mermaid/definition.yml | 5 +- .../renderer/markdown-plantuml/definition.yml | 5 +- .../markdown-tasklists/definition.yml | 5 +- server/setup.js | 188 +--------------- 26 files changed, 413 insertions(+), 537 deletions(-) create mode 100644 client/graph/admin/rendering/rendering-mutation-save-renderers.gql create mode 100644 client/graph/admin/rendering/rendering-query-renderers.gql create mode 100644 server/graph/resolvers/rendering.js create mode 100644 server/graph/schemas/rendering.graphql create mode 100644 server/models/renderers.js create mode 100644 server/modules/renderer/html-core/definition.yml create mode 100644 server/modules/renderer/html-core/renderer.js diff --git a/client/components/admin/admin-rendering.vue b/client/components/admin/admin-rendering.vue index 8e3c2e3e..cdca6214 100644 --- a/client/components/admin/admin-rendering.vue +++ b/client/components/admin/admin-rendering.vue @@ -6,141 +6,45 @@ .subheading.grey--text Configure how content is rendered v-layout.mt-3(row wrap) v-flex(lg3 xs12) - v-card - v-toolbar( - color='primary' - dense - flat - dark + v-toolbar( + color='primary' + dense + flat + dark + ) + v-icon.mr-2 line_weight + .subheading Pipeline + v-expansion-panel.adm-rendering-pipeline + v-expansion-panel-content( + hide-actions + v-for='core in cores' + :key='core.key' ) - v-icon.mr-2 line_weight - .subheading Pipeline - v-toolbar( - color='blue' - dense - dark - ) - v-icon.mr-2 arrow_downward - .body-2 Markdown - v-spacer - v-btn( - icon - @click='' + v-toolbar( + slot='header' + color='blue' + dense + dark + flat ) - v-icon add - v-list(two-line, dense) - v-list-tile(avatar) - v-list-tile-avatar - v-icon(color='grey') crop_free - v-list-tile-content - v-list-tile-title Core - v-list-tile-sub-title Basic Markdown parser - v-list-tile-avatar - v-icon(color='green', small) lens - v-divider.my-0 - v-list-tile(avatar) - v-list-tile-avatar - v-icon(color='grey') tag_faces - v-list-tile-content - v-list-tile-title Emoji - v-list-tile-sub-title Convert tags to emojis - v-list-tile-avatar - v-icon(color='green', small) lens - v-divider.my-0 - v-list-tile(avatar) - v-list-tile-avatar - v-icon(color='grey') list - v-list-tile-content - v-list-tile-title Task Lists - v-list-tile-sub-title Parse task lists to checkboxes - v-list-tile-avatar - v-icon(color='green', small) lens - v-divider.my-0 - v-list-tile(avatar) - v-list-tile-avatar - v-icon(color='grey') multiline_chart - v-list-tile-content - v-list-tile-title PlantUML - v-list-tile-sub-title Generate diagrams from PlantUML syntax - v-list-tile-avatar - v-icon(color='green', small) lens - v-divider.my-0 - v-list-tile(avatar) - v-list-tile-avatar - v-icon(color='grey') merge_type - v-list-tile-content - v-list-tile-title Mermaid - v-list-tile-sub-title Generate flowcharts from Mermaid syntax - v-list-tile-avatar - v-icon(color='green', small) lens - v-divider.my-0 - v-list-tile(avatar) - v-list-tile-avatar - v-icon(color='grey') functions - v-list-tile-content - v-list-tile-title Mathjax Pre-Processor - v-list-tile-sub-title Parse Mathjax content from Markdown - v-list-tile-avatar - v-icon(color='red', small) trip_origin - - v-toolbar( - color='blue' - dense - dark - ) - v-icon.mr-2 arrow_downward - .body-2 HTML - v-spacer - v-btn( - icon - @click='' - ) - v-icon add - v-list(two-line, dense) - v-list-tile(avatar) - v-list-tile-avatar - v-icon(color='grey') subscriptions - v-list-tile-content - v-list-tile-title Video Players - v-list-tile-sub-title Embed video players such as Youtube, Vimeo, etc. - v-list-tile-avatar - v-icon(color='green', small) lens - v-divider.my-0 - v-list-tile(avatar) - v-list-tile-avatar - v-icon(color='grey') subtitles - v-list-tile-content - v-list-tile-title Asciinema - v-list-tile-sub-title Embed asciinema players from compatible links - v-list-tile-avatar - v-icon(color='green', small) lens - v-divider.my-0 - v-list-tile(avatar) - v-list-tile-avatar - v-icon(color='grey') volume_up - v-list-tile-content - v-list-tile-title Audio Players - v-list-tile-sub-title Embed audio players for audio content - v-list-tile-avatar - v-icon(color='green', small) lens - v-divider.my-0 - v-list-tile(avatar) - v-list-tile-avatar - v-icon(color='grey') insert_comment - v-list-tile-content - v-list-tile-title Blockquotes - v-list-tile-sub-title Process styled blockquotes - v-list-tile-avatar - v-icon(color='green', small) lens - v-divider.my-0 - v-list-tile(avatar) - v-list-tile-avatar - v-icon(color='grey') functions - v-list-tile-content - v-list-tile-title Mathjax Processor - v-list-tile-sub-title TeX/MathML Math Equations Parser - v-list-tile-avatar - v-icon(color='red', small) trip_origin + .body-2 {{core.input}} + v-icon.mx-2 arrow_forward + .caption {{core.output}} + v-list(two-line, dense) + v-list-tile( + avatar + v-for='rdr in core.children' + :key='rdr.key' + ) + v-list-tile-avatar + v-icon(color='grey') {{rdr.icon}} + v-list-tile-content + v-list-tile-title {{rdr.title}} + v-list-tile-sub-title {{rdr.description}} + v-list-tile-avatar + v-icon(color='green', small, v-if='rdr.isEnabled') lens + v-icon(color='red', small, v-else) trip_origin + v-divider.my-0 v-flex(lg9 xs12) v-card @@ -201,16 +105,45 @@ diff --git a/client/components/setup.vue b/client/components/setup.vue index 40a21f84..8b261d05 100644 --- a/client/components/setup.vue +++ b/client/components/setup.vue @@ -14,22 +14,14 @@ small Wiki.js Installation Wizard v-divider v-stepper-step(step='2' :complete='state > 2') - | System Check - small Checking your system for compatibility - v-divider - v-stepper-step(step='3' :complete='state > 3') - | General Information - small Site Title, Language and Access - v-divider - v-stepper-step(step='4' :complete='state > 4') | Administration Account small Create the admin account v-divider(v-if='this.conf.upgrade') - v-stepper-step(step='5' :complete='state > 5', v-if='this.conf.upgrade') + v-stepper-step(step='3' :complete='state > 3', v-if='this.conf.upgrade') | Upgrade from Wiki.js 1.x small Migrate your existing installation v-divider - v-stepper-step(:step='this.conf.upgrade ? 6 : 5' :complete='this.conf.upgrade ? state > 5 : state > 6') + v-stepper-step(:step='this.conf.upgrade ? 4 : 3' :complete='this.conf.upgrade ? state > 3 : state > 4') | Final Steps small Finalizing your installation @@ -68,126 +60,13 @@ ) v-divider .pt-3.text-xs-center - v-btn(color='primary', @click='proceedToSyscheck', :disabled='loading') Start - - //- ============================================== - //- SYSTEM CHECK - //- ============================================== - - v-stepper-content(step='2') - v-card.text-xs-center(flat) - svg.icons.is-64: use(xlink:href='#nc-metrics') - .subheading System Check - v-container - v-layout(row, align-center, v-if='loading') - v-progress-circular(v-if='loading', indeterminate, color='blue') - .body-2.blue--text.ml-3 Checking your system for compatibility... - v-alert(type='success', outline, :value='!loading && syscheck.ok') Looks good! No issues so far. - v-alert(type='error', outline, :value='!loading && !syscheck.ok') {{ syscheck.error }} - v-list.mt-3(two-line, v-if='!loading && syscheck.ok', dense) - template(v-for='(rs, n) in syscheck.results') - v-divider(v-if='n > 0', inset) - v-list-tile - v-list-tile-avatar(color='green lighten-5') - v-icon(color='green') check - v-list-tile-content - v-list-tile-title {{rs.title}} - v-list-tile-sub-title {{rs.subtitle}} - v-divider - .pt-3.text-xs-center - v-btn(@click='proceedToWelcome', :disabled='loading') Back - v-btn(color='primary', @click='proceedToSyscheck', v-if='!loading && !syscheck.ok') Check Again - v-btn(color='red', dark, @click='proceedToGeneral', v-if='!loading && !syscheck.ok') Continue Anyway - v-btn(color='primary', @click='proceedToGeneral', v-if='loading || syscheck.ok', :disabled='loading') Continue - - //- ============================================== - //- GENERAL - //- ============================================== - - v-stepper-content(step='3') - v-card.text-xs-center(flat) - svg.icons.is-64: use(xlink:href='#nc-webpage') - .subheading General Information - v-form - v-container - v-layout(row, wrap) - v-flex(xs12, sm6).pr-3 - v-text-field( - outline - background-color='grey lighten-2' - v-model='conf.title', - label='Site Title', - :counter='255', - persistent-hint, - hint='The site title will appear in the top left corner on every page and within the window title bar.', - v-validate='{ required: true, min: 2 }', - data-vv-name='siteTitle', - data-vv-as='Site Title', - data-vv-scope='general', - :error-messages='errors.collect(`siteTitle`)' - ) - v-flex.pr-3(xs12, sm6) - v-text-field( - outline - background-color='grey lighten-2' - v-model='conf.port', - label='Server Port', - persistent-hint, - hint='The port on which Wiki.js will listen to. Usually port 80 if connecting directly, or a random port (e.g. 3000) if using a web server in front of it. Set $(PORT) to use the PORT environment variable.', - v-validate='{ required: true }', - data-vv-name='port', - data-vv-as='Port', - data-vv-scope='general', - :error-messages='errors.collect(`port`)' - ) - v-layout(row, wrap).mt-3 - v-flex(xs12, sm6).pr-3 - v-text-field( - outline - background-color='grey lighten-2' - v-model='conf.pathContent', - label='Content Data Path', - persistent-hint, - hint='The path where content is stored (markdown files, uploads, etc.)', - v-validate='{ required: true, min: 2 }', - data-vv-name='pathContent', - data-vv-as='Content Data Path', - data-vv-scope='general', - :error-messages='errors.collect(`pathContent`)' - ) - v-flex(xs12, sm6) - v-text-field( - outline - background-color='grey lighten-2' - v-model='conf.pathData', - label='Temporary Data Path', - persistent-hint, - hint='The path where temporary data is stored (cache, thumbnails, temporary upload files, etc.)', - v-validate='{ required: true, min: 2 }', - data-vv-name='pathData', - data-vv-as='Temporary Data Path', - data-vv-scope='general', - :error-messages='errors.collect(`pathData`)' - ) - v-layout(row, wrap).mt-3 - v-flex(xs12) - v-checkbox( - color='primary', - v-model='conf.public', - label='Public Access', - persistent-hint, - hint='Should the site be accessible (read only) without login.' - ) - v-divider - .pt-3.text-xs-center - v-btn(@click='proceedToSyscheck', :disabled='loading') Back - v-btn(color='primary', @click='proceedToAdmin', :disabled='loading') Continue + v-btn(color='primary', @click='proceedToAdmin', :disabled='loading') Start //- ============================================== //- ADMINISTRATOR ACCOUNT //- ============================================== - v-stepper-content(step='4') + v-stepper-content(step='2') v-card.text-xs-center(flat) svg.icons.is-64: use(xlink:href='#nc-man-black') .subheading Administrator Account @@ -245,14 +124,14 @@ :error-messages='errors.collect(`adminPasswordConfirm`)' ) .pt-3.text-xs-center - v-btn(@click='proceedToGeneral', :disabled='loading') Back + v-btn(@click='proceedToWelcome', :disabled='loading') Back v-btn(color='primary', @click='proceedToUpgrade', :disabled='loading') Continue //- ============================================== //- UPGRADE FROM 1.x //- ============================================== - v-stepper-content(step='5', v-if='conf.upgrade') + v-stepper-content(step='3', v-if='conf.upgrade') v-card.text-xs-center(flat) svg.icons.is-64: use(xlink:href='#nc-spaceship') .subheading Upgrade from Wiki.js 1.x @@ -283,7 +162,7 @@ //- FINAL //- ============================================== - v-stepper-content(:step='conf.upgrade ? 6 : 5') + v-stepper-content(:step='conf.upgrade ? 4 : 3') v-card.text-xs-center(flat) template(v-if='loading') .mt-3(style='width: 64px; display:inline-block;') @@ -340,11 +219,6 @@ export default { return { loading: false, state: 1, - syscheck: { - ok: false, - error: '', - results: [] - }, final: { ok: false, error: '', @@ -354,13 +228,7 @@ export default { adminEmail: '', adminPassword: '', adminPasswordConfirm: '', - lang: siteConfig.lang || 'en', - pathData: './data', - pathContent: './content', - port: siteConfig.port || 80, - public: (siteConfig.public === true), telemetry: true, - title: siteConfig.title || 'Wiki', upgrade: false, upgMongo: 'mongodb://' }, @@ -373,73 +241,43 @@ export default { this.state = 1 this.loading = false }, - proceedToSyscheck () { - let self = this - this.state = 2 - this.loading = true - this.syscheck = { - ok: false, - error: '', - results: [] - } - - _.delay(() => { - axios.post('/syscheck', self.conf).then(resp => { - if (resp.data.ok === true) { - self.syscheck.ok = true - self.syscheck.results = resp.data.results - } else { - self.syscheck.ok = false - self.syscheck.error = resp.data.error - } - self.loading = false - self.$nextTick() - }).catch(err => { - window.alert(err.message) - }) - }, 1000) - }, - proceedToGeneral () { - this.state = 3 - this.loading = false - }, async proceedToAdmin () { - if (this.state < 4) { + if (this.state < 2) { const validationSuccess = await this.$validator.validateAll('general') if (!validationSuccess) { - this.state = 3 + this.state = 1 return } } - this.state = 4 + this.state = 2 this.loading = false }, async proceedToUpgrade () { - if (this.state < 5) { + if (this.state < 3) { const validationSuccess = await this.$validator.validateAll('admin') if (!validationSuccess || this.conf.adminPassword !== this.conf.adminPasswordConfirm) { - this.state = 4 + this.state = 2 return } } if (this.conf.upgrade) { - this.state = 5 + this.state = 3 this.loading = false } else { this.proceedToFinal() } }, async proceedToFinal () { - if (this.conf.upgrade && this.state < 6) { + if (this.conf.upgrade && this.state < 4) { const validationSuccess = await this.$validator.validateAll('upgrade') if (!validationSuccess) { - this.state = 5 + this.state = 3 return } } - this.state = this.conf.upgrade ? 6 : 5 + this.state = this.conf.upgrade ? 4 : 3 this.loading = true this.final = { ok: false, diff --git a/client/graph/admin/rendering/rendering-mutation-save-renderers.gql b/client/graph/admin/rendering/rendering-mutation-save-renderers.gql new file mode 100644 index 00000000..8a717785 --- /dev/null +++ b/client/graph/admin/rendering/rendering-mutation-save-renderers.gql @@ -0,0 +1,12 @@ +mutation($renderers: [RendererInput]) { + rendering { + updateRenderers(renderers: $renderers) { + responseResult { + succeeded + errorCode + slug + message + } + } + } +} diff --git a/client/graph/admin/rendering/rendering-query-renderers.gql b/client/graph/admin/rendering/rendering-query-renderers.gql new file mode 100644 index 00000000..b117b7ab --- /dev/null +++ b/client/graph/admin/rendering/rendering-query-renderers.gql @@ -0,0 +1,18 @@ +{ + rendering { + renderers { + isEnabled + key + title + description + icon + dependsOn + input + output + config { + key + value + } + } + } +} diff --git a/server/core/kernel.js b/server/core/kernel.js index 3c389c52..44647146 100644 --- a/server/core/kernel.js +++ b/server/core/kernel.js @@ -51,6 +51,7 @@ module.exports = { async postBootMaster() { await WIKI.models.authentication.refreshStrategiesFromDisk() await WIKI.models.editors.refreshEditorsFromDisk() + await WIKI.models.renderers.refreshRenderersFromDisk() await WIKI.models.storage.refreshTargetsFromDisk() await WIKI.auth.activateStrategies() diff --git a/server/db/migrations/2.0.0.js b/server/db/migrations/2.0.0.js index 380e7e48..e441cad1 100644 --- a/server/db/migrations/2.0.0.js +++ b/server/db/migrations/2.0.0.js @@ -96,7 +96,7 @@ exports.up = knex => { table.string('createdAt').notNullable() table.string('updatedAt').notNullable() }) - // STORAGE ----------------------------- + // RENDERERS --------------------------- .createTable('renderers', table => { table.increments('id').primary() table.string('key').notNullable().unique() @@ -199,14 +199,19 @@ exports.up = knex => { exports.down = knex => { return knex.schema .dropTableIfExists('userGroups') + .dropTableIfExists('pageHistoryTags') + .dropTableIfExists('pageHistory') .dropTableIfExists('pageTags') .dropTableIfExists('assets') .dropTableIfExists('assetFolders') .dropTableIfExists('comments') + .dropTableIfExists('editors') .dropTableIfExists('groups') .dropTableIfExists('locales') .dropTableIfExists('pages') + .dropTableIfExists('renderers') .dropTableIfExists('settings') + .dropTableIfExists('storage') .dropTableIfExists('tags') .dropTableIfExists('users') } diff --git a/server/graph/resolvers/rendering.js b/server/graph/resolvers/rendering.js new file mode 100644 index 00000000..8917fba4 --- /dev/null +++ b/server/graph/resolvers/rendering.js @@ -0,0 +1,58 @@ +const _ = require('lodash') +const graphHelper = require('../../helpers/graph') + +/* global WIKI */ + +module.exports = { + Query: { + async rendering() { return {} } + }, + Mutation: { + async rendering() { return {} } + }, + RenderingQuery: { + async renderers(obj, args, context, info) { + let renderers = await WIKI.models.renderers.getRenderers() + renderers = renderers.map(rdr => { + const rendererInfo = _.find(WIKI.data.renderers, ['key', rdr.key]) || {} + return { + ...rendererInfo, + ...rdr, + config: _.sortBy(_.transform(rdr.config, (res, value, key) => { + const configData = _.get(rendererInfo.props, key, {}) + res.push({ + key, + value: JSON.stringify({ + ...configData, + value + }) + }) + }, []), 'key') + } + }) + if (args.filter) { renderers = graphHelper.filter(renderers, args.filter) } + if (args.orderBy) { renderers = graphHelper.orderBy(renderers, args.orderBy) } + return renderers + } + }, + RenderingMutation: { + async updateRenderers(obj, args, context) { + try { + for (let rdr of args.renderers) { + await WIKI.models.storage.query().patch({ + isEnabled: rdr.isEnabled, + config: _.reduce(rdr.config, (result, value, key) => { + _.set(result, `${value.key}`, value.value) + return result + }, {}) + }).where('key', rdr.key) + } + return { + responseResult: graphHelper.generateSuccess('Renderers updated successfully') + } + } catch (err) { + return graphHelper.generateError(err) + } + } + } +} diff --git a/server/graph/schemas/rendering.graphql b/server/graph/schemas/rendering.graphql new file mode 100644 index 00000000..13d5eb0f --- /dev/null +++ b/server/graph/schemas/rendering.graphql @@ -0,0 +1,55 @@ +# =============================================== +# RENDERING +# =============================================== + +extend type Query { + rendering: RenderingQuery +} + +extend type Mutation { + rendering: RenderingMutation +} + +# ----------------------------------------------- +# QUERIES +# ----------------------------------------------- + +type RenderingQuery { + renderers( + filter: String + orderBy: String + ): [Renderer] +} + +# ----------------------------------------------- +# MUTATIONS +# ----------------------------------------------- + +type RenderingMutation { + updateRenderers( + renderers: [RendererInput] + ): DefaultResponse +} + +# ----------------------------------------------- +# TYPES +# ----------------------------------------------- + +type Renderer { + isEnabled: Boolean! + key: String! + title: String! + description: String + icon: String + dependsOn: String + input: String + output: String + config: [KeyValuePair] +} + +input RendererInput { + isEnabled: Boolean! + key: String! + mode: String! + config: [KeyValuePairInput] +} diff --git a/server/models/renderers.js b/server/models/renderers.js new file mode 100644 index 00000000..96b9d524 --- /dev/null +++ b/server/models/renderers.js @@ -0,0 +1,108 @@ +const Model = require('objection').Model +const path = require('path') +const fs = require('fs-extra') +const _ = require('lodash') +const yaml = require('js-yaml') +const commonHelper = require('../helpers/common') + +/* global WIKI */ + +/** + * Renderer model + */ +module.exports = class Renderer extends Model { + static get tableName() { return 'renderers' } + + static get jsonSchema () { + return { + type: 'object', + required: ['key', 'isEnabled'], + + properties: { + id: {type: 'integer'}, + key: {type: 'string'}, + isEnabled: {type: 'boolean'}, + config: {type: 'object'} + } + } + } + + static async getRenderers() { + return WIKI.models.renderers.query() + } + + static async refreshRenderersFromDisk() { + let trx + try { + const dbRenderers = await WIKI.models.renderers.query() + + // -> Fetch definitions from disk + const rendererDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/renderer')) + let diskRenderers = [] + for (let dir of rendererDirs) { + const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/renderer', dir, 'definition.yml'), 'utf8') + diskRenderers.push(yaml.safeLoad(def)) + } + WIKI.data.renderers = diskRenderers.map(renderer => ({ + ...renderer, + props: commonHelper.parseModuleProps(renderer.props) + })) + + // -> Insert new Renderers + let newRenderers = [] + for (let renderer of WIKI.data.renderers) { + if (!_.some(dbRenderers, ['key', renderer.key])) { + newRenderers.push({ + key: renderer.key, + isEnabled: _.get(renderer, 'enabledDefault', true), + config: _.transform(renderer.props, (result, value, key) => { + _.set(result, key, value.default) + return result + }, {}) + }) + } else { + const rendererConfig = _.get(_.find(dbRenderers, ['key', renderer.key]), 'config', {}) + await WIKI.models.renderers.query().patch({ + config: _.transform(renderer.props, (result, value, key) => { + if (!_.has(result, key)) { + _.set(result, key, value.default) + } + return result + }, rendererConfig) + }).where('key', renderer.key) + } + } + if (newRenderers.length > 0) { + trx = await WIKI.models.Objection.transaction.start(WIKI.models.knex) + for (let renderer of newRenderers) { + await WIKI.models.renderers.query(trx).insert(renderer) + } + await trx.commit() + WIKI.logger.info(`Loaded ${newRenderers.length} new renderers: [ OK ]`) + } else { + WIKI.logger.info(`No new renderers found: [ SKIPPED ]`) + } + } catch (err) { + WIKI.logger.error(`Failed to scan or load new renderers: [ FAILED ]`) + WIKI.logger.error(err) + if (trx) { + trx.rollback() + } + } + } + + static async pageEvent({ event, page }) { + const targets = await WIKI.models.storage.query().where('isEnabled', true) + if (targets && targets.length > 0) { + _.forEach(targets, target => { + WIKI.queue.job.syncStorage.add({ + event, + target, + page + }, { + removeOnComplete: true + }) + }) + } + } +} diff --git a/server/modules/renderer/html-asciinema/definition.yml b/server/modules/renderer/html-asciinema/definition.yml index 0454c0ea..f1f78ff7 100644 --- a/server/modules/renderer/html-asciinema/definition.yml +++ b/server/modules/renderer/html-asciinema/definition.yml @@ -1,7 +1,8 @@ -key: markdownAbbr -title: Abbreviations -description: Parse abbreviations into abbr tags +key: htmlAsciinema +title: Asciinema +description: Embed asciinema players from compatible links author: requarks.io -dependsOn: - - markdownCore +icon: subtitles +enabledDefault: false +dependsOn: htmlCore props: {} diff --git a/server/modules/renderer/html-blockquotes/definition.yml b/server/modules/renderer/html-blockquotes/definition.yml index 0454c0ea..e299688e 100644 --- a/server/modules/renderer/html-blockquotes/definition.yml +++ b/server/modules/renderer/html-blockquotes/definition.yml @@ -1,7 +1,8 @@ -key: markdownAbbr -title: Abbreviations -description: Parse abbreviations into abbr tags +key: htmlBlockquotes +title: Blockquotes +description: Embed audio players for audio content author: requarks.io -dependsOn: - - markdownCore +icon: insert_comment +enabledDefault: true +dependsOn: htmlCore props: {} diff --git a/server/modules/renderer/html-core/definition.yml b/server/modules/renderer/html-core/definition.yml new file mode 100644 index 00000000..e7108d52 --- /dev/null +++ b/server/modules/renderer/html-core/definition.yml @@ -0,0 +1,8 @@ +key: htmlCore +title: Core +description: Basic HTML Parser +author: requarks.io +input: html +output: html +icon: crop_free +props: {} diff --git a/server/modules/renderer/html-core/renderer.js b/server/modules/renderer/html-core/renderer.js new file mode 100644 index 00000000..4ba52ba2 --- /dev/null +++ b/server/modules/renderer/html-core/renderer.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/server/modules/renderer/html-mathjax/definition.yml b/server/modules/renderer/html-mathjax/definition.yml index 0454c0ea..e892cb09 100644 --- a/server/modules/renderer/html-mathjax/definition.yml +++ b/server/modules/renderer/html-mathjax/definition.yml @@ -1,7 +1,8 @@ -key: markdownAbbr -title: Abbreviations -description: Parse abbreviations into abbr tags +key: htmlMathjax +title: Mathjax Processor +description: TeX/MathML Math Equations Parser author: requarks.io -dependsOn: - - markdownCore +icon: functions +enabledDefault: false +dependsOn: htmlCore props: {} diff --git a/server/modules/renderer/html-mediaplayers/definition.yml b/server/modules/renderer/html-mediaplayers/definition.yml index 0454c0ea..85119dc5 100644 --- a/server/modules/renderer/html-mediaplayers/definition.yml +++ b/server/modules/renderer/html-mediaplayers/definition.yml @@ -1,7 +1,8 @@ -key: markdownAbbr -title: Abbreviations -description: Parse abbreviations into abbr tags +key: htmlMedia +title: Media Players +description: Embed players such as Youtube, Vimeo, Soundcloud, etc. author: requarks.io -dependsOn: - - markdownCore +icon: subscriptions +enabledDefault: false +dependsOn: htmlCore props: {} diff --git a/server/modules/renderer/html-security/definition.yml b/server/modules/renderer/html-security/definition.yml index 0454c0ea..7171c9c6 100644 --- a/server/modules/renderer/html-security/definition.yml +++ b/server/modules/renderer/html-security/definition.yml @@ -1,7 +1,8 @@ -key: markdownAbbr -title: Abbreviations -description: Parse abbreviations into abbr tags +key: htmlSecurity +title: Security +description: Filter and strips potentially dangerous content author: requarks.io -dependsOn: - - markdownCore +icon: whatshot +enabledDefault: true +dependsOn: htmlCore props: {} diff --git a/server/modules/renderer/markdown-abbr/definition.yml b/server/modules/renderer/markdown-abbr/definition.yml index 0454c0ea..ceab9fc7 100644 --- a/server/modules/renderer/markdown-abbr/definition.yml +++ b/server/modules/renderer/markdown-abbr/definition.yml @@ -2,6 +2,7 @@ key: markdownAbbr title: Abbreviations description: Parse abbreviations into abbr tags author: requarks.io -dependsOn: - - markdownCore +icon: text_format +enabledDefault: true +dependsOn: markdownCore props: {} diff --git a/server/modules/renderer/markdown-core/definition.yml b/server/modules/renderer/markdown-core/definition.yml index 6c2cb483..87939d42 100644 --- a/server/modules/renderer/markdown-core/definition.yml +++ b/server/modules/renderer/markdown-core/definition.yml @@ -2,7 +2,9 @@ key: markdownCore title: Core description: Basic Markdown Parser author: requarks.io -dependsOn: [] +input: markdown +output: html +icon: crop_free props: linkify: type: Boolean diff --git a/server/modules/renderer/markdown-emoji/definition.yml b/server/modules/renderer/markdown-emoji/definition.yml index 3220194b..a816adb4 100644 --- a/server/modules/renderer/markdown-emoji/definition.yml +++ b/server/modules/renderer/markdown-emoji/definition.yml @@ -2,6 +2,7 @@ key: markdownEmoji title: Emoji description: Convert tags to emojis author: requarks.io -dependsOn: - - markdownCore +icon: tag_faces +enabledDefault: true +dependsOn: markdownCore props: {} diff --git a/server/modules/renderer/markdown-expandtabs/definition.yml b/server/modules/renderer/markdown-expandtabs/definition.yml index 92f7cada..3775f1fa 100644 --- a/server/modules/renderer/markdown-expandtabs/definition.yml +++ b/server/modules/renderer/markdown-expandtabs/definition.yml @@ -2,6 +2,7 @@ key: markdownExpandtabs title: Expand Tabs description: Replace tabs with spaces in code blocks author: requarks.io -dependsOn: - - markdownCore +icon: space_bar +enabledDefault: true +dependsOn: markdownCore props: {} diff --git a/server/modules/renderer/markdown-footnotes/definition.yml b/server/modules/renderer/markdown-footnotes/definition.yml index 44913865..8f1a162c 100644 --- a/server/modules/renderer/markdown-footnotes/definition.yml +++ b/server/modules/renderer/markdown-footnotes/definition.yml @@ -2,6 +2,7 @@ key: markdownFootnotes title: Footnotes description: Parse footnotes references author: requarks.io -dependsOn: - - markdownCore +icon: low_priority +enabledDefault: true +dependsOn: markdownCore props: {} diff --git a/server/modules/renderer/markdown-mathjax/definition.yml b/server/modules/renderer/markdown-mathjax/definition.yml index dd7b1c84..b6e2437a 100644 --- a/server/modules/renderer/markdown-mathjax/definition.yml +++ b/server/modules/renderer/markdown-mathjax/definition.yml @@ -2,6 +2,7 @@ key: markdownMathjax title: Mathjax Pre-Processor description: Pre-parse TeX blocks for Mathjax author: requarks.io -dependsOn: - - markdownCore +icon: functions +enabledDefault: false +dependsOn: markdownCore props: {} diff --git a/server/modules/renderer/markdown-mermaid/definition.yml b/server/modules/renderer/markdown-mermaid/definition.yml index 573e6e96..205505b6 100644 --- a/server/modules/renderer/markdown-mermaid/definition.yml +++ b/server/modules/renderer/markdown-mermaid/definition.yml @@ -2,6 +2,7 @@ key: markdownMermaid title: Mermaid description: Generate flowcharts from Mermaid syntax author: requarks.io -dependsOn: - - markdownCore +icon: merge_type +enabledDefault: false +dependsOn: markdownCore props: {} diff --git a/server/modules/renderer/markdown-plantuml/definition.yml b/server/modules/renderer/markdown-plantuml/definition.yml index 0a24302d..5f36cc29 100644 --- a/server/modules/renderer/markdown-plantuml/definition.yml +++ b/server/modules/renderer/markdown-plantuml/definition.yml @@ -2,6 +2,7 @@ key: markdownPlantuml title: PlantUML description: Generate diagrams from PlantUML syntax author: requarks.io -dependsOn: - - markdownCore +icon: multiline_chart +enabledDefault: false +dependsOn: markdownCore props: {} diff --git a/server/modules/renderer/markdown-tasklists/definition.yml b/server/modules/renderer/markdown-tasklists/definition.yml index 27c5410f..7d8212da 100644 --- a/server/modules/renderer/markdown-tasklists/definition.yml +++ b/server/modules/renderer/markdown-tasklists/definition.yml @@ -2,6 +2,7 @@ key: markdownTasklists title: Task Lists description: Parse task lists to checkboxes author: requarks.io -dependsOn: - - markdownCore +icon: list +enabledDefault: true +dependsOn: markdownCore props: {} diff --git a/server/setup.js b/server/setup.js index d39c0b57..ea056ebd 100644 --- a/server/setup.js +++ b/server/setup.js @@ -24,7 +24,6 @@ module.exports = () => { const yaml = require('js-yaml') const _ = require('lodash') const cfgHelper = require('./helpers/config') - const filesize = require('filesize.js') const crypto = Promise.promisifyAll(require('crypto')) // ---------------------------------------- @@ -76,174 +75,6 @@ module.exports = () => { }) }) - /** - * Perform basic system checks - */ - app.post('/syscheck', (req, res) => { - WIKI.telemetry.enabled = (req.body.telemetry === true) - WIKI.telemetry.sendEvent('setup', 'start') - - Promise.mapSeries([ - () => { - const semver = require('semver') - if (!semver.satisfies(semver.clean(process.version), '>=8.9.0')) { - throw new Error('Node.js version is too old. Minimum is 8.9.0.') - } - return { - title: 'Node.js ' + process.version + ' detected.', - subtitle: ' Minimum is 8.9.0.' - } - }, - () => { - return Promise.try(() => { - require('crypto') - }).catch(err => { - throw new Error('Crypto Node.js module is not available.') - }).return({ - title: 'Node.js Crypto module is available.', - subtitle: 'Crypto module is required.' - }) - }, - () => { - const exec = require('child_process').exec - const semver = require('semver') - return new Promise((resolve, reject) => { - exec('git --version', (err, stdout, stderr) => { - if (err || stdout.length < 3) { - reject(new Error('Git is not installed or not reachable from PATH.')) - } - let gitver = _.head(stdout.match(/[\d]+\.[\d]+(\.[\d]+)?/gi)) - if (!gitver || !semver.satisfies(semver.clean(gitver), '>=2.7.4')) { - reject(new Error('Git version is too old. Minimum is 2.7.4.')) - } - resolve({ - title: 'Git ' + gitver + ' detected.', - subtitle: 'Minimum is 2.7.4.' - }) - }) - }) - }, - () => { - const os = require('os') - if (os.totalmem() < 1000 * 1000 * 768) { - throw new Error('Not enough memory. Minimum is 768 MB.') - } - return { - title: filesize(os.totalmem()) + ' of system memory available.', - subtitle: 'Minimum is 768 MB.' - } - }, - () => { - let fs = require('fs') - return Promise.try(() => { - fs.accessSync(path.join(WIKI.ROOTPATH, 'config.yml'), (fs.constants || fs).W_OK) - }).catch(err => { - throw new Error('config.yml file is not writable by Node.js process or was not created properly.') - }).return({ - title: 'config.yml is writable by the setup process.', - subtitle: 'Setup will write to this file.' - }) - } - ], test => test()).then(results => { - res.json({ ok: true, results }) - }).catch(err => { - res.json({ ok: false, error: err.message }) - }) - }) - - /** - * Check the Git connection - */ - app.post('/gitcheck', (req, res) => { - WIKI.telemetry.sendEvent('setup', 'gitcheck') - - const exec = require('execa') - const url = require('url') - - const dataDir = path.resolve(WIKI.ROOTPATH, cfgHelper.parseConfigValue(req.body.pathData)) - const gitDir = path.resolve(WIKI.ROOTPATH, cfgHelper.parseConfigValue(req.body.pathRepo)) - - let gitRemoteUrl = '' - - if (req.body.gitUseRemote === true) { - let urlObj = url.parse(cfgHelper.parseConfigValue(req.body.gitUrl)) - if (req.body.gitAuthType === 'basic') { - urlObj.auth = req.body.gitAuthUser + ':' + req.body.gitAuthPass - } - gitRemoteUrl = url.format(urlObj) - } - - Promise.mapSeries([ - () => { - return fs.ensureDir(dataDir).then(() => 'Data directory path is valid.') - }, - () => { - return fs.ensureDir(gitDir).then(() => 'Git directory path is valid.') - }, - () => { - return exec.stdout('git', ['init'], { cwd: gitDir }).then(result => { - return 'Local git repository has been initialized.' - }) - }, - () => { - if (req.body.gitUseRemote === false) { return false } - return exec.stdout('git', ['config', '--local', 'user.name', 'Wiki'], { cwd: gitDir }).then(result => { - return 'Git Signature Name has been set successfully.' - }) - }, - () => { - if (req.body.gitUseRemote === false) { return false } - return exec.stdout('git', ['config', '--local', 'user.email', req.body.gitServerEmail], { cwd: gitDir }).then(result => { - return 'Git Signature Name has been set successfully.' - }) - }, - () => { - if (req.body.gitUseRemote === false) { return false } - return exec.stdout('git', ['config', '--local', '--bool', 'http.sslVerify', req.body.gitAuthSSL], { cwd: gitDir }).then(result => { - return 'Git SSL Verify flag has been set successfully.' - }) - }, - () => { - if (req.body.gitUseRemote === false) { return false } - if (_.includes(['sshenv', 'sshdb'], req.body.gitAuthType)) { - req.body.gitAuthSSHKey = path.join(dataDir, 'ssh/key.pem') - } - if (_.startsWith(req.body.gitAuthType, 'ssh')) { - return exec.stdout('git', ['config', '--local', 'core.sshCommand', 'ssh -i "' + req.body.gitAuthSSHKey + '" -o StrictHostKeyChecking=no'], { cwd: gitDir }).then(result => { - return 'Git SSH Private Key path has been set successfully.' - }) - } else { - return false - } - }, - () => { - if (req.body.gitUseRemote === false) { return false } - return exec.stdout('git', ['remote', 'rm', 'origin'], { cwd: gitDir }).catch(err => { - if (_.includes(err.message, 'No such remote') || _.includes(err.message, 'Could not remove')) { - return true - } else { - throw err - } - }).then(() => { - return exec.stdout('git', ['remote', 'add', 'origin', gitRemoteUrl], { cwd: gitDir }).then(result => { - return 'Git Remote was added successfully.' - }) - }) - }, - () => { - if (req.body.gitUseRemote === false) { return false } - return exec.stdout('git', ['pull', 'origin', req.body.gitBranch], { cwd: gitDir }).then(result => { - return 'Git Pull operation successful.' - }) - } - ], step => { return step() }).then(results => { - return res.json({ ok: true, results: _.without(results, false) }) - }).catch(err => { - let errMsg = (err.stderr) ? err.stderr.replace(/(error:|warning:|fatal:)/gi, '').replace(/ \s+/g, ' ') : err.message - res.json({ ok: false, error: errMsg }) - }) - }) - /** * Finalize */ @@ -263,13 +94,6 @@ module.exports = () => { let confRaw = await fs.readFileAsync(path.join(WIKI.ROOTPATH, 'config.yml'), 'utf8') let conf = yaml.safeLoad(confRaw) - conf.port = req.body.port - conf.paths.data = req.body.pathData - conf.paths.content = req.body.pathContent - - confRaw = yaml.safeDump(conf) - await fs.writeFileAsync(path.join(WIKI.ROOTPATH, 'config.yml'), confRaw) - // Create directory structure await fs.ensureDir(conf.paths.data) await fs.ensureDir(path.join(conf.paths.data, 'cache')) @@ -283,16 +107,13 @@ module.exports = () => { _.set(WIKI.config, 'lang.autoUpdate', true) _.set(WIKI.config, 'lang.namespacing', false) _.set(WIKI.config, 'lang.namespaces', []) - _.set(WIKI.config, 'paths.content', req.body.pathContent) - _.set(WIKI.config, 'paths.data', req.body.pathData) - _.set(WIKI.config, 'port', req.body.port) - _.set(WIKI.config, 'public', req.body.public === 'true') + _.set(WIKI.config, 'public', false) _.set(WIKI.config, 'sessionSecret', (await crypto.randomBytesAsync(32)).toString('hex')) _.set(WIKI.config, 'telemetry.isEnabled', req.body.telemetry === 'true') _.set(WIKI.config, 'telemetry.clientId', WIKI.telemetry.cid) _.set(WIKI.config, 'theming.theme', 'default') _.set(WIKI.config, 'theming.darkMode', false) - _.set(WIKI.config, 'title', req.body.title) + _.set(WIKI.config, 'title', 'Wiki.js') // Save config to DB WIKI.logger.info('Persisting config to DB...') @@ -325,6 +146,9 @@ module.exports = () => { await WIKI.models.editors.refreshEditorsFromDisk() await WIKI.models.editors.query().patch({ isEnabled: true }).where('key', 'markdown') + // Load renderers + await WIKI.models.renderers.refreshRenderersFromDisk() + // Load storage targets await WIKI.models.storage.refreshTargetsFromDisk() @@ -367,7 +191,7 @@ module.exports = () => { WIKI.logger.info('Setup is complete!') res.json({ ok: true, - redirectPath: WIKI.config.site.path, + redirectPath: '/', redirectPort: WIKI.config.port }).end()