mirror of
https://github.com/marktext/marktext.git
synced 2025-05-04 03:51:01 +08:00
support ruby and better raw html display (#849)
* support ruby and better raw html display * finish ruby render * add kbd style * if content is empty string, do not hide tag * update changelog * add auto complement to inline html * opti slit words * opti tool bar style * support open inline a tag if it is a external link * fix: auto complete * add attribute white list * add comment * delete some commented codes
This commit is contained in:
parent
f226f87dcb
commit
5d748eb196
21
.github/CHANGELOG.md
vendored
21
.github/CHANGELOG.md
vendored
@ -6,6 +6,25 @@ This update **fixes a XSS security vulnerability** when exporting a document.
|
|||||||
|
|
||||||
- Minimum supported macOS version is 10.10 (Yosemite)
|
- Minimum supported macOS version is 10.10 (Yosemite)
|
||||||
- Remove `lightColor` and `darkColor` in user preference (color change in view menu does not work any, and will remove when add custom theme.)
|
- Remove `lightColor` and `darkColor` in user preference (color change in view menu does not work any, and will remove when add custom theme.)
|
||||||
|
- We recommand user not use block element in paragraph, please use block element in html block.
|
||||||
|
|
||||||
|
*Not Recommand*
|
||||||
|
|
||||||
|
```md
|
||||||
|
foo<section>bar</section>zar
|
||||||
|
```
|
||||||
|
|
||||||
|
*Recommand*
|
||||||
|
|
||||||
|
```md
|
||||||
|
<div>
|
||||||
|
foo
|
||||||
|
<section>
|
||||||
|
bar
|
||||||
|
</section>
|
||||||
|
zar
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
**:cactus:Feature**
|
**:cactus:Feature**
|
||||||
|
|
||||||
@ -21,6 +40,7 @@ This update **fixes a XSS security vulnerability** when exporting a document.
|
|||||||
- Support maxOS `dark mode`, when you change `mode dark or light` in system, Mark Text will change its theme.
|
- Support maxOS `dark mode`, when you change `mode dark or light` in system, Mark Text will change its theme.
|
||||||
- Add new themes: Ulysses Light, Graphite Light, Material Dark and One Dark.
|
- Add new themes: Ulysses Light, Graphite Light, Material Dark and One Dark.
|
||||||
- Watch file changed in tabs and show a notice(autoSave is `false`) or update the file(autoSave is `true`)
|
- Watch file changed in tabs and show a notice(autoSave is `false`) or update the file(autoSave is `true`)
|
||||||
|
- Support input inline Ruby charactors as raw html (#257)
|
||||||
|
|
||||||
**:butterfly:Optimization**
|
**:butterfly:Optimization**
|
||||||
|
|
||||||
@ -38,6 +58,7 @@ This update **fixes a XSS security vulnerability** when exporting a document.
|
|||||||
- Make table of contents in sidebar collapsible (#404)
|
- Make table of contents in sidebar collapsible (#404)
|
||||||
- Hide titlebar control buttons in custom titlebar style
|
- Hide titlebar control buttons in custom titlebar style
|
||||||
- Corrected hamburger menu offset
|
- Corrected hamburger menu offset
|
||||||
|
- Optimization of inline html displa, now you can nest other inline syntax in inline html(#849)
|
||||||
|
|
||||||
**:beetle:Bug fix**
|
**:beetle:Bug fix**
|
||||||
|
|
||||||
|
@ -117,6 +117,10 @@ span.ag-html-tag {
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.ag-ruby {
|
||||||
|
position: relative;
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
span.ag-math {
|
span.ag-math {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: var(--editorColor);
|
color: var(--editorColor);
|
||||||
@ -125,7 +129,8 @@ span.ag-math {
|
|||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-math > .ag-math-render {
|
.ag-math > .ag-math-render,
|
||||||
|
.ag-ruby > .ag-ruby-render {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@ -135,6 +140,12 @@ span.ag-math {
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-ruby > .ag-ruby-render {
|
||||||
|
padding-bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
div.ag-empty {
|
div.ag-empty {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--editorColor50);
|
color: var(--editorColor50);
|
||||||
@ -163,18 +174,19 @@ span.ag-math > .ag-math-render.ag-math-error {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-hide.ag-ruby,
|
||||||
.ag-hide.ag-math {
|
.ag-hide.ag-math {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
.ag-hide.ag-ruby > .ag-ruby-text,
|
||||||
.ag-hide.ag-math > .ag-math-text {
|
.ag-hide.ag-math > .ag-math-text {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.ag-hide.ag-ruby > .ag-ruby-render,
|
||||||
.ag-hide.ag-math > .ag-math-render {
|
.ag-hide.ag-math > .ag-math-render {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -184,6 +196,7 @@ span.ag-math > .ag-math-render.ag-math-error {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-gray.ag-ruby > .ag-ruby-render::before
|
||||||
.ag-gray.ag-math > .ag-math-render::before {
|
.ag-gray.ag-math > .ag-math-render::before {
|
||||||
border-width: 5px;
|
border-width: 5px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
@ -195,6 +208,7 @@ span.ag-math > .ag-math-render.ag-math-error {
|
|||||||
content: "";
|
content: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-hide.ag-ruby > .ag-ruby-render::before
|
||||||
.ag-hide.ag-math > .ag-math-render::before {
|
.ag-hide.ag-math > .ag-math-render::before {
|
||||||
content: none;
|
content: none;
|
||||||
}
|
}
|
||||||
@ -209,7 +223,7 @@ figure {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -15px;
|
top: -20px;
|
||||||
left: 0;
|
left: 0;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -720,3 +734,7 @@ span.ag-reference-link {
|
|||||||
.ag-meta-or-ctrl a.ag-inline-rule {
|
.ag-meta-or-ctrl a.ag-inline-rule {
|
||||||
cursor: pointer !important;
|
cursor: pointer !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-ruby-render {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
@ -109,6 +109,9 @@ export const CLASS_OR_ID = genUpper2LowerKeyHash([
|
|||||||
'AG_MATH',
|
'AG_MATH',
|
||||||
'AG_MATH_TEXT',
|
'AG_MATH_TEXT',
|
||||||
'AG_MATH_RENDER',
|
'AG_MATH_RENDER',
|
||||||
|
'AG_RUBY',
|
||||||
|
'AG_RUBY_TEXT',
|
||||||
|
'AG_RUBY_RENDER',
|
||||||
'AG_MATH_ERROR',
|
'AG_MATH_ERROR',
|
||||||
'AG_EMPTY',
|
'AG_EMPTY',
|
||||||
'AG_MATH_MARKER',
|
'AG_MATH_MARKER',
|
||||||
@ -238,3 +241,10 @@ export const isInElectron = window && window.process && window.process.type ===
|
|||||||
export const isOsx = window && window.navigator && /Mac/.test(window.navigator.platform)
|
export const isOsx = window && window.navigator && /Mac/.test(window.navigator.platform)
|
||||||
// http[s] (domain or IPv4 or localhost or IPv6) [port] /not-white-space
|
// http[s] (domain or IPv4 or localhost or IPv6) [port] /not-white-space
|
||||||
export const URL_REG = /^http(s)?:\/\/([a-z0-9\-._~]+\.[a-z]{2,}|[0-9.]+|localhost|\[[a-f0-9.:]+\])(:[0-9]{1,5})?\/[\S]+/i
|
export const URL_REG = /^http(s)?:\/\/([a-z0-9\-._~]+\.[a-z]{2,}|[0-9.]+|localhost|\[[a-f0-9.:]+\])(:[0-9]{1,5})?\/[\S]+/i
|
||||||
|
|
||||||
|
// selected from https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
|
||||||
|
export const WHITELIST_ATTRIBUTES = [
|
||||||
|
'align', 'alt', 'checked', 'class', 'color', 'dir', 'disabled', 'for', 'height', 'hidden',
|
||||||
|
'href', 'id', 'lang', 'lazyload', 'rel', 'spellcheck', 'src', 'srcset', 'start', 'style',
|
||||||
|
'target', 'title', 'type', 'value', 'width'
|
||||||
|
]
|
||||||
|
@ -26,6 +26,7 @@ const copyCutCtrl = ContentState => {
|
|||||||
const removedElements = wrapper.querySelectorAll(
|
const removedElements = wrapper.querySelectorAll(
|
||||||
`.${CLASS_OR_ID['AG_TOOL_BAR']},
|
`.${CLASS_OR_ID['AG_TOOL_BAR']},
|
||||||
.${CLASS_OR_ID['AG_MATH_RENDER']},
|
.${CLASS_OR_ID['AG_MATH_RENDER']},
|
||||||
|
.${CLASS_OR_ID['AG_RUBY_RENDER']},
|
||||||
.${CLASS_OR_ID['AG_HTML_PREVIEW']},
|
.${CLASS_OR_ID['AG_HTML_PREVIEW']},
|
||||||
.${CLASS_OR_ID['AG_MATH_PREVIEW']},
|
.${CLASS_OR_ID['AG_MATH_PREVIEW']},
|
||||||
.${CLASS_OR_ID['AG_COPY_REMOVE']},
|
.${CLASS_OR_ID['AG_COPY_REMOVE']},
|
||||||
|
@ -50,7 +50,7 @@ const inputCtrl = ContentState => {
|
|||||||
const key = start.key
|
const key = start.key
|
||||||
const block = this.getBlock(key)
|
const block = this.getBlock(key)
|
||||||
const paragraph = document.querySelector(`#${key}`)
|
const paragraph = document.querySelector(`#${key}`)
|
||||||
let text = getTextContent(paragraph, [ CLASS_OR_ID['AG_MATH_RENDER'] ])
|
let text = getTextContent(paragraph, [ CLASS_OR_ID['AG_MATH_RENDER'], CLASS_OR_ID['AG_RUBY_RENDER'] ])
|
||||||
let needRender = false
|
let needRender = false
|
||||||
let needRenderAll = false
|
let needRenderAll = false
|
||||||
|
|
||||||
|
@ -198,8 +198,7 @@ const tabCtrl = ContentState => {
|
|||||||
start.key === end.key &&
|
start.key === end.key &&
|
||||||
start.offset === end.offset &&
|
start.offset === end.offset &&
|
||||||
startBlock.type === 'span' &&
|
startBlock.type === 'span' &&
|
||||||
startBlock.functionType === 'codeLine' &&
|
(!startBlock.functionType || startBlock.functionType === 'codeLine' && startBlock.lang === 'markup')
|
||||||
startBlock.lang === 'markup'
|
|
||||||
) {
|
) {
|
||||||
const { text } = startBlock
|
const { text } = startBlock
|
||||||
const lastWord = text.split(/\s+/).pop()
|
const lastWord = text.split(/\s+/).pop()
|
||||||
|
@ -59,7 +59,9 @@ class ClickEvent {
|
|||||||
// handler image and inline math preview click
|
// handler image and inline math preview click
|
||||||
const markedImageText = target.previousElementSibling
|
const markedImageText = target.previousElementSibling
|
||||||
const mathRender = target.closest(`.${CLASS_OR_ID['AG_MATH_RENDER']}`)
|
const mathRender = target.closest(`.${CLASS_OR_ID['AG_MATH_RENDER']}`)
|
||||||
|
const rubyRender = target.closest(`.${CLASS_OR_ID['AG_RUBY_RENDER']}`)
|
||||||
const mathText = mathRender && mathRender.previousElementSibling
|
const mathText = mathRender && mathRender.previousElementSibling
|
||||||
|
const rubyText = rubyRender && rubyRender.previousElementSibling
|
||||||
if (markedImageText && markedImageText.classList.contains(CLASS_OR_ID['AG_IMAGE_MARKED_TEXT'])) {
|
if (markedImageText && markedImageText.classList.contains(CLASS_OR_ID['AG_IMAGE_MARKED_TEXT'])) {
|
||||||
eventCenter.dispatch('format-click', {
|
eventCenter.dispatch('format-click', {
|
||||||
event,
|
event,
|
||||||
@ -69,6 +71,8 @@ class ClickEvent {
|
|||||||
selectionText(markedImageText)
|
selectionText(markedImageText)
|
||||||
} else if (mathText) {
|
} else if (mathText) {
|
||||||
selectionText(mathText)
|
selectionText(mathText)
|
||||||
|
} else if (rubyText) {
|
||||||
|
selectionText(rubyText)
|
||||||
}
|
}
|
||||||
// handler html preview click
|
// handler html preview click
|
||||||
const htmlPreview = target.closest(`.ag-function-html`)
|
const htmlPreview = target.closest(`.ag-function-html`)
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { beginRules, inlineRules } from './rules'
|
import { beginRules, inlineRules } from './rules'
|
||||||
import { isLengthEven, union } from '../utils'
|
import { isLengthEven, union } from '../utils'
|
||||||
import { punctuation } from '../config'
|
import { punctuation, WHITELIST_ATTRIBUTES } from '../config'
|
||||||
|
|
||||||
const CAN_NEST_RULES = ['strong', 'em', 'link', 'del', 'image', 'a_link'] // image can not nest but it has children
|
const CAN_NEST_RULES = ['strong', 'em', 'link', 'del', 'image', 'a_link'] // image can not nest but it has children
|
||||||
|
// disallowed html tags in https://github.github.com/gfm/#raw-html
|
||||||
|
const disallowedHtmlTag = /(?:title|textarea|style|xmp|iframe|noembed|noframes|script|plaintext)/i
|
||||||
const validateRules = Object.assign({}, inlineRules)
|
const validateRules = Object.assign({}, inlineRules)
|
||||||
delete validateRules.em
|
delete validateRules.em
|
||||||
delete validateRules.strong
|
delete validateRules.strong
|
||||||
@ -16,21 +17,22 @@ const validWidthAndHeight = value => {
|
|||||||
return value >= 0 ? value : ''
|
return value >= 0 ? value : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSrcAlt = text => {
|
const getAttributes = html => {
|
||||||
const SRC_REG = /src\s*=\s*("|')([^\1]+?)\1/
|
const parser = new DOMParser()
|
||||||
const ALT_REG = /alt\s*=\s*("|')([^\1]+?)\1/
|
const doc = parser.parseFromString(html, 'text/html')
|
||||||
const WIDTH_REG = /width\s*=\s*("|')([^\1]+?)\1/
|
const target = doc.querySelector('body').firstElementChild
|
||||||
const HEIGHT_REG = /height\s*=\s*("|')([^\1]+?)\1/
|
if (!target) return null
|
||||||
const srcMatch = SRC_REG.exec(text)
|
const attrs = {}
|
||||||
const src = srcMatch ? srcMatch[2] : ''
|
for (const attr of target.getAttributeNames()) {
|
||||||
const altMatch = ALT_REG.exec(text)
|
if (!WHITELIST_ATTRIBUTES.includes(attr)) continue
|
||||||
const alt = altMatch ? altMatch[2] : ''
|
if (/width|height/.test(attr)) {
|
||||||
const widthMatch = WIDTH_REG.exec(text)
|
attrs[attr] = validWidthAndHeight(target.getAttribute(attr))
|
||||||
const width = widthMatch ? validWidthAndHeight(widthMatch[2]) : ''
|
} else {
|
||||||
const heightMatch = HEIGHT_REG.exec(text)
|
attrs[attr] = target.getAttribute(attr)
|
||||||
const height = heightMatch ? validWidthAndHeight(heightMatch[2]) : ''
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { src, alt, width, height }
|
return attrs
|
||||||
}
|
}
|
||||||
|
|
||||||
const lowerPriority = (src, offset) => {
|
const lowerPriority = (src, offset) => {
|
||||||
@ -364,57 +366,6 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top) => {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// a_link `<a href="url">Anchor</a>`
|
|
||||||
const aLinkTo = inlineRules['a_link'].exec(src)
|
|
||||||
if (aLinkTo) {
|
|
||||||
pushPending()
|
|
||||||
tokens.push({
|
|
||||||
type: 'a_link',
|
|
||||||
raw: aLinkTo[0],
|
|
||||||
href: aLinkTo[3],
|
|
||||||
openTag: aLinkTo[1],
|
|
||||||
closeTag: aLinkTo[5],
|
|
||||||
anchor: aLinkTo[4],
|
|
||||||
parent: tokens,
|
|
||||||
range: {
|
|
||||||
start: pos,
|
|
||||||
end: pos + aLinkTo[0].length
|
|
||||||
},
|
|
||||||
children: tokenizerFac(aLinkTo[4], undefined, inlineRules, pos + aLinkTo[1].length, false)
|
|
||||||
})
|
|
||||||
|
|
||||||
src = src.substring(aLinkTo[0].length)
|
|
||||||
pos = pos + aLinkTo[0].length
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// html-image
|
|
||||||
const htmlImageTo = inlineRules['html_image'].exec(src)
|
|
||||||
if (htmlImageTo) {
|
|
||||||
const rawAttr = htmlImageTo[2]
|
|
||||||
const { src: imageSrc, alt, width, height } = getSrcAlt(rawAttr)
|
|
||||||
if (imageSrc) {
|
|
||||||
pushPending()
|
|
||||||
tokens.push({
|
|
||||||
type: 'html_image',
|
|
||||||
raw: htmlImageTo[0],
|
|
||||||
tag: htmlImageTo[1],
|
|
||||||
parent: tokens,
|
|
||||||
src: imageSrc,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
alt,
|
|
||||||
range: {
|
|
||||||
start: pos,
|
|
||||||
end: pos + htmlImageTo[0].length
|
|
||||||
}
|
|
||||||
})
|
|
||||||
src = src.substring(htmlImageTo[0].length)
|
|
||||||
pos = pos + htmlImageTo[0].length
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// html escape
|
// html escape
|
||||||
const htmlEscapeTo = inlineRules['html_escape'].exec(src)
|
const htmlEscapeTo = inlineRules['html_escape'].exec(src)
|
||||||
if (htmlEscapeTo) {
|
if (htmlEscapeTo) {
|
||||||
@ -437,14 +388,43 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top) => {
|
|||||||
|
|
||||||
// html-tag
|
// html-tag
|
||||||
const htmlTo = inlineRules['html_tag'].exec(src)
|
const htmlTo = inlineRules['html_tag'].exec(src)
|
||||||
if (htmlTo) {
|
let attrs
|
||||||
|
// handle comment
|
||||||
|
if (htmlTo && htmlTo[1] && !htmlTo[3]) {
|
||||||
const len = htmlTo[0].length
|
const len = htmlTo[0].length
|
||||||
pushPending()
|
pushPending()
|
||||||
tokens.push({
|
tokens.push({
|
||||||
type: 'html_tag',
|
type: 'html_tag',
|
||||||
raw: htmlTo[0],
|
raw: htmlTo[0],
|
||||||
tag: htmlTo[1],
|
tag: '<!---->',
|
||||||
|
openTag: htmlTo[1],
|
||||||
parent: tokens,
|
parent: tokens,
|
||||||
|
attrs: {},
|
||||||
|
range: {
|
||||||
|
start: pos,
|
||||||
|
end: pos + len
|
||||||
|
}
|
||||||
|
})
|
||||||
|
src = src.substring(len)
|
||||||
|
pos = pos + len
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (htmlTo && !(disallowedHtmlTag.test(htmlTo[3])) && (attrs = getAttributes(htmlTo[0]))) {
|
||||||
|
const tag = htmlTo[3]
|
||||||
|
const html = htmlTo[0]
|
||||||
|
const len = htmlTo[0].length
|
||||||
|
|
||||||
|
pushPending()
|
||||||
|
tokens.push({
|
||||||
|
type: 'html_tag',
|
||||||
|
raw: html,
|
||||||
|
tag,
|
||||||
|
openTag: htmlTo[2],
|
||||||
|
closeTag: htmlTo[5],
|
||||||
|
parent: tokens,
|
||||||
|
attrs,
|
||||||
|
content: htmlTo[4],
|
||||||
|
children: htmlTo[4] ? tokenizerFac(htmlTo[4], undefined, inlineRules, pos + htmlTo[2].length, false) : '',
|
||||||
range: {
|
range: {
|
||||||
start: pos,
|
start: pos,
|
||||||
end: pos + len
|
end: pos + len
|
||||||
|
@ -84,6 +84,7 @@ export default function renderLeafBlock (block, cursor, activeBlocks, matches, u
|
|||||||
this.tokenCache.set(text, tokens)
|
this.tokenCache.set(text, tokens)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
children = tokens.reduce((acc, token) => [...acc, ...this[snakeToCamel(token.type)](h, cursor, block, token)], [])
|
children = tokens.reduce((acc, token) => [...acc, ...this[snakeToCamel(token.type)](h, cursor, block, token)], [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import { CLASS_OR_ID } from '../../../config'
|
|
||||||
import { snakeToCamel } from '../../../utils'
|
|
||||||
|
|
||||||
// `a_link`: `<a href="url">anchor</a>`
|
|
||||||
export default function aLink (h, cursor, block, token, outerClass) {
|
|
||||||
const className = this.getClassName(outerClass, block, token, cursor)
|
|
||||||
const tagClassName = className === CLASS_OR_ID['AG_HIDE'] ? className : CLASS_OR_ID['AG_HTML_TAG']
|
|
||||||
const { start, end } = token.range
|
|
||||||
const openTag = this.highlight(h, block, start, start + token.openTag.length, token)
|
|
||||||
const anchor = token.children.reduce((acc, to) => {
|
|
||||||
const chunk = this[snakeToCamel(to.type)](h, cursor, block, to, className)
|
|
||||||
return Array.isArray(chunk) ? [...acc, ...chunk] : [...acc, chunk]
|
|
||||||
}, [])
|
|
||||||
const closeTag = this.highlight(h, block, end - token.closeTag.length, end, token)
|
|
||||||
|
|
||||||
return [
|
|
||||||
h(`span.${tagClassName}.${CLASS_OR_ID['AG_OUTPUT_REMOVE']}`, openTag),
|
|
||||||
h(`a.${CLASS_OR_ID['AG_A_LINK']}`, {
|
|
||||||
dataset: {
|
|
||||||
href: token.href
|
|
||||||
}
|
|
||||||
}, anchor),
|
|
||||||
h(`span.${tagClassName}.${CLASS_OR_ID['AG_OUTPUT_REMOVE']}`, closeTag)
|
|
||||||
]
|
|
||||||
}
|
|
@ -7,7 +7,7 @@ export default function htmlImage (h, cursor, block, token, outerClass) {
|
|||||||
const imageClass = CLASS_OR_ID['AG_IMAGE_MARKED_TEXT']
|
const imageClass = CLASS_OR_ID['AG_IMAGE_MARKED_TEXT']
|
||||||
const { start, end } = token.range
|
const { start, end } = token.range
|
||||||
const tag = this.highlight(h, block, start, end, token)
|
const tag = this.highlight(h, block, start, end, token)
|
||||||
const { src: rawSrc, alt, width, height } = token
|
const { src: rawSrc, alt = '', width, height } = token.attrs
|
||||||
const imageInfo = getImageInfo(rawSrc)
|
const imageInfo = getImageInfo(rawSrc)
|
||||||
const { src } = imageInfo
|
const { src } = imageInfo
|
||||||
let id
|
let id
|
||||||
|
27
src/muya/lib/parser/render/renderInlines/htmlRuby.js
Normal file
27
src/muya/lib/parser/render/renderInlines/htmlRuby.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { CLASS_OR_ID } from '../../../config'
|
||||||
|
import { htmlToVNode } from '../snabbdom'
|
||||||
|
|
||||||
|
export default function htmlRuby (h, cursor, block, token, outerClass) {
|
||||||
|
const className = this.getClassName(outerClass, block, token, cursor)
|
||||||
|
const { children } = token
|
||||||
|
const { start, end } = token.range
|
||||||
|
const content = this.highlight(h, block, start, end, token)
|
||||||
|
const vNode = htmlToVNode(token.raw)
|
||||||
|
|
||||||
|
const previewSelector = `span.${CLASS_OR_ID['AG_RUBY_RENDER']}`
|
||||||
|
|
||||||
|
|
||||||
|
return children ? [
|
||||||
|
h(`span.${className}.${CLASS_OR_ID['AG_RUBY']}`, [
|
||||||
|
h(`span.${CLASS_OR_ID['AG_INLINE_RULE']}.${CLASS_OR_ID['AG_RUBY_TEXT']}`, content),
|
||||||
|
h(previewSelector, {
|
||||||
|
attrs: { contenteditable: 'false' }
|
||||||
|
}, vNode)
|
||||||
|
])
|
||||||
|
// if children is empty string, no need to render ruby charactors...
|
||||||
|
] : [
|
||||||
|
h(`span.${className}.${CLASS_OR_ID['AG_RUBY']}`, [
|
||||||
|
h(`span.${CLASS_OR_ID['AG_INLINE_RULE']}.${CLASS_OR_ID['AG_RUBY_TEXT']}`, content)
|
||||||
|
])
|
||||||
|
]
|
||||||
|
}
|
@ -1,11 +1,66 @@
|
|||||||
import { CLASS_OR_ID } from '../../../config'
|
import { CLASS_OR_ID, BLOCK_TYPE6 } from '../../../config'
|
||||||
|
import { snakeToCamel } from '../../../utils'
|
||||||
|
|
||||||
export default function htmlTag (h, cursor, block, token, outerClass) {
|
export default function htmlTag (h, cursor, block, token, outerClass) {
|
||||||
const className = CLASS_OR_ID['AG_HTML_TAG']
|
const { tag, openTag, closeTag, children, attrs } = token
|
||||||
|
const className = children ? this.getClassName(outerClass, block, token, cursor) : CLASS_OR_ID['AG_GRAY']
|
||||||
|
const tagClassName = className === CLASS_OR_ID['AG_HIDE'] ? className : CLASS_OR_ID['AG_HTML_TAG']
|
||||||
const { start, end } = token.range
|
const { start, end } = token.range
|
||||||
const tag = this.highlight(h, block, start, end, token)
|
const openContent = this.highlight(h, block, start, start + openTag.length, token)
|
||||||
const isBr = /<br(?=\s|\/|>)/.test(token.tag)
|
const closeContent = closeTag
|
||||||
|
? this.highlight(h, block, end - closeTag.length, end, token)
|
||||||
|
: ''
|
||||||
|
|
||||||
|
const anchor = Array.isArray(children) && tag !== 'ruby' // important
|
||||||
|
? children.reduce((acc, to) => {
|
||||||
|
const chunk = this[snakeToCamel(to.type)](h, cursor, block, to, className)
|
||||||
|
return Array.isArray(chunk) ? [...acc, ...chunk] : [...acc, chunk]
|
||||||
|
}, [])
|
||||||
|
: ''
|
||||||
|
switch (tag) {
|
||||||
|
case 'img': {
|
||||||
|
return this.htmlImage(h, cursor, block, token, outerClass)
|
||||||
|
}
|
||||||
|
case 'br': {
|
||||||
|
return [h(`span.${CLASS_OR_ID['AG_HTML_TAG']}`, [...openContent, h(tag)])]
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// handle void html tag
|
||||||
|
if (!closeTag) {
|
||||||
|
return [h(`span.${CLASS_OR_ID['AG_HTML_TAG']}`, openContent)]
|
||||||
|
} else if (tag === 'ruby') {
|
||||||
|
return this.htmlRuby(h, cursor, block, token, outerClass)
|
||||||
|
} else {
|
||||||
|
// if tag is a block level element, use a inline element `span` to instead.
|
||||||
|
// Because we can not nest a block level element in span element(line is span element)
|
||||||
|
// we also recommand user not use block level element in paragraph. use block element in html block.
|
||||||
|
let selector = BLOCK_TYPE6.includes(tag) ? 'span' : tag
|
||||||
|
selector += `.${CLASS_OR_ID['AG_INLINE_RULE']}`
|
||||||
|
const data = {
|
||||||
|
attrs: {},
|
||||||
|
dataset: {}
|
||||||
|
}
|
||||||
|
if (attrs.id) {
|
||||||
|
selector += `#${attrs.id}`
|
||||||
|
}
|
||||||
|
if (attrs.class && /\S/.test(attrs.class)) {
|
||||||
|
const classNames = attrs.class.split(/\s+/)
|
||||||
|
for (const className of classNames) {
|
||||||
|
selector += `.${className}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const attr of Object.keys(attrs)) {
|
||||||
|
if (attr !== 'id' && attr !== 'class') {
|
||||||
|
data.attrs[attr] = attrs[attr]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
h(`span.${className}`, isBr ? [...tag, h('br')] : tag)
|
h(`span.${tagClassName}.${CLASS_OR_ID['AG_OUTPUT_REMOVE']}`, openContent),
|
||||||
|
h(`${selector}`, data, anchor),
|
||||||
|
h(`span.${tagClassName}.${CLASS_OR_ID['AG_OUTPUT_REMOVE']}`, closeContent)
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import tailHeader from './tailHeader'
|
|||||||
import hardLineBreak from './hardLineBreak'
|
import hardLineBreak from './hardLineBreak'
|
||||||
import codeFense from './codeFense'
|
import codeFense from './codeFense'
|
||||||
import inlineMath from './inlineMath'
|
import inlineMath from './inlineMath'
|
||||||
import aLink from './aLink'
|
|
||||||
import autoLink from './autoLink'
|
import autoLink from './autoLink'
|
||||||
import loadImageAsync from './loadImageAsync'
|
import loadImageAsync from './loadImageAsync'
|
||||||
import htmlImage from './htmlImage'
|
import htmlImage from './htmlImage'
|
||||||
@ -24,6 +23,7 @@ import strong from './strong'
|
|||||||
import htmlEscape from './htmlEscape'
|
import htmlEscape from './htmlEscape'
|
||||||
import multipleMath from './multipleMath'
|
import multipleMath from './multipleMath'
|
||||||
import referenceDefinition from './referenceDefinition'
|
import referenceDefinition from './referenceDefinition'
|
||||||
|
import htmlRuby from './htmlRuby'
|
||||||
import referenceLink from './referenceLink'
|
import referenceLink from './referenceLink'
|
||||||
import referenceImage from './referenceImage'
|
import referenceImage from './referenceImage'
|
||||||
|
|
||||||
@ -39,7 +39,6 @@ export default {
|
|||||||
hardLineBreak,
|
hardLineBreak,
|
||||||
codeFense,
|
codeFense,
|
||||||
inlineMath,
|
inlineMath,
|
||||||
aLink,
|
|
||||||
autoLink,
|
autoLink,
|
||||||
loadImageAsync,
|
loadImageAsync,
|
||||||
htmlImage,
|
htmlImage,
|
||||||
@ -54,6 +53,7 @@ export default {
|
|||||||
htmlEscape,
|
htmlEscape,
|
||||||
multipleMath,
|
multipleMath,
|
||||||
referenceDefinition,
|
referenceDefinition,
|
||||||
|
htmlRuby,
|
||||||
referenceLink,
|
referenceLink,
|
||||||
referenceImage
|
referenceImage
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,7 @@ export const inlineRules = {
|
|||||||
'reference_link': /^\[([^\]]+?)(\\*)\](?:\[([^\]]*?)(\\*)\])?/,
|
'reference_link': /^\[([^\]]+?)(\\*)\](?:\[([^\]]*?)(\\*)\])?/,
|
||||||
'reference_image': /^\!\[([^\]]+?)(\\*)\](?:\[([^\]]*?)(\\*)\])?/,
|
'reference_image': /^\!\[([^\]]+?)(\\*)\](?:\[([^\]]*?)(\\*)\])?/,
|
||||||
'tail_header': /^(\s{1,}#{1,})(\s*)$/,
|
'tail_header': /^(\s{1,}#{1,})(\s*)$/,
|
||||||
'a_link': /^(<a[\s\S]*href\s*=\s*("|')(.+?)\2(?=\s|>)[\s\S]*(?!\\)>)([\s\S]*)(<\/a>)/, // can nest
|
'html_tag': /^(<!--[\s\S]*?-->|(<([a-zA-Z]{1}[a-zA-Z\d-]*) *[_\.\-/:a-zA-Z\d='"; ]* *(?:\/)?>)(?:([\s\S]*?)(<\/\3 *>))?)/, // row html
|
||||||
'html_image': /^(<img\s([\s\S]*?src[\s\S]+?)(?!\\)>)/,
|
|
||||||
'html_tag': /^(<!--[\s\S]*?-->|<\/?[a-zA-Z\d-]+[\s\S]*?(?!\\)>)/,
|
|
||||||
'html_escape': new RegExp(`^(${escapeCharacters.join('|')})`, 'i'),
|
'html_escape': new RegExp(`^(${escapeCharacters.join('|')})`, 'i'),
|
||||||
'hard_line_break': /^(\s{2,})$/,
|
'hard_line_break': /^(\s{2,})$/,
|
||||||
|
|
||||||
|
@ -420,10 +420,10 @@ class Selection {
|
|||||||
let count = 0
|
let count = 0
|
||||||
for (i = 0; i < len; i++) {
|
for (i = 0; i < len; i++) {
|
||||||
const child = childNodes[i]
|
const child = childNodes[i]
|
||||||
if (count + getTextContent(child, [ CLASS_OR_ID['AG_MATH_RENDER'] ]).length >= offset) {
|
if (count + getTextContent(child, [ CLASS_OR_ID['AG_MATH_RENDER'], CLASS_OR_ID['AG_RUBY_RENDER'] ]).length >= offset) {
|
||||||
return getNodeAndOffset(child, offset - count)
|
return getNodeAndOffset(child, offset - count)
|
||||||
} else {
|
} else {
|
||||||
count += getTextContent(child, [ CLASS_OR_ID['AG_MATH_RENDER'] ]).length
|
count += getTextContent(child, [ CLASS_OR_ID['AG_MATH_RENDER'], CLASS_OR_ID['AG_RUBY_RENDER'] ]).length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { node, offset }
|
return { node, offset }
|
||||||
@ -471,7 +471,7 @@ class Selection {
|
|||||||
do {
|
do {
|
||||||
preSibling = preSibling.previousSibling
|
preSibling = preSibling.previousSibling
|
||||||
if (preSibling) {
|
if (preSibling) {
|
||||||
offset += getTextContent(preSibling, [ CLASS_OR_ID['AG_MATH_RENDER'] ]).length
|
offset += getTextContent(preSibling, [ CLASS_OR_ID['AG_MATH_RENDER'], CLASS_OR_ID['AG_RUBY_RENDER'] ]).length
|
||||||
}
|
}
|
||||||
} while (preSibling)
|
} while (preSibling)
|
||||||
return (node === paragraph || node.parentNode === paragraph)
|
return (node === paragraph || node.parentNode === paragraph)
|
||||||
|
@ -26,7 +26,8 @@
|
|||||||
color: var(--editorColor);
|
color: var(--editorColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-list-picker .active, .ag-list-picker .item:hover {
|
.ag-list-picker .item:hover,
|
||||||
|
.ag-list-picker .item.active {
|
||||||
background-color: var(--floatHoverColor);
|
background-color: var(--floatHoverColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +112,17 @@ pre.ag-paragraph {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
color: var(--editorColor);
|
||||||
|
background: var(--floatBgColor);
|
||||||
|
border: 1px solid var(--floatBorderColor);
|
||||||
|
border-radius: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
transform: scale(.8);
|
||||||
|
padding: 0px 5px;
|
||||||
|
box-shadow: inset 0 -1px 0 var(--floatBorderColor);
|
||||||
|
}
|
||||||
|
|
||||||
@media not print {
|
@media not print {
|
||||||
|
|
||||||
#ag-editor-id {
|
#ag-editor-id {
|
||||||
@ -414,19 +425,22 @@ pre[class*="language-"] {
|
|||||||
color: var(--editorColor50);
|
color: var(--editorColor50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-hide.ag-ruby > .ag-ruby-render,
|
||||||
.ag-hide.ag-math > .ag-math-render {
|
.ag-hide.ag-math > .ag-math-render {
|
||||||
color: var(--editorColor);
|
color: var(--editorColor);
|
||||||
}
|
}
|
||||||
|
blockquote .ag-hide.ag-ruby > .ag-ruby-render,
|
||||||
blockquote .ag-hide.ag-math > .ag-math-render {
|
blockquote .ag-hide.ag-math > .ag-math-render {
|
||||||
color: var(--editorColor50);
|
color: var(--editorColor50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-gray.ag-ruby > .ag-ruby-render,
|
||||||
.ag-gray.ag-math > .ag-math-render {
|
.ag-gray.ag-math > .ag-math-render {
|
||||||
color: var(--editorColor);
|
color: var(--editorColor);
|
||||||
background: var(--floatBgColor);
|
background: var(--floatBgColor);
|
||||||
box-shadow: 0 4px 8px 0 var(--floatBorderColor);
|
box-shadow: 0 4px 8px 0 var(--floatBorderColor);
|
||||||
}
|
}
|
||||||
|
.ag-gray.ag-ruby > .ag-ruby-render::before,
|
||||||
.ag-gray.ag-math > .ag-math-render::before {
|
.ag-gray.ag-math > .ag-math-render::before {
|
||||||
border-bottom-color: var(--floatBgColor);
|
border-bottom-color: var(--floatBgColor);
|
||||||
}
|
}
|
||||||
@ -458,7 +472,7 @@ body.dark .ag-image-marked-text.ag-image-fail::before {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ag-front-icon {
|
.ag-front-icon {
|
||||||
fill: var(--editorColor50);
|
fill: var(--editorColor30);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-front-icon::before {
|
.ag-front-icon::before {
|
||||||
|
Loading…
Reference in New Issue
Block a user