From 80b1cbff5c498c402cc33c08b13a06a34c0ef94f Mon Sep 17 00:00:00 2001 From: NGPixel Date: Sat, 13 May 2023 02:36:45 +0000 Subject: [PATCH] feat: editor + page rendering improvements --- server/renderers/markdown.mjs | 10 +- .../renderers/modules/markdown-it-imsize.mjs | 259 ++++++++++++++++++ ux/src/components/PageHeader.vue | 38 ++- ux/src/css/page-contents.scss | 113 ++++++-- ux/src/i18n/locales/en.json | 2 + ux/src/pages/Index.vue | 4 +- ux/src/renderers/markdown.js | 10 +- .../renderers/modules/markdown-it-imsize.js | 259 ++++++++++++++++++ 8 files changed, 651 insertions(+), 44 deletions(-) create mode 100644 server/renderers/modules/markdown-it-imsize.mjs create mode 100644 ux/src/renderers/modules/markdown-it-imsize.js diff --git a/server/renderers/markdown.mjs b/server/renderers/markdown.mjs index 7c7dff28..4d087bfb 100644 --- a/server/renderers/markdown.mjs +++ b/server/renderers/markdown.mjs @@ -10,9 +10,9 @@ import mdSub from 'markdown-it-sub' import mdMark from 'markdown-it-mark' import mdMultiTable from 'markdown-it-multimd-table' import mdFootnote from 'markdown-it-footnote' -// import mdImsize from 'markdown-it-imsize' import katex from 'katex' -import underline from './modules/markdown-it-underline.mjs' +import mdImsize from './modules/markdown-it-imsize.mjs' +import mdUnderline from './modules/markdown-it-underline.mjs' // import 'katex/dist/contrib/mhchem' import twemoji from 'twemoji' import plantuml from './modules/plantuml.mjs' @@ -51,7 +51,7 @@ export async function render (input, config) { } else if (['mermaid', 'plantuml'].includes(lang)) { return `
${escape(str)}
` } else { - const highlighted = lang ? hljs.highlight(str, { language: lang, ignoreIllegals: true }) : hljs.highlightAuto(str) + const highlighted = lang ? hljs.highlight(str, { language: lang, ignoreIllegals: true }) : { value: str } const lineCount = highlighted.value.match(/\n/g).length const lineNums = lineCount > 1 ? `` : '' return `
${highlighted.value}${lineNums}
` @@ -70,10 +70,10 @@ export async function render (input, config) { .use(mdSub) .use(mdMark) .use(mdFootnote) - // .use(mdImsize) + .use(mdImsize) if (config.underline) { - md.use(underline) + md.use(mdUnderline) } if (config.mdmultiTable) { diff --git a/server/renderers/modules/markdown-it-imsize.mjs b/server/renderers/modules/markdown-it-imsize.mjs new file mode 100644 index 00000000..6b6b30e6 --- /dev/null +++ b/server/renderers/modules/markdown-it-imsize.mjs @@ -0,0 +1,259 @@ +// Adapted from markdown-it-imsize plugin by @tatsy +// Original source https://github.com/tatsy/markdown-it-imsize/blob/master/lib/index.js + +function renderImSize (state, silent) { + let attrs + let code + let label + let pos + let ref + let res + let title + let width = '' + let height = '' + let token + let tokens + let start + let href = '' + const oldPos = state.pos + const max = state.posMax + + if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false } + if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false } + + const labelStart = state.pos + 2 + const labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false) + + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { return false } + + pos = labelEnd + 1 + if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { + // + // Inline link + // + + // [link]( "title" ) + // ^^ skipping these spaces + pos++ + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (code !== 0x20 && code !== 0x0A) { break } + } + if (pos >= max) { return false } + + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax) + if (res.ok) { + href = state.md.normalizeLink(res.str) + if (state.md.validateLink(href)) { + pos = res.pos + } else { + href = '' + } + } + + // [link]( "title" ) + // ^^ skipping these spaces + start = pos + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (code !== 0x20 && code !== 0x0A) { break } + } + + // [link]( "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax) + if (pos < max && start !== pos && res.ok) { + title = res.str + pos = res.pos + + // [link]( "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (code !== 0x20 && code !== 0x0A) { break } + } + } else { + title = '' + } + + // [link]( "title" =WxH ) + // ^^^^ parsing image size + if (pos - 1 >= 0) { + code = state.src.charCodeAt(pos - 1) + + // there must be at least one white spaces + // between previous field and the size + if (code === 0x20) { + res = parseImageSize(state.src, pos, state.posMax) + if (res.ok) { + width = res.width + height = res.height + pos = res.pos + + // [link]( "title" =WxH ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (code !== 0x20 && code !== 0x0A) { break } + } + } + } + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { + state.pos = oldPos + return false + } + pos++ + } else { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { return false } + + // [foo] [bar] + // ^^ optional whitespace (can include newlines) + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (code !== 0x20 && code !== 0x0A) { break } + } + + if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { + start = pos + 1 + pos = state.md.helpers.parseLinkLabel(state, pos) + if (pos >= 0) { + label = state.src.slice(start, pos++) + } else { + pos = labelEnd + 1 + } + } else { + pos = labelEnd + 1 + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { label = state.src.slice(labelStart, labelEnd) } + + ref = state.env.references[state.md.utils.normalizeReference(label)] + if (!ref) { + state.pos = oldPos + return false + } + href = ref.href + title = ref.title + } + + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + state.pos = labelStart + state.posMax = labelEnd + + const newState = new state.md.inline.State( + state.src.slice(labelStart, labelEnd), + state.md, + state.env, + tokens = [] + ) + newState.md.inline.tokenize(newState) + + token = state.push('image', 'img', 0) + token.attrs = attrs = [['src', href], + ['alt', '']] + token.children = tokens + if (title) { + attrs.push(['title', title]) + } + + if (width !== '') { + attrs.push(['width', width]) + } + + if (height !== '') { + attrs.push(['height', height]) + } + } + + state.pos = pos + state.posMax = max + return true +} + +function parseNextNumber (str, pos, max) { + let code + const start = pos + const result = { + ok: false, + pos, + value: '' + } + + code = str.charCodeAt(pos) + + while ((pos < max && (code >= 0x30 /* 0 */ && code <= 0x39 /* 9 */)) || code === 0x25 /* % */) { + code = str.charCodeAt(++pos) + } + + result.ok = true + result.pos = pos + result.value = str.slice(start, pos) + + return result +} + +function parseImageSize (str, pos, max) { + let code + const result = { + ok: false, + pos: 0, + width: '', + height: '' + } + + if (pos >= max) { return result } + + code = str.charCodeAt(pos) + + if (code !== 0x3d /* = */) { return result } + + pos++ + + // size must follow = without any white spaces as follows + // (1) =300x200 + // (2) =300x + // (3) =x200 + code = str.charCodeAt(pos) + if (code !== 0x78 /* x */ && (code < 0x30 || code > 0x39) /* [0-9] */) { + return result + } + + // parse width + const resultW = parseNextNumber(str, pos, max) + pos = resultW.pos + + // next charactor must be 'x' + code = str.charCodeAt(pos) + if (code !== 0x78 /* x */) { return result } + + pos++ + + // parse height + const resultH = parseNextNumber(str, pos, max) + pos = resultH.pos + + result.width = resultW.value + result.height = resultH.value + result.pos = pos + result.ok = true + return result +} + +export default (md) => { + md.inline.ruler.before('emphasis', 'image', renderImSize) +} diff --git a/ux/src/components/PageHeader.vue b/ux/src/components/PageHeader.vue index 86318fbe..62ddc836 100644 --- a/ux/src/components/PageHeader.vue +++ b/ux/src/components/PageHeader.vue @@ -116,14 +116,16 @@ :href='siteStore.docsBase + `/editor/${editorStore.editor}`' target='_blank' type='a' - ) + ) + q-tooltip {{ t(`common.actions.viewDocs`) }} q-btn.q-ml-sm.acrylic-btn( icon='las la-cog' flat color='grey' :aria-label='t(`editor.settings`)' @click='openEditorSettings' - ) + ) + q-tooltip {{ t(`editor.settings`) }} template(v-if='editorStore.isActive || editorStore.hasPendingChanges') q-btn.acrylic-btn.q-ml-sm( flat @@ -139,8 +141,8 @@ flat icon='las la-check' color='positive' - label='Create Page' - aria-label='Create Page' + :label='t(`editor.createPage`)' + :aria-label='t(`editor.createPage`)' no-caps @click='createPage' ) @@ -149,19 +151,21 @@ flat icon='las la-check' color='positive' - label='Save Changes' - aria-label='Save Changes' + :label='t(`common.actions.saveChanges`)' + :aria-label='t(`common.actions.saveChanges`)' :disabled='!editorStore.hasPendingChanges' no-caps - @click='saveChanges' - ) + @click.exact='saveChanges(false)' + @click.ctrl.exact='saveChanges(true)' + ) + q-tooltip {{ t(`editor.saveAndCloseTip`) }} template(v-else-if='userStore.can(`edit:pages`)') q-btn.acrylic-btn.q-ml-md( flat icon='las la-edit' color='deep-orange-9' - label='Edit' - aria-label='Edit' + :label='t(`common.actions.edit`)' + :aria-label='t(`common.actions.edit`)' no-caps @click='editPage' ) @@ -258,7 +262,7 @@ async function discardChanges () { $q.loading.hide() } -async function saveChanges () { +async function saveChanges (closeAfter = false) { if (siteStore.features.reasonForChange !== 'off') { $q.dialog({ component: defineAsyncComponent(() => import('../components/PageReasonForChangeDialog.vue')), @@ -269,14 +273,14 @@ async function saveChanges () { editorStore.$patch({ reasonForChange: reason }) - saveChangesCommit() + saveChangesCommit(closeAfter) }) } else { - saveChangesCommit() + saveChangesCommit(closeAfter) } } -async function saveChangesCommit () { +async function saveChangesCommit (closeAfter = false) { $q.loading.show() try { await pageStore.pageSave() @@ -284,6 +288,12 @@ async function saveChangesCommit () { type: 'positive', message: 'Page saved successfully.' }) + if (closeAfter) { + editorStore.$patch({ + isActive: false, + editor: '' + }) + } } catch (err) { $q.notify({ type: 'negative', diff --git a/ux/src/css/page-contents.scss b/ux/src/css/page-contents.scss index fa478411..a8af019e 100644 --- a/ux/src/css/page-contents.scss +++ b/ux/src/css/page-contents.scss @@ -1,6 +1,6 @@ .page-contents { color: #424242; - font-size: 14px; + font-size: 16px; > *:first-child { margin-top: 0; @@ -15,7 +15,7 @@ // --------------------------------- a { - color: $blue; + color: $blue-8; &.is-internal-link.is-invalid-page { color: $red-8; @@ -40,7 +40,7 @@ } @at-root .body--dark & { - color: $blue-2; + color: $blue-4; } } @@ -51,6 +51,7 @@ h1, h2, h3, h4, h5, h6 { padding: 0; margin: 0; + font-weight: 400; position: relative; line-height: normal; @@ -65,14 +66,11 @@ } } - P + h2 { - margin-top: 12px; - } - h1 { font-size: 3em; font-weight: 500; padding: 12px 0; + // color: var(--q-primary); } h2 { font-size: 2.4em; @@ -92,6 +90,29 @@ font-size: 1.25em; } + * + h1 { + margin-top: .5em; + padding-top: .5em; + // border-top: 2px solid var(--q-primary); + position: relative; + + &::before { + position: absolute; + width: 100%; + height: 1px; + content: ' '; + background: linear-gradient(to right, var(--q-primary), transparent); + top: 0; + left: -16px; + } + } + + *:not(h1) + h2 { + margin-top: .5em; + padding-top: .5em; + border-top: 1px dotted #CCC; + } + .toc-anchor { display: none; position: absolute; @@ -107,12 +128,8 @@ // --------------------------------- p { - padding: 1rem 0 0 0; - margin: 0; - - @at-root .page-contents > div > p:first-child { - padding-top: 0; - } + padding: 0; + margin: .3em 0 1em 0; } // --------------------------------- @@ -120,7 +137,7 @@ // --------------------------------- blockquote { - padding: 0 1rem 1rem 1rem; + padding: 1em 1em .3em 1em; background-color: $blue-grey-1; border-left: 55px solid $blue-grey-5; border-radius: .5rem; @@ -239,21 +256,29 @@ // --------------------------------- ol, ul:not(.tabset-tabs) { - padding-top: 1rem; width: 100%; + li > p { + &:first-child { + margin-top: 0; + } + &:last-child { + margin-bottom: 0; + } + } + @at-root .is-rtl & { padding-left: 0; - padding-right: 1rem; + padding-right: 1em; } li > ul, li > ol { padding-top: .5rem; - padding-left: 1rem; + padding-left: 1em; @at-root .is-rtl & { padding-left: 0; - padding-right: 1rem; + padding-right: 1em; } } @@ -413,6 +438,58 @@ } } + // --------------------------------- + // TASK LISTS + // --------------------------------- + + .contains-task-list { + padding-left: 1em; + } + + .task-list-item { + position: relative; + list-style-type: none; + + &-checkbox[disabled] { + width: 1.1rem; + height: 1.1rem; + top: 2px; + position: relative; + margin-right: .4em; + background-color: $dark-5; + border-width: 0; + + &::after { + position: absolute; + left: 0; + top: 0; + font-family: "Material Design Icons"; + font-size: 1.25em; + font-weight: normal; + content: '\F0131'; + color: $grey-10; + display: block; + border: none; + background-color: #FFF; + line-height: 1em; + cursor: default; + + @at-root .body--dark & { + color: #FFF; + background-color: $dark-6; + } + } + + &[checked]::after { + content: '\F0C52'; + } + } + + .contains-task-list { + padding: .5rem 0 0 1.5rem; + } + } + // --------------------------------- // CODE // --------------------------------- diff --git a/ux/src/i18n/locales/en.json b/ux/src/i18n/locales/en.json index 5e301ad2..0146e1ed 100644 --- a/ux/src/i18n/locales/en.json +++ b/ux/src/i18n/locales/en.json @@ -1417,6 +1417,7 @@ "editor.conflict.whatToDo": "What do you want to do?", "editor.conflict.whatToDoLocal": "Use your current local version and ignore the latest changes.", "editor.conflict.whatToDoRemote": "Use the remote version (latest) and discard your changes.", + "editor.createPage": "Create Page", "editor.markup.admonitionDanger": "Danger / Important Admonition", "editor.markup.admonitionInfo": "Info / Note Admonition", "editor.markup.admonitionSuccess": "Tip / Success Admonition", @@ -1554,6 +1555,7 @@ "editor.save.processing": "Rendering", "editor.save.saved": "Saved", "editor.save.updateSuccess": "Page updated successfully.", + "editor.saveAndCloseTip": "Ctrl / Cmd + Click to save and close", "editor.select.cannotChange": "This cannot be changed once the page is created.", "editor.select.customView": "or create a custom view?", "editor.select.title": "Which editor do you want to use for this page?", diff --git a/ux/src/pages/Index.vue b/ux/src/pages/Index.vue index e8d630b2..6d097527 100644 --- a/ux/src/pages/Index.vue +++ b/ux/src/pages/Index.vue @@ -229,12 +229,12 @@ const state = reactive({ const thumbStyle = { right: '2px', borderRadius: '5px', - backgroundColor: '#000', + backgroundColor: $q.dark.isActive ? '#FFF' : '#000', width: '5px', opacity: 0.15 } const barStyle = { - backgroundColor: '#FAFAFA', + backgroundColor: $q.dark.isActive ? '#161b22' : '#FAFAFA', width: '9px', opacity: 1 } diff --git a/ux/src/renderers/markdown.js b/ux/src/renderers/markdown.js index a6783ee9..74ffc4fb 100644 --- a/ux/src/renderers/markdown.js +++ b/ux/src/renderers/markdown.js @@ -10,9 +10,9 @@ import mdSub from 'markdown-it-sub' import mdMark from 'markdown-it-mark' import mdMultiTable from 'markdown-it-multimd-table' import mdFootnote from 'markdown-it-footnote' -// import mdImsize from 'markdown-it-imsize' import katex from 'katex' -import underline from './modules/markdown-it-underline' +import mdUnderline from './modules/markdown-it-underline' +import mdImsize from './modules/markdown-it-imsize' import 'katex/dist/contrib/mhchem' import twemoji from 'twemoji' import plantuml from './modules/plantuml' @@ -52,7 +52,7 @@ export class MarkdownRenderer { } else if (['mermaid', 'plantuml'].includes(lang)) { return `
${escape(str)}
` } else { - const highlighted = lang ? hljs.highlight(str, { language: lang, ignoreIllegals: true }) : hljs.highlightAuto(str) + const highlighted = lang ? hljs.highlight(str, { language: lang, ignoreIllegals: true }) : { value: str } const lineCount = highlighted.value.match(/\n/g).length const lineNums = lineCount > 1 ? `` : '' return `
${highlighted.value}${lineNums}
` @@ -71,10 +71,10 @@ export class MarkdownRenderer { .use(mdSub) .use(mdMark) .use(mdFootnote) - // .use(mdImsize) + .use(mdImsize) if (config.underline) { - this.md.use(underline) + this.md.use(mdUnderline) } if (config.mdmultiTable) { diff --git a/ux/src/renderers/modules/markdown-it-imsize.js b/ux/src/renderers/modules/markdown-it-imsize.js new file mode 100644 index 00000000..6b6b30e6 --- /dev/null +++ b/ux/src/renderers/modules/markdown-it-imsize.js @@ -0,0 +1,259 @@ +// Adapted from markdown-it-imsize plugin by @tatsy +// Original source https://github.com/tatsy/markdown-it-imsize/blob/master/lib/index.js + +function renderImSize (state, silent) { + let attrs + let code + let label + let pos + let ref + let res + let title + let width = '' + let height = '' + let token + let tokens + let start + let href = '' + const oldPos = state.pos + const max = state.posMax + + if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false } + if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false } + + const labelStart = state.pos + 2 + const labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false) + + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { return false } + + pos = labelEnd + 1 + if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { + // + // Inline link + // + + // [link]( "title" ) + // ^^ skipping these spaces + pos++ + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (code !== 0x20 && code !== 0x0A) { break } + } + if (pos >= max) { return false } + + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax) + if (res.ok) { + href = state.md.normalizeLink(res.str) + if (state.md.validateLink(href)) { + pos = res.pos + } else { + href = '' + } + } + + // [link]( "title" ) + // ^^ skipping these spaces + start = pos + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (code !== 0x20 && code !== 0x0A) { break } + } + + // [link]( "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax) + if (pos < max && start !== pos && res.ok) { + title = res.str + pos = res.pos + + // [link]( "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (code !== 0x20 && code !== 0x0A) { break } + } + } else { + title = '' + } + + // [link]( "title" =WxH ) + // ^^^^ parsing image size + if (pos - 1 >= 0) { + code = state.src.charCodeAt(pos - 1) + + // there must be at least one white spaces + // between previous field and the size + if (code === 0x20) { + res = parseImageSize(state.src, pos, state.posMax) + if (res.ok) { + width = res.width + height = res.height + pos = res.pos + + // [link]( "title" =WxH ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (code !== 0x20 && code !== 0x0A) { break } + } + } + } + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { + state.pos = oldPos + return false + } + pos++ + } else { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { return false } + + // [foo] [bar] + // ^^ optional whitespace (can include newlines) + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (code !== 0x20 && code !== 0x0A) { break } + } + + if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { + start = pos + 1 + pos = state.md.helpers.parseLinkLabel(state, pos) + if (pos >= 0) { + label = state.src.slice(start, pos++) + } else { + pos = labelEnd + 1 + } + } else { + pos = labelEnd + 1 + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { label = state.src.slice(labelStart, labelEnd) } + + ref = state.env.references[state.md.utils.normalizeReference(label)] + if (!ref) { + state.pos = oldPos + return false + } + href = ref.href + title = ref.title + } + + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + state.pos = labelStart + state.posMax = labelEnd + + const newState = new state.md.inline.State( + state.src.slice(labelStart, labelEnd), + state.md, + state.env, + tokens = [] + ) + newState.md.inline.tokenize(newState) + + token = state.push('image', 'img', 0) + token.attrs = attrs = [['src', href], + ['alt', '']] + token.children = tokens + if (title) { + attrs.push(['title', title]) + } + + if (width !== '') { + attrs.push(['width', width]) + } + + if (height !== '') { + attrs.push(['height', height]) + } + } + + state.pos = pos + state.posMax = max + return true +} + +function parseNextNumber (str, pos, max) { + let code + const start = pos + const result = { + ok: false, + pos, + value: '' + } + + code = str.charCodeAt(pos) + + while ((pos < max && (code >= 0x30 /* 0 */ && code <= 0x39 /* 9 */)) || code === 0x25 /* % */) { + code = str.charCodeAt(++pos) + } + + result.ok = true + result.pos = pos + result.value = str.slice(start, pos) + + return result +} + +function parseImageSize (str, pos, max) { + let code + const result = { + ok: false, + pos: 0, + width: '', + height: '' + } + + if (pos >= max) { return result } + + code = str.charCodeAt(pos) + + if (code !== 0x3d /* = */) { return result } + + pos++ + + // size must follow = without any white spaces as follows + // (1) =300x200 + // (2) =300x + // (3) =x200 + code = str.charCodeAt(pos) + if (code !== 0x78 /* x */ && (code < 0x30 || code > 0x39) /* [0-9] */) { + return result + } + + // parse width + const resultW = parseNextNumber(str, pos, max) + pos = resultW.pos + + // next charactor must be 'x' + code = str.charCodeAt(pos) + if (code !== 0x78 /* x */) { return result } + + pos++ + + // parse height + const resultH = parseNextNumber(str, pos, max) + pos = resultH.pos + + result.width = resultW.value + result.height = resultH.value + result.pos = pos + result.ok = true + return result +} + +export default (md) => { + md.inline.ruler.before('emphasis', 'image', renderImSize) +}