diff --git a/client/components/admin.vue b/client/components/admin.vue index f6e3722a..1d0762f3 100644 --- a/client/components/admin.vue +++ b/client/components/admin.vue @@ -65,15 +65,6 @@ v-list-item(to='/comments') v-list-item-avatar(size='24', tile): v-icon mdi-comment-text-outline v-list-item-title {{ $t('admin:comments.title') }} - v-list-item(to='/editor', disabled) - v-list-item-avatar(size='24', tile): v-icon(color='grey lighten-2') mdi-playlist-edit - v-list-item-title {{ $t('admin:editor.title') }} - v-list-item(to='/extensions') - v-list-item-avatar(size='24', tile): v-icon mdi-chip - v-list-item-title {{ $t('admin:extensions.title') }} - v-list-item(to='/logging', disabled) - v-list-item-avatar(size='24', tile): v-icon(color='grey lighten-2') mdi-script-text-outline - v-list-item-title {{ $t('admin:logging.title') }} v-list-item(to='/rendering', color='primary') v-list-item-avatar(size='24', tile): v-icon mdi-cogs v-list-item-title {{ $t('admin:rendering.title') }} @@ -104,9 +95,6 @@ v-list-item(to='/utilities', color='primary', v-if='hasPermission(`manage:system`)') v-list-item-avatar(size='24', tile): v-icon mdi-wrench-outline v-list-item-title {{ $t('admin:utilities.title') }} - v-list-item(to='/webhooks', v-if='hasPermission(`manage:system`)', disabled) - v-list-item-avatar(size='24', tile): v-icon(color='grey lighten-2') mdi-webhook - v-list-item-title {{ $t('admin:webhooks.title') }} v-list-group( to='/dev' no-action diff --git a/client/components/editor.vue b/client/components/editor.vue index 15311e9b..7cd577b1 100644 --- a/client/components/editor.vue +++ b/client/components/editor.vue @@ -77,6 +77,7 @@ export default { editorApi: () => import(/* webpackChunkName: "editor-api", webpackMode: "lazy" */ './editor/editor-api.vue'), editorCode: () => import(/* webpackChunkName: "editor-code", webpackMode: "lazy" */ './editor/editor-code.vue'), editorCkeditor: () => import(/* webpackChunkName: "editor-ckeditor", webpackMode: "lazy" */ './editor/editor-ckeditor.vue'), + editorAsciidoc: () => import(/* webpackChunkName: "editor-asciidoc", webpackMode: "lazy" */ './editor/editor-asciidoc.vue'), editorMarkdown: () => import(/* webpackChunkName: "editor-markdown", webpackMode: "lazy" */ './editor/editor-markdown.vue'), editorRedirect: () => import(/* webpackChunkName: "editor-redirect", webpackMode: "lazy" */ './editor/editor-redirect.vue'), editorModalEditorselect: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-editorselect.vue'), diff --git a/client/components/editor/markdown/fold.js b/client/components/editor/common/cmFold.js similarity index 92% rename from client/components/editor/markdown/fold.js rename to client/components/editor/common/cmFold.js index 1bc56ef5..52267303 100644 --- a/client/components/editor/markdown/fold.js +++ b/client/components/editor/common/cmFold.js @@ -7,7 +7,13 @@ const maxDepth = 100 const codeBlockStartMatch = /^`{3}[a-zA-Z0-9]+$/ const codeBlockEndMatch = /^`{3}$/ -CodeMirror.registerHelper('fold', 'markdown', function (cm, start) { +export default { + register(lang) { + CodeMirror.registerHelper('fold', lang, foldHandler) + } +} + +function foldHandler (cm, start) { const firstLine = cm.getLine(start.line) const lastLineNo = cm.lastLine() let end @@ -59,4 +65,4 @@ CodeMirror.registerHelper('fold', 'markdown', function (cm, start) { from: CodeMirror.Pos(start.line, firstLine.length), to: CodeMirror.Pos(end, cm.getLine(end).length) } -}) +} diff --git a/client/components/editor/editor-asciidoc.vue b/client/components/editor/editor-asciidoc.vue new file mode 100644 index 00000000..296b2414 --- /dev/null +++ b/client/components/editor/editor-asciidoc.vue @@ -0,0 +1,707 @@ + + .editor-asciidoc + v-toolbar.editor-asciidoc-toolbar(dense, color='primary', dark, flat, style='overflow-x: hidden;') + template(v-if='isModalShown') + v-spacer + v-btn.animated.fadeInRight(text, @click='closeAllModal') + v-icon(left) mdi-arrow-left-circle + span {{$t('editor:backToEditor')}} + template(v-else) + v-tooltip(bottom, color='primary') + template(v-slot:activator='{ on }') + v-btn.animated.fadeIn(icon, tile, v-on='on', @click='toggleMarkup({ start: `**` })').mx-0 + v-icon mdi-format-bold + span {{$t('editor:markup.bold')}} + v-tooltip(bottom, color='primary') + template(v-slot:activator='{ on }') + v-btn.animated.fadeIn.wait-p1s(icon, tile, v-on='on', @click='toggleMarkup({ start: `__` })').mx-0 + v-icon mdi-format-italic + span {{$t('editor:markup.italic')}} + v-menu(offset-y, open-on-hover) + template(v-slot:activator='{ on }') + v-btn.animated.fadeIn.wait-p3s(icon, tile, v-on='on').mx-0 + v-icon mdi-format-header-pound + v-list.py-0 + template(v-for='(n, idx) in 6') + v-list-item(@click='setHeaderLine(n)', :key='idx') + v-list-item-action + v-icon(:size='24 - (idx - 1) * 2') mdi-format-header-{{n}} + v-list-item-title {{$t('editor:markup.heading', { level: n })}} + v-divider(v-if='idx < 5') + v-tooltip(bottom, color='primary') + template(v-slot:activator='{ on }') + v-btn.animated.fadeIn.wait-p4s(icon, tile, v-on='on', @click='toggleMarkup({ start: `~` })').mx-0 + v-icon mdi-format-subscript + span {{$t('editor:markup.subscript')}} + v-tooltip(bottom, color='primary') + template(v-slot:activator='{ on }') + v-btn.animated.fadeIn.wait-p5s(icon, tile, v-on='on', @click='toggleMarkup({ start: `^` })').mx-0 + v-icon mdi-format-superscript + span {{$t('editor:markup.superscript')}} + v-menu(offset-y, open-on-hover) + template(v-slot:activator='{ on }') + v-btn.animated.fadeIn.wait-p6s(icon, tile, v-on='on').mx-0 + v-icon mdi-alpha-t-box-outline + v-list.py-0 + v-list-item(@click='insertBeforeEachLine({ content: `> `})') + v-list-item-action + v-icon mdi-alpha-t-box-outline + v-list-item-title {{$t('editor:markup.blockquote')}} + v-divider + v-list-item(@click='insertBeforeEachLine({ content: `NOTE: `})') + v-list-item-action + v-icon(color='blue') mdi-alpha-n-box-outline + v-list-item-title {{'Note blockquote'}} + v-divider + v-list-item(@click='insertBeforeEachLine({ content: `TIP: `})') + v-list-item-action + v-icon(color='success') mdi-alpha-t-box-outline + v-list-item-title {{'Tip blockquote'}} + v-divider + v-list-item(@click='insertBeforeEachLine({ content: `WARNING: `})') + v-list-item-action + v-icon(color='warning') mdi-alpha-w-box-outline + v-list-item-title {{$t('editor:markup.blockquoteWarning')}} + v-divider + v-list-item(@click='insertBeforeEachLine({ content: `CAUTION: `})') + v-list-item-action + v-icon(color='purple') mdi-alpha-c-box-outline + v-list-item-title {{'Caution blockquote'}} + v-list-item(@click='insertBeforeEachLine({ content: `IMPORTANT: `})') + v-list-item-action + v-icon(color='error') mdi-alpha-i-box-outline + v-list-item-title {{'Important blockquote'}} + v-divider + template(v-if='$vuetify.breakpoint.mdAndUp') + v-spacer + v-tooltip(bottom, color='primary') + template(v-slot:activator='{ on }') + v-btn.animated.fadeIn.wait-p2s(icon, tile, v-on='on', @click='previewShown = !previewShown').mx-0 + v-icon mdi-book-open-outline + span {{$t('editor:markup.togglePreviewPane')}} + + .editor-asciidoc-main + .editor-asciidoc-sidebar + v-tooltip(right, color='teal') + template(v-slot:activator='{ on }') + v-btn.animated.fadeInLeft(icon, tile, v-on='on', dark, @click='insertLink').mx-0 + v-icon mdi-link-plus + span {{$t('editor:markup.insertLink')}} + v-tooltip(right, color='teal') + template(v-slot:activator='{ on }') + v-btn.mt-3.animated.fadeInLeft.wait-p1s(icon, tile, v-on='on', dark, @click='toggleModal(`editorModalMedia`)').mx-0 + v-icon(:color='activeModal === `editorModalMedia` ? `teal` : ``') mdi-folder-multiple-image + span {{$t('editor:markup.insertAssets')}} + v-tooltip(right, color='teal') + template(v-slot:activator='{ on }') + v-btn.mt-3.animated.fadeInLeft.wait-p5s(icon, tile, v-on='on', dark, @click='toggleModal(`editorModalDrawio`)').mx-0 + v-icon mdi-chart-multiline + span {{$t('editor:markup.insertDiagram')}} + template(v-if='$vuetify.breakpoint.mdAndUp') + v-spacer + v-tooltip(right, color='teal') + template(v-slot:activator='{ on }') + v-btn.mt-3.animated.fadeInLeft.wait-p8s(icon, tile, v-on='on', dark, @click='toggleFullscreen').mx-0 + v-icon mdi-arrow-expand-all + span {{$t('editor:markup.distractionFreeMode')}} + .editor-asciidoc-editor + textarea(ref='cm') + transition(name='editor-asciidoc-preview') + .editor-asciidoc-preview(v-if='previewShown') + .editor-asciidoc-preview-content.contents(ref='editorPreviewContainer') + div( + ref='editorPreview' + v-html='previewHTML' + ) + + v-system-bar.editor-asciidoc-sysbar(dark, status, color='grey darken-3') + .caption.editor-asciidoc-sysbar-locale {{locale.toUpperCase()}} + .caption.px-3 /{{path}} + template(v-if='$vuetify.breakpoint.mdAndUp') + v-spacer + .caption AsciiDoc + v-spacer + .caption Ln {{cursorPos.line + 1}}, Col {{cursorPos.ch + 1}} + page-selector(mode='select', v-model='insertLinkDialog', :open-handler='insertLinkHandler', :path='path', :locale='locale') + + + + + diff --git a/client/components/editor/editor-markdown.vue b/client/components/editor/editor-markdown.vue index 6fc2e581..04b5c6aa 100644 --- a/client/components/editor/editor-markdown.vue +++ b/client/components/editor/editor-markdown.vue @@ -124,44 +124,19 @@ span {{$t('editor:markup.insertAssets')}} v-tooltip(right, color='teal') template(v-slot:activator='{ on }') - v-btn.mt-3.animated.fadeInLeft.wait-p2s(icon, tile, v-on='on', dark, disabled, @click='toggleModal(`editorModalBlocks`)').mx-0 - v-icon(:color='activeModal === `editorModalBlocks` ? `teal` : ``') mdi-view-dashboard-outline - span {{$t('editor:markup.insertBlock')}} - v-tooltip(right, color='teal') - template(v-slot:activator='{ on }') - v-btn.mt-3.animated.fadeInLeft.wait-p3s(icon, tile, v-on='on', dark, disabled).mx-0 - v-icon mdi-code-braces - span {{$t('editor:markup.insertCodeBlock')}} - v-tooltip(right, color='teal') - template(v-slot:activator='{ on }') - v-btn.mt-3.animated.fadeInLeft.wait-p4s(icon, tile, v-on='on', dark, disabled).mx-0 - v-icon mdi-movie - span {{$t('editor:markup.insertVideoAudio')}} - v-tooltip(right, color='teal') - template(v-slot:activator='{ on }') - v-btn.mt-3.animated.fadeInLeft.wait-p5s(icon, tile, v-on='on', dark, @click='toggleModal(`editorModalDrawio`)').mx-0 + v-btn.mt-3.animated.fadeInLeft.wait-p2s(icon, tile, v-on='on', dark, @click='toggleModal(`editorModalDrawio`)').mx-0 v-icon mdi-chart-multiline span {{$t('editor:markup.insertDiagram')}} - v-tooltip(right, color='teal') - template(v-slot:activator='{ on }') - v-btn.mt-3.animated.fadeInLeft.wait-p6s(icon, tile, v-on='on', dark, disabled).mx-0 - v-icon mdi-function-variant - span {{$t('editor:markup.insertMathExpression')}} - v-tooltip(right, color='teal') - template(v-slot:activator='{ on }') - v-btn.mt-3.animated.fadeInLeft.wait-p7s(icon, tile, v-on='on', dark, disabled).mx-0 - v-icon mdi-table-plus - span {{$t('editor:markup.tableHelper')}} template(v-if='$vuetify.breakpoint.mdAndUp') v-spacer v-tooltip(right, color='teal') template(v-slot:activator='{ on }') - v-btn.mt-3.animated.fadeInLeft.wait-p8s(icon, tile, v-on='on', dark, @click='toggleFullscreen').mx-0 + v-btn.mt-3.animated.fadeInLeft.wait-p3s(icon, tile, v-on='on', dark, @click='toggleFullscreen').mx-0 v-icon mdi-arrow-expand-all span {{$t('editor:markup.distractionFreeMode')}} v-tooltip(right, color='teal') template(v-slot:activator='{ on }') - v-btn.mt-3.animated.fadeInLeft.wait-p9s(icon, tile, v-on='on', dark, @click='toggleHelp').mx-0 + v-btn.mt-3.animated.fadeInLeft.wait-p4s(icon, tile, v-on='on', dark, @click='toggleHelp').mx-0 v-icon(:color='helpShown ? `teal` : ``') mdi-help-circle span {{$t('editor:markup.markdownFormattingHelp')}} .editor-markdown-editor @@ -220,7 +195,6 @@ import 'codemirror/addon/hint/show-hint.js' import 'codemirror/addon/fold/foldcode.js' import 'codemirror/addon/fold/foldgutter.js' import 'codemirror/addon/fold/foldgutter.css' -import './markdown/fold' // Markdown-it import MarkdownIt from 'markdown-it' @@ -251,6 +225,7 @@ import mermaid from 'mermaid' // Helpers import katexHelper from './common/katex' import tabsetHelper from './markdown/tabset' +import cmFold from './common/cmFold' // ======================================== // INIT @@ -337,6 +312,7 @@ md.renderer.rules.paragraph_open = injectLineNumbers md.renderer.rules.heading_open = injectLineNumbers md.renderer.rules.blockquote_open = injectLineNumbers +cmFold.register('markdown') // ======================================== // PLANTUML // ======================================== diff --git a/client/components/editor/editor-modal-editorselect.vue b/client/components/editor/editor-modal-editorselect.vue index b5ce35f5..df2adf96 100644 --- a/client/components/editor/editor-modal-editorselect.vue +++ b/client/components/editor/editor-modal-editorselect.vue @@ -6,57 +6,7 @@ .subtitle-1.white--text {{$t('editor:select.title')}} v-container(grid-list-lg, fluid) v-layout(row, wrap, justify-center) - v-flex(xs4) - v-hover - template(v-slot:default='{ hover }') - v-card.radius-7.primary.animated.fadeInUp( - hover - light - ripple - ) - v-card-text.text-center(@click='') - img(src='/_assets/svg/editor-icon-api.svg', alt='API', style='width: 36px; opacity: .5;') - .body-2.blue--text.mt-2.text--lighten-2 API Docs - .caption.blue--text.text--lighten-1 REST / GraphQL - v-fade-transition - v-overlay( - v-if='hover' - absolute - color='primary' - opacity='.8' - ) - .body-2.mt-7 Coming Soon - v-flex(xs4) - v-hover - template(v-slot:default='{ hover }') - v-card.radius-7.primary.animated.fadeInUp.wait-p1s( - hover - light - ripple - ) - v-card-text.text-center(@click='') - img(src='/_assets/svg/editor-icon-wikitext.svg', alt='WikiText', style='width: 36px; opacity: .5;') - .body-2.blue--text.mt-2.text--lighten-2 Blog - .caption.blue--text.text--lighten-1 Timeline of Posts - v-fade-transition - v-overlay( - v-if='hover' - absolute - color='primary' - opacity='.8' - ) - .body-2.mt-7 Coming Soon - v-flex(xs4) - v-card.radius-7.animated.fadeInUp.wait-p2s( - hover - light - ripple - ) - v-card-text.text-center(@click='selectEditor("code")') - img(src='/_assets/svg/editor-icon-code.svg', alt='Code', style='width: 36px;') - .body-2.primary--text.mt-2 Code - .caption.grey--text Raw HTML - v-flex(xs4) + v-flex(xs6) v-card.radius-7.animated.fadeInUp.wait-p1s( hover light @@ -66,28 +16,8 @@ img(src='/_assets/svg/editor-icon-markdown.svg', alt='Markdown', style='width: 36px;') .body-2.primary--text.mt-2 Markdown .caption.grey--text Plain Text Formatting - v-flex(xs4) - v-hover - template(v-slot:default='{ hover }') - v-card.radius-7.primary.animated.fadeInUp.wait-p2s( - hover - light - ripple - ) - v-card-text.text-center(@click='') - img(src='/_assets/svg/editor-icon-tabular.svg', alt='Tabular', style='width: 36px; opacity: .5;') - .body-2.blue--text.mt-2.text--lighten-2 Tabular - .caption.blue--text.text--lighten-1 Excel-like - v-fade-transition - v-overlay( - v-if='hover' - absolute - color='primary' - opacity='.8' - ) - .body-2.mt-7 Coming Soon - v-flex(xs4) - v-card.radius-7.animated.fadeInUp.wait-p3s( + v-flex(xs6) + v-card.radius-7.animated.fadeInUp.wait-p2s( hover light ripple @@ -96,85 +26,36 @@ img(src='/_assets/svg/editor-icon-ckeditor.svg', alt='Visual Editor', style='width: 36px;') .body-2.mt-2.primary--text Visual Editor .caption.grey--text Rich-text WYSIWYG - //- .caption.blue--text.text--lighten-2 {{$t('editor:select.cannotChange')}} - - v-card.radius-7.mt-2(color='teal darken-3', dark) - v-card-text.text-center.py-4 - .subtitle-1.white--text {{$t('editor:select.customView')}} - v-container(grid-list-lg, fluid) - v-layout(row, wrap, justify-center) v-flex(xs4) - v-hover - template(v-slot:default='{ hover }') - v-card.radius-7.animated.fadeInUp( - hover - light - ripple - ) - v-card-text.text-center(@click='fromTemplate') - img(src='/_assets/svg/icon-cube.svg', alt='From Template', style='width: 42px; opacity: .5;') - .body-2.mt-1.teal--text From Template - .caption.grey--text Use an existing page... + v-card.radius-7.animated.fadeInUp.wait-p3s( + hover + light + ripple + ) + v-card-text.text-center(@click='selectEditor("asciidoc")') + img(src='/_assets/svg/editor-icon-asciidoc.svg', alt='AsciiDoc', style='width: 36px;') + .body-2.primary--text.mt-2 AsciiDoc + .caption.grey--text Plain Text Formatting v-flex(xs4) - v-hover - template(v-slot:default='{ hover }') - v-card.radius-7.teal.animated.fadeInUp.wait-p1s( - hover - light - ripple - ) - //- v-card-text.text-center(@click='selectEditor("redirect")') - v-card-text.text-center(@click='') - img(src='/_assets/svg/icon-route.svg', alt='Redirection', style='width: 42px; opacity: .5;') - .body-2.mt-1.teal--text.text--lighten-2 Redirection - .caption.teal--text.text--lighten-1 Redirect the user to... + v-card.radius-7.animated.fadeInUp.wait-p4s( + hover + light + ripple + ) + v-card-text.text-center(@click='selectEditor("code")') + img(src='/_assets/svg/editor-icon-code.svg', alt='Code', style='width: 36px;') + .body-2.primary--text.mt-2 Code + .caption.grey--text Raw HTML v-flex(xs4) - v-hover - template(v-slot:default='{ hover }') - v-card.radius-7.teal.animated.fadeInUp.wait-p2s( - hover - light - ripple - ) - v-card-text.text-center(@click='') - img(src='/_assets/svg/icon-sewing-patch.svg', alt='Code', style='width: 42px; opacity: .5;') - .body-2.mt-1.teal--text.text--lighten-2 Embed - .caption.teal--text.text--lighten-1 Include external pages - v-fade-transition - v-overlay( - v-if='hover' - absolute - color='teal' - opacity='.8' - ) - .body-2.mt-7 Coming Soon - v-hover - template(v-slot:default='{ hover }') - v-card.radius-7.mt-2(color='indigo darken-3', dark) - v-toolbar(dense, flat, color='light-green darken-3') - v-spacer - .caption.mr-1 or convert from - v-btn.mx-1.animated.fadeInUp(depressed, color='light-green darken-2', @click='', disabled) - v-icon(left) mdi-alpha-a-circle - .body-2.text-none AsciiDoc - v-btn.mx-1.animated.fadeInUp.wait-p1s(depressed, color='light-green darken-2', @click='', disabled) - v-icon(left) mdi-alpha-c-circle - .body-2.text-none CREOLE - v-btn.mx-1.animated.fadeInUp.wait-p2s(depressed, color='light-green darken-2', @click='', disabled) - v-icon(left) mdi-alpha-t-circle - .body-2.text-none Textile - v-btn.mx-1.animated.fadeInUp.wait-p3s(depressed, color='light-green darken-2', @click='', disabled) - v-icon(left) mdi-alpha-w-circle - .body-2.text-none WikiText - v-spacer - v-fade-transition - v-overlay( - v-if='hover' - absolute - color='light-green darken-3' - opacity='.8' - ) - .body-2 Coming Soon + v-card.radius-7.animated.fadeInUp.wait-p5s( + hover + light + ripple + ) + v-card-text.text-center(@click='fromTemplate') + img(src='/_assets/svg/icon-cube.svg', alt='From Template', style='width: 42px; opacity: .5;') + .body-2.mt-1.teal--text From Template + .caption.grey--text Use an existing page... page-selector(mode='select', v-model='templateDialogIsShown', :open-handler='fromTemplateHandle', :path='path', :locale='locale', must-exist) diff --git a/client/static/svg/editor-icon-asciidoc.svg b/client/static/svg/editor-icon-asciidoc.svg new file mode 100644 index 00000000..d0c954b1 --- /dev/null +++ b/client/static/svg/editor-icon-asciidoc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/themes/default/scss/app.scss b/client/themes/default/scss/app.scss index 8bba5e72..5e7f0a95 100644 --- a/client/themes/default/scss/app.scss +++ b/client/themes/default/scss/app.scss @@ -356,6 +356,148 @@ } } + // --------------------------------- + // ASCIIDOC SPECIFIC + // --------------------------------- + + .admonitionblock { + margin: 1rem 0; + position: relative; + + table { + border: none; + background-color: transparent; + width: 100%; + } + + td.icon { + border-bottom-left-radius: 7px; + border-top-left-radius: 7px; + text-align: center; + width: 56px; + + &::before { + display: inline-block; + font: normal normal normal 24px/1 "Material Design Icons", sans-serif !important; + } + } + + td.content { + border-bottom-right-radius: 7px; + border-top-right-radius: 7px; + } + + &.note { + td.icon { + background-color: mc('blue', '300'); + color: mc('blue', '50'); + &::before { + content: "\F02FC"; + } + } + td.content { + color: darken(mc('blue', '900'), 10%); + background-color: mc('blue', '50'); + + @at-root .theme--dark & { + background-color: mc('blue', '900'); + color: mc('blue', '50'); + } + } + } + &.tip { + td.icon { + background-color: mc('green', '300'); + color: mc('green', '50'); + &::before { + content: "\F0335"; + } + } + td.content { + color: darken(mc('green', '900'), 10%); + background-color: mc('green', '50'); + + @at-root .theme--dark & { + background-color: mc('green', '900'); + color: mc('green', '50'); + } + } + } + &.warning { + background-color: transparent !important; + + td.icon { + background-color: mc('orange', '300'); + color: #FFF; + &::before { + content: "\F0026"; + } + } + td.content { + color: darken(mc('orange', '900'), 10%); + background-color: mc('orange', '50'); + + @at-root .theme--dark & { + background-color: darken(mc('orange', '900'), 5%); + color: mc('orange', '100'); + } + } + } + &.caution { + td.icon { + background-color: mc('purple', '300'); + color: mc('purple', '50'); + &::before { + content: "\f0238"; + } + } + td.content { + color: darken(mc('purple', '900'), 10%); + background-color: mc('purple', '50'); + + @at-root .theme--dark & { + background-color: mc('purple', '900'); + color: mc('purple', '100'); + } + } + } + &.important { + td.icon { + background-color: mc('red', '300'); + color: mc('red', '50'); + &::before { + content: "\F0159"; + } + } + td.content { + color: darken(mc('red', '900'), 10%); + background-color: mc('red', '50'); + + @at-root .theme--dark & { + background-color: mc('red', '900'); + color: mc('red', '100'); + } + } + } + } + + .exampleblock { + > .title { + font-style: italic; + font-size: 1rem !important; + color: #7a2717; + + @at-root .theme--dark & { + color: mc('brown', '300'); + } + } + > .content { + border: 1px solid mc('grey', '200'); + border-radius: 7px; + margin-bottom: 12px; + padding: 16px; + } + } // --------------------------------- // LISTS // --------------------------------- @@ -547,6 +689,23 @@ } } + dl { + dt { + margin-top: 0.3em; + margin-bottom: 0.3em; + font-weight: bold; + } + + dd { + margin-left: 1.125em; + margin-bottom: 0.75em; + + > p { + padding: 0; + } + } + } + // --------------------------------- // CODE // --------------------------------- diff --git a/package.json b/package.json index d6edb3fb..32d4ecd6 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "node": ">=10.12" }, "dependencies": { + "asciidoctor": "2.2.6", "@azure/storage-blob": "12.12.0", "@exlinc/keycloak-passport": "1.0.2", "@joplin/turndown-plugin-gfm": "1.0.45", @@ -231,6 +232,7 @@ "clean-webpack-plugin": "3.0.0", "clipboard": "2.0.11", "codemirror": "5.58.2", + "codemirror-asciidoc": "1.0.4", "copy-webpack-plugin": "6.2.1", "core-js": "3.6.5", "css-loader": "4.3.0", diff --git a/server/helpers/page.js b/server/helpers/page.js index 8049edc1..8bea0aa3 100644 --- a/server/helpers/page.js +++ b/server/helpers/page.js @@ -10,6 +10,7 @@ const unsafeCharsRegex = /[\x00-\x1f\x80-\x9f\\"|<>:*?]/ const contentToExt = { markdown: 'md', + asciidoc: 'adoc', html: 'html' } const extToContent = _.invert(contentToExt) diff --git a/server/models/editors.js b/server/models/editors.js index 8a05ba98..f4d091af 100644 --- a/server/models/editors.js +++ b/server/models/editors.js @@ -101,6 +101,8 @@ module.exports = class Editor extends Model { return 'markdown' case 'html': return 'ckeditor' + case 'asciidoc': + return 'asciidoc' default: return 'code' } diff --git a/server/modules/editor/asciidoc/definition.yml b/server/modules/editor/asciidoc/definition.yml new file mode 100644 index 00000000..378b60da --- /dev/null +++ b/server/modules/editor/asciidoc/definition.yml @@ -0,0 +1,6 @@ +key: asciidoc +title: Asciidoc +description: Basic Asciidoc editor +contentType: asciidoc +author: dzruyk +props: {} diff --git a/server/modules/rendering/asciidoc-core/definition.yml b/server/modules/rendering/asciidoc-core/definition.yml new file mode 100644 index 00000000..8a11eb14 --- /dev/null +++ b/server/modules/rendering/asciidoc-core/definition.yml @@ -0,0 +1,20 @@ +key: asciidocCore +title: Core +description: Basic Asciidoc Parser +author: dzruyk (Based on asciidoctor.js renderer) +input: asciidoc +output: html +icon: mdi-sitemap +enabledDefault: true +props: + safeMode: + type: String + default: server + title: Safe Mode + hint: Sets the safe mode to use when parsing content to HTML. + order: 1 + enum: + - unsafe + - safe + - server + - secure diff --git a/server/modules/rendering/asciidoc-core/renderer.js b/server/modules/rendering/asciidoc-core/renderer.js new file mode 100644 index 00000000..e37217d8 --- /dev/null +++ b/server/modules/rendering/asciidoc-core/renderer.js @@ -0,0 +1,26 @@ +const asciidoctor = require('asciidoctor')() +const cheerio = require('cheerio') + +module.exports = { + async render() { + const html = asciidoctor.convert(this.input, { + standalone: false, + safe: this.config.safeMode, + attributes: { + showtitle: true, + icons: 'font' + } + }) + + const $ = cheerio.load(html, { + decodeEntities: true + }) + + $('pre.highlight > code.language-diagram').each((i, elm) => { + const diagramContent = Buffer.from($(elm).html(), 'base64').toString() + $(elm).parent().replaceWith(`
${diagramContent}`) + }) + + return $.html() + } +} diff --git a/yarn.lock b/yarn.lock index 37a56058..92107b1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -177,6 +177,21 @@ dependencies: tslib "~2.0.1" +"@asciidoctor/cli@3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@asciidoctor/cli/-/cli-3.5.0.tgz#0b0a0204880b325971fb2af33bf490ab67672d8f" + integrity sha512-/VMHXcZBnZ9vgWfmqk9Hu0x0gMjPLup0YGq/xA8qCQuk11kUIZNMVQwgSsIUzOEwJqIUD7CgncJdtfwv1Ndxuw== + dependencies: + yargs "16.2.0" + +"@asciidoctor/core@2.2.6": + version "2.2.6" + resolved "https://registry.yarnpkg.com/@asciidoctor/core/-/core-2.2.6.tgz#a59a9e8ab48ac0a615d5a3200214d3071291c5d5" + integrity sha512-TmB2K5UfpDpSbCNBBntXzKHcAk2EA3/P68jmWvmJvglVUdkO9V6kTAuXVe12+h6C4GK0ndwuCrHHtEVcL5t6pQ== + dependencies: + asciidoctor-opal-runtime "0.3.3" + unxhr "1.0.1" + "@azure/abort-controller@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.0.1.tgz#8510935b25ac051e58920300e9d7b511ca6e656a" @@ -5168,6 +5183,22 @@ asap@^2.0.0, asap@~2.0.3: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= +asciidoctor-opal-runtime@0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/asciidoctor-opal-runtime/-/asciidoctor-opal-runtime-0.3.3.tgz#2667635f858d3eb3fdfcf6795cf68138e2040174" + integrity sha512-/CEVNiOia8E5BMO9FLooo+Kv18K4+4JBFRJp8vUy/N5dMRAg+fRNV4HA+o6aoSC79jVU/aT5XvUpxSxSsTS8FQ== + dependencies: + glob "7.1.3" + unxhr "1.0.1" + +asciidoctor@2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/asciidoctor/-/asciidoctor-2.2.6.tgz#43b5fec8ab91ed2d8d1815c75067cfa29da2e568" + integrity sha512-EXG3+F2pO21B+COfQmV/WgEgGiy7nG/mJiS/o5DXpaT2q82FRZWPVkbMZrpDvpu4pjXe5c754RbZR9Vz0L0Vtw== + dependencies: + "@asciidoctor/cli" "3.5.0" + "@asciidoctor/core" "2.2.6" + asn1.js@^4.0.0: version "4.10.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" @@ -6681,6 +6712,11 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +codemirror-asciidoc@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/codemirror-asciidoc/-/codemirror-asciidoc-1.0.4.tgz#7439fcc38f30f1408ab144d156bf398ed35b2cc0" + integrity sha512-U+G8+ToPONYFGkwTprxpFzV6EV1bCA9zChAA8uT2YAnKFn357JMWL2VkdIPy2yP5N/X13GzslMOGaAk1UNE3rA== + codemirror@5.58.2: version "5.58.2" resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.58.2.tgz#ed54a1796de1498688bea1cdd4e9eeb187565d1b" @@ -10252,6 +10288,18 @@ glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" +glob@7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^6.0.1: version "6.0.4" resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" @@ -19425,6 +19473,11 @@ untildify@^4.0.0: resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== +unxhr@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unxhr/-/unxhr-1.0.1.tgz#92200322d66c728993de771f9e01eeb21f41bc7b" + integrity sha512-MAhukhVHyaLGDjyDYhy8gVjWJyhTECCdNsLwlMoGFoNJ3o79fpQhtQuzmAE4IxCMDwraF4cW8ZjpAV0m9CRQbg== + upath@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" @@ -20445,6 +20498,11 @@ y18n@^5.0.2: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18" integrity sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg== +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + yaeti@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" @@ -20514,6 +20572,19 @@ yargs@16.1.0: y18n "^5.0.2" yargs-parser "^20.2.2" +yargs@16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + yargs@^13.3.2: version "13.3.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"