mirror of https://github.com/Requarks/wiki.git
24 changed files with 1076 additions and 346 deletions
Split View
Diff Options
-
42client/components/admin.vue
-
8client/components/admin/admin-dashboard.vue
-
70client/components/admin/admin-general.vue
-
1client/components/admin/admin-rendering.vue
-
173client/components/admin/admin-ssl.vue
-
84client/components/common/nav-header.vue
-
14client/components/editor/editor-modal-media.vue
-
6client/graph/admin/site/site-mutation-save-config.gql
-
3client/graph/admin/site/site-query-config.gql
-
2client/scss/app.scss
-
4client/scss/components/katex.scss
-
1client/static/svg/icon-validation.svg
-
1client/store/site.js
-
8dev/templates/master.pug
-
72package.json
-
1server/app/data.yml
-
1server/core/config.js
-
9server/graph/resolvers/site.js
-
6server/graph/schemas/site.graphql
-
4server/master.js
-
54server/modules/rendering/html-security/renderer.js
-
20server/modules/rendering/markdown-katex/definition.yml
-
181server/modules/rendering/markdown-katex/renderer.js
-
657yarn.lock
@ -0,0 +1,173 @@ |
|||
<template lang='pug'> |
|||
v-container(fluid, grid-list-lg) |
|||
v-layout(row wrap) |
|||
v-flex(xs12) |
|||
.admin-header |
|||
img.animated.fadeInUp(src='/svg/icon-validation.svg', alt='SSL', style='width: 80px;') |
|||
.admin-header-title |
|||
.headline.primary--text.animated.fadeInLeft {{ $t('admin:ssl.title') }} |
|||
.subtitle-1.grey--text.animated.fadeInLeft {{ $t('admin:ssl.subtitle') }} |
|||
v-spacer |
|||
v-btn.animated.fadeInDown(color='success', depressed, @click='save', large) |
|||
v-icon(left) mdi-check |
|||
span {{$t('common:actions.apply')}} |
|||
v-form.pt-3 |
|||
v-layout(row wrap) |
|||
v-flex(lg6 xs12) |
|||
v-form |
|||
v-card.animated.fadeInUp |
|||
v-toolbar(color='primary', dark, dense, flat) |
|||
v-toolbar-title.subtitle-1 {{ $t('admin:ssl.provider') }} |
|||
v-card-text |
|||
v-select( |
|||
:items='providers' |
|||
outlined |
|||
:label='$t(`admin:ssl.provider`)' |
|||
required |
|||
:counter='255' |
|||
v-model='config.provider' |
|||
prepend-icon='mdi-handshake' |
|||
:hint='$t(`admin:ssl.providerHint`)' |
|||
persistent-hint |
|||
) |
|||
v-text-field.mt-3( |
|||
outlined |
|||
:label='$t(`admin:ssl.domain`)' |
|||
required |
|||
:counter='255' |
|||
v-model='config.domain' |
|||
prepend-icon='mdi-earth' |
|||
:hint='$t(`admin:ssl.domainHint`)' |
|||
persistent-hint |
|||
:disabled='config.provider === ``' |
|||
) |
|||
|
|||
v-card.animated.fadeInUp.wait-p2s.mt-3(v-if='config.provider !== ``') |
|||
v-toolbar(color='primary', dark, dense, flat) |
|||
v-toolbar-title.subtitle-1 {{$t('admin:ssl.providerOptions')}} |
|||
v-card-text --- |
|||
|
|||
v-flex(lg6 xs12) |
|||
v-card.animated.fadeInUp.wait-p2s |
|||
v-toolbar(color='primary', dark, dense, flat) |
|||
v-toolbar-title.subtitle-1 {{ $t('admin:ssl.ports') }} |
|||
v-card-text |
|||
v-row |
|||
v-col(cols='6') |
|||
v-text-field( |
|||
outlined |
|||
:label='$t(`admin:ssl.httpPort`)' |
|||
v-model='config.httpPort' |
|||
prepend-icon='mdi-lock-open-variant-outline' |
|||
:hint='$t(`admin:ssl.httpPortHint`)' |
|||
persistent-hint |
|||
) |
|||
v-col(cols='6') |
|||
v-checkbox( |
|||
:label='$t(`admin:ssl.httpPortRedirect`)' |
|||
v-model='config.httpRedirect' |
|||
:hint='$t(`admin:ssl.httpPortRedirectHint`)' |
|||
:disabled='config.provider === ``' |
|||
persistent-hint |
|||
color='primary' |
|||
) |
|||
v-col(cols='6') |
|||
v-text-field( |
|||
outlined |
|||
:label='$t(`admin:ssl.httpsPort`)' |
|||
v-model='config.httpsPort' |
|||
prepend-icon='mdi-lock' |
|||
:hint='$t(`admin:ssl.httpsPortHint`)' |
|||
persistent-hint |
|||
:disabled='config.provider === ``' |
|||
) |
|||
v-card-text.grey(:class='$vuetify.theme.dark ? `darken-4-l5` : `lighten-4`') |
|||
.caption {{$t(`admin:ssl.writableConfigFileWarning`)}} |
|||
|
|||
</template> |
|||
|
|||
<script> |
|||
import _ from 'lodash' |
|||
import siteConfigQuery from 'gql/admin/site/site-query-config.gql' |
|||
import siteUpdateConfigMutation from 'gql/admin/site/site-mutation-save-config.gql' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
config: { |
|||
provider: '', |
|||
domain: '', |
|||
httpPort: 3000, |
|||
httpPortRedirect: true, |
|||
httpsPort: 443 |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
providers () { |
|||
return [ |
|||
{ text: this.$t('admin:ssl.providerDisabled'), value: '' }, |
|||
{ text: this.$t('admin:ssl.providerLetsEncrypt'), value: 'letsencrypt' }, |
|||
{ text: this.$t('admin:ssl.providerCustomCertificate'), value: 'custom' } |
|||
] |
|||
} |
|||
}, |
|||
methods: { |
|||
async save () { |
|||
try { |
|||
await this.$apollo.mutate({ |
|||
mutation: siteUpdateConfigMutation, |
|||
variables: { |
|||
host: _.get(this.config, 'host', ''), |
|||
title: _.get(this.config, 'title', ''), |
|||
description: _.get(this.config, 'description', ''), |
|||
robots: _.get(this.config, 'robots', []), |
|||
analyticsService: _.get(this.config, 'analyticsService', ''), |
|||
analyticsId: _.get(this.config, 'analyticsId', ''), |
|||
company: _.get(this.config, 'company', ''), |
|||
hasLogo: _.get(this.config, 'hasLogo', false), |
|||
logoIsSquare: _.get(this.config, 'logoIsSquare', false), |
|||
featurePageRatings: _.get(this.config, 'featurePageRatings', false), |
|||
featurePageComments: _.get(this.config, 'featurePageComments', false), |
|||
featurePersonalWikis: _.get(this.config, 'featurePersonalWikis', false), |
|||
securityIframe: _.get(this.config, 'securityIframe', false), |
|||
securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false), |
|||
securityTrustProxy: _.get(this.config, 'securityTrustProxy', false), |
|||
securitySRI: _.get(this.config, 'securitySRI', false), |
|||
securityHSTS: _.get(this.config, 'securityHSTS', false), |
|||
securityHSTSDuration: _.get(this.config, 'securityHSTSDuration', 0), |
|||
securityCSP: _.get(this.config, 'securityCSP', false), |
|||
securityCSPDirectives: _.get(this.config, 'securityCSPDirectives', '') |
|||
}, |
|||
watchLoading (isLoading) { |
|||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-update') |
|||
} |
|||
}) |
|||
this.$store.commit('showNotification', { |
|||
style: 'success', |
|||
message: 'Configuration saved successfully.', |
|||
icon: 'check' |
|||
}) |
|||
this.siteTitle = this.config.title |
|||
this.company = this.config.company |
|||
} catch (err) { |
|||
this.$store.commit('pushGraphError', err) |
|||
} |
|||
} |
|||
} |
|||
// apollo: { |
|||
// config: { |
|||
// query: siteConfigQuery, |
|||
// fetchPolicy: 'network-only', |
|||
// update: (data) => _.cloneDeep(data.site.config), |
|||
// watchLoading (isLoading) { |
|||
// this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-refresh') |
|||
// } |
|||
// } |
|||
// } |
|||
} |
|||
</script> |
|||
|
|||
<style lang='scss'> |
|||
|
|||
</style> |
@ -0,0 +1,4 @@ |
|||
.v-application .katex .accent { |
|||
background-color: inherit !important; |
|||
border-color: inherit !important; |
|||
} |
@ -0,0 +1 @@ |
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="96px" height="96px"><linearGradient id="K7gJwCzSKQrggg~9x8k6Ya" x1="29" x2="29" y1="5.5" y2="54.82" gradientUnits="userSpaceOnUse" spreadMethod="reflect"><stop offset="0" stop-color="#1a6dff"/><stop offset="1" stop-color="#c822ff"/></linearGradient><path fill="url(#K7gJwCzSKQrggg~9x8k6Ya)" d="M12,49V29c0-1.654,1.346-3,3-3h28c1.654,0,3,1.346,3,3v12h2V29c0-2.757-2.243-5-5-5h-3v-1 c0-1.654-1.346-3-3-3v-6c0-4.411-3.589-8-8-8s-8,3.589-8,8v6c-1.654,0-3,1.346-3,3v1h-3c-2.757,0-5,2.243-5,5v20 c0,2.757,2.243,5,5,5h23v-2H15C13.346,52,12,50.654,12,49z M38,23v1h-4v-1c0-0.552,0.448-1,1-1h2C37.552,22,38,22.448,38,23z M23,14 c0-3.309,2.691-6,6-6s6,2.691,6,6v6c-1.654,0-3,1.346-3,3v1h-6v-1c0-1.654-1.346-3-3-3V14z M20,23c0-0.552,0.448-1,1-1h2 c0.552,0,1,0.448,1,1v1h-4V23z"/><linearGradient id="K7gJwCzSKQrggg~9x8k6Yb" x1="45.001" x2="45.001" y1="5.5" y2="54.82" gradientUnits="userSpaceOnUse" spreadMethod="reflect"><stop offset="0" stop-color="#1a6dff"/><stop offset="1" stop-color="#c822ff"/></linearGradient><path fill="url(#K7gJwCzSKQrggg~9x8k6Yb)" d="M48.788 45.189L43.984 49.993 41.191 47.282 39.799 48.718 44.006 52.8 50.202 46.604z"/><linearGradient id="K7gJwCzSKQrggg~9x8k6Yc" x1="26" x2="26" y1="5.5" y2="54.82" gradientUnits="userSpaceOnUse" spreadMethod="reflect"><stop offset="0" stop-color="#1a6dff"/><stop offset="1" stop-color="#c822ff"/></linearGradient><path fill="url(#K7gJwCzSKQrggg~9x8k6Yc)" d="M15 48H37V50H15z"/><linearGradient id="K7gJwCzSKQrggg~9x8k6Yd" x1="19" x2="19" y1="5.5" y2="54.82" gradientUnits="userSpaceOnUse" spreadMethod="reflect"><stop offset="0" stop-color="#1a6dff"/><stop offset="1" stop-color="#c822ff"/></linearGradient><path fill="url(#K7gJwCzSKQrggg~9x8k6Yd)" d="M23,39c0-2.206-1.794-4-4-4s-4,1.794-4,4s1.794,4,4,4S23,41.206,23,39z M17,39 c0-1.103,0.897-2,2-2s2,0.897,2,2s-0.897,2-2,2S17,40.103,17,39z"/><linearGradient id="K7gJwCzSKQrggg~9x8k6Ye" x1="29" x2="29" y1="5.5" y2="54.82" gradientUnits="userSpaceOnUse" spreadMethod="reflect"><stop offset="0" stop-color="#1a6dff"/><stop offset="1" stop-color="#c822ff"/></linearGradient><path fill="url(#K7gJwCzSKQrggg~9x8k6Ye)" d="M29,35c-2.206,0-4,1.794-4,4s1.794,4,4,4s4-1.794,4-4S31.206,35,29,35z M29,41 c-1.103,0-2-0.897-2-2s0.897-2,2-2s2,0.897,2,2S30.103,41,29,41z"/><linearGradient id="K7gJwCzSKQrggg~9x8k6Yf" x1="39" x2="39" y1="5.5" y2="54.82" gradientUnits="userSpaceOnUse" spreadMethod="reflect"><stop offset="0" stop-color="#1a6dff"/><stop offset="1" stop-color="#c822ff"/></linearGradient><path fill="url(#K7gJwCzSKQrggg~9x8k6Yf)" d="M43,39c0-2.206-1.794-4-4-4s-4,1.794-4,4s1.794,4,4,4S43,41.206,43,39z M39,41 c-1.103,0-2-0.897-2-2s0.897-2,2-2s2,0.897,2,2S40.103,41,39,41z"/><linearGradient id="K7gJwCzSKQrggg~9x8k6Yg" x1="45" x2="45" y1="39.667" y2="58.252" gradientUnits="userSpaceOnUse" spreadMethod="reflect"><stop offset="0" stop-color="#6dc7ff"/><stop offset="1" stop-color="#e6abff"/></linearGradient><path fill="url(#K7gJwCzSKQrggg~9x8k6Yg)" d="M45,58c-4.963,0-9-4.037-9-9s4.037-9,9-9s9,4.037,9,9S49.963,58,45,58z M45,42 c-3.859,0-7,3.141-7,7s3.141,7,7,7s7-3.141,7-7S48.859,42,45,42z"/><linearGradient id="K7gJwCzSKQrggg~9x8k6Yh" x1="22" x2="22" y1="21.583" y2="24.172" gradientUnits="userSpaceOnUse" spreadMethod="reflect"><stop offset="0" stop-color="#6dc7ff"/><stop offset="1" stop-color="#e6abff"/></linearGradient><path fill="url(#K7gJwCzSKQrggg~9x8k6Yh)" d="M24,24h-4v-1c0-0.552,0.448-1,1-1h2c0.552,0,1,0.448,1,1V24z"/><linearGradient id="K7gJwCzSKQrggg~9x8k6Yi" x1="36" x2="36" y1="21.583" y2="24.172" gradientUnits="userSpaceOnUse" spreadMethod="reflect"><stop offset="0" stop-color="#6dc7ff"/><stop offset="1" stop-color="#e6abff"/></linearGradient><path fill="url(#K7gJwCzSKQrggg~9x8k6Yi)" d="M38,24h-4v-1c0-0.552,0.448-1,1-1h2c0.552,0,1,0.448,1,1V24z"/></svg> |
@ -0,0 +1,20 @@ |
|||
key: markdownKatex |
|||
title: Katex |
|||
description: LaTeX Math Typesetting Renderer |
|||
author: requarks.io |
|||
icon: mdi-math-integral |
|||
enabledDefault: true |
|||
dependsOn: markdownCore |
|||
props: |
|||
useInline: |
|||
type: Boolean |
|||
default: true |
|||
title: Inline TeX |
|||
hint: Process inline TeX expressions surrounded by $ symbols. |
|||
order: 1 |
|||
useBlocks: |
|||
type: Boolean |
|||
default: true |
|||
title: TeX Blocks |
|||
hint: Process TeX blocks enclosed by $$ symbols. |
|||
order: 2 |
@ -0,0 +1,181 @@ |
|||
const katex = require('katex') |
|||
|
|||
/* global WIKI */ |
|||
|
|||
// ------------------------------------
|
|||
// Markdown - KaTeX Renderer
|
|||
// ------------------------------------
|
|||
//
|
|||
// Includes code from https://github.com/liradb2000/markdown-it-katex
|
|||
|
|||
module.exports = { |
|||
init (mdinst, conf) { |
|||
if (conf.useInline) { |
|||
mdinst.inline.ruler.after('escape', 'katex_inline', katexInline) |
|||
mdinst.renderer.rules.katex_inline = (tokens, idx) => { |
|||
try { |
|||
return katex.renderToString(tokens[idx].content, { |
|||
displayMode: false |
|||
}) |
|||
} catch (err) { |
|||
WIKI.logger.warn(err) |
|||
return tokens[idx].content |
|||
} |
|||
} |
|||
} |
|||
if (conf.useBlocks) { |
|||
mdinst.block.ruler.after('blockquote', 'katex_block', katexBlock, { |
|||
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ] |
|||
}) |
|||
mdinst.renderer.rules.katex_block = (tokens, idx) => { |
|||
try { |
|||
return `<p>` + katex.renderToString(tokens[idx].content, { |
|||
displayMode: true |
|||
}) + `</p>` |
|||
} catch (err) { |
|||
WIKI.logger.warn(err) |
|||
return tokens[idx].content |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Test if potential opening or closing delimieter
|
|||
// Assumes that there is a "$" at state.src[pos]
|
|||
function isValidDelim (state, pos) { |
|||
let prevChar |
|||
let nextChar |
|||
let max = state.posMax |
|||
let canOpen = true |
|||
let canClose = true |
|||
|
|||
prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1 |
|||
nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1 |
|||
|
|||
// Check non-whitespace conditions for opening and closing, and
|
|||
// check that closing delimeter isn't followed by a number
|
|||
if (prevChar === 0x20/* " " */ || prevChar === 0x09/* \t */ || |
|||
(nextChar >= 0x30/* "0" */ && nextChar <= 0x39/* "9" */)) { |
|||
canClose = false |
|||
} |
|||
if (nextChar === 0x20/* " " */ || nextChar === 0x09/* \t */) { |
|||
canOpen = false |
|||
} |
|||
|
|||
return { |
|||
canOpen: canOpen, |
|||
canClose: canClose |
|||
} |
|||
} |
|||
|
|||
function katexInline (state, silent) { |
|||
let start, match, token, res, pos |
|||
|
|||
if (state.src[state.pos] !== '$') { return false } |
|||
|
|||
res = isValidDelim(state, state.pos) |
|||
if (!res.canOpen) { |
|||
if (!silent) { state.pending += '$' } |
|||
state.pos += 1 |
|||
return true |
|||
} |
|||
|
|||
// First check for and bypass all properly escaped delimieters
|
|||
// This loop will assume that the first leading backtick can not
|
|||
// be the first character in state.src, which is known since
|
|||
// we have found an opening delimieter already.
|
|||
start = state.pos + 1 |
|||
match = start |
|||
while ((match = state.src.indexOf('$', match)) !== -1) { |
|||
// Found potential $, look for escapes, pos will point to
|
|||
// first non escape when complete
|
|||
pos = match - 1 |
|||
while (state.src[pos] === '\\') { pos -= 1 } |
|||
|
|||
// Even number of escapes, potential closing delimiter found
|
|||
if (((match - pos) % 2) === 1) { break } |
|||
match += 1 |
|||
} |
|||
|
|||
// No closing delimter found. Consume $ and continue.
|
|||
if (match === -1) { |
|||
if (!silent) { state.pending += '$' } |
|||
state.pos = start |
|||
return true |
|||
} |
|||
|
|||
// Check if we have empty content, ie: $$. Do not parse.
|
|||
if (match - start === 0) { |
|||
if (!silent) { state.pending += '$$' } |
|||
state.pos = start + 1 |
|||
return true |
|||
} |
|||
|
|||
// Check for valid closing delimiter
|
|||
res = isValidDelim(state, match) |
|||
if (!res.canClose) { |
|||
if (!silent) { state.pending += '$' } |
|||
state.pos = start |
|||
return true |
|||
} |
|||
|
|||
if (!silent) { |
|||
token = state.push('katex_inline', 'math', 0) |
|||
token.markup = '$' |
|||
token.content = state.src.slice(start, match) |
|||
} |
|||
|
|||
state.pos = match + 1 |
|||
return true |
|||
} |
|||
|
|||
function katexBlock (state, start, end, silent) { |
|||
let firstLine; let lastLine; let next; let lastPos; let found = false; let token |
|||
let pos = state.bMarks[start] + state.tShift[start] |
|||
let max = state.eMarks[start] |
|||
|
|||
if (pos + 2 > max) { return false } |
|||
if (state.src.slice(pos, pos + 2) !== '$$') { return false } |
|||
|
|||
pos += 2 |
|||
firstLine = state.src.slice(pos, max) |
|||
|
|||
if (silent) { return true } |
|||
if (firstLine.trim().slice(-2) === '$$') { |
|||
// Single line expression
|
|||
firstLine = firstLine.trim().slice(0, -2) |
|||
found = true |
|||
} |
|||
|
|||
for (next = start; !found;) { |
|||
next++ |
|||
|
|||
if (next >= end) { break } |
|||
|
|||
pos = state.bMarks[next] + state.tShift[next] |
|||
max = state.eMarks[next] |
|||
|
|||
if (pos < max && state.tShift[next] < state.blkIndent) { |
|||
// non-empty line with negative indent should stop the list:
|
|||
break |
|||
} |
|||
|
|||
if (state.src.slice(pos, max).trim().slice(-2) === '$$') { |
|||
lastPos = state.src.slice(0, max).lastIndexOf('$$') |
|||
lastLine = state.src.slice(pos, lastPos) |
|||
found = true |
|||
} |
|||
} |
|||
|
|||
state.line = next + 1 |
|||
|
|||
token = state.push('katex_block', 'math', 0) |
|||
token.block = true |
|||
token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '') + |
|||
state.getLines(start + 1, next, state.tShift[start], true) + |
|||
(lastLine && lastLine.trim() ? lastLine : '') |
|||
token.map = [ start, state.line ] |
|||
token.markup = '$$' |
|||
return true |
|||
} |
657
yarn.lock
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save