mirror of
https://github.com/marktext/marktext.git
synced 2025-05-02 17:31:26 +08:00
parent
ecc23a9cb0
commit
c959d185b2
@ -67,7 +67,7 @@
|
|||||||
"katex": "^0.15.2",
|
"katex": "^0.15.2",
|
||||||
"keyboard-layout": "^2.0.17",
|
"keyboard-layout": "^2.0.17",
|
||||||
"keytar": "^7.7.0",
|
"keytar": "^7.7.0",
|
||||||
"mermaid": "8.8.4",
|
"mermaid": "^8.13.10",
|
||||||
"minizlib": "^2.1.1",
|
"minizlib": "^2.1.1",
|
||||||
"plist": "^3.0.4",
|
"plist": "^3.0.4",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
@ -78,9 +78,9 @@
|
|||||||
"turndown": "^7.1.1",
|
"turndown": "^7.1.1",
|
||||||
"underscore": "^1.13.2",
|
"underscore": "^1.13.2",
|
||||||
"unsplash-js": "^7.0.15",
|
"unsplash-js": "^7.0.15",
|
||||||
"vega": "^5.17.3",
|
"vega": "^5.21.0",
|
||||||
"vega-embed": "^6.14.2",
|
"vega-embed": "^6.20.5",
|
||||||
"vega-lite": "^4.17.0",
|
"vega-lite": "^5.2.0",
|
||||||
"vscode-ripgrep": "^1.12.1",
|
"vscode-ripgrep": "^1.12.1",
|
||||||
"vue": "^2.6.14",
|
"vue": "^2.6.14",
|
||||||
"vue-electron": "^1.0.6",
|
"vue-electron": "^1.0.6",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import loadRenderer from '../../renderers'
|
import loadRenderer from '../../renderers'
|
||||||
import { CLASS_OR_ID } from '../../config'
|
import { CLASS_OR_ID, PREVIEW_DOMPURIFY_CONFIG } from '../../config'
|
||||||
import { conflict, mixins, camelToSnake } from '../../utils'
|
import { conflict, mixins, camelToSnake, sanitize } from '../../utils'
|
||||||
import { patch, toVNode, toHTML, h, addNStoVNodeSvgChildren } from './snabbdom'
|
import { patch, toVNode, toHTML, h, addNStoVNodeSvgChildren } from './snabbdom'
|
||||||
import { beginRules } from '../rules'
|
import { beginRules } from '../rules'
|
||||||
import renderInlines from './renderInlines'
|
import renderInlines from './renderInlines'
|
||||||
@ -99,6 +99,7 @@ class StateRender {
|
|||||||
if (this.mermaidCache.size) {
|
if (this.mermaidCache.size) {
|
||||||
const mermaid = await loadRenderer('mermaid')
|
const mermaid = await loadRenderer('mermaid')
|
||||||
mermaid.initialize({
|
mermaid.initialize({
|
||||||
|
securityLevel: 'strict',
|
||||||
theme: this.muya.options.mermaidTheme
|
theme: this.muya.options.mermaidTheme
|
||||||
})
|
})
|
||||||
for (const [key, value] of this.mermaidCache.entries()) {
|
for (const [key, value] of this.mermaidCache.entries()) {
|
||||||
@ -109,7 +110,7 @@ class StateRender {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
mermaid.parse(code)
|
mermaid.parse(code)
|
||||||
target.innerHTML = code
|
target.innerHTML = sanitize(code, PREVIEW_DOMPURIFY_CONFIG, true)
|
||||||
mermaid.init(undefined, target)
|
mermaid.init(undefined, target)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
target.innerHTML = '< Invalid Mermaid Codes >'
|
target.innerHTML = '< Invalid Mermaid Codes >'
|
||||||
|
@ -38,18 +38,20 @@ class ExportHtml {
|
|||||||
for (const code of codes) {
|
for (const code of codes) {
|
||||||
const preEle = code.parentNode
|
const preEle = code.parentNode
|
||||||
const mermaidContainer = document.createElement('div')
|
const mermaidContainer = document.createElement('div')
|
||||||
mermaidContainer.innerHTML = code.innerHTML
|
mermaidContainer.innerHTML = sanitize(unescapeHtml(code.innerHTML), EXPORT_DOMPURIFY_CONFIG, true)
|
||||||
mermaidContainer.classList.add('mermaid')
|
mermaidContainer.classList.add('mermaid')
|
||||||
preEle.replaceWith(mermaidContainer)
|
preEle.replaceWith(mermaidContainer)
|
||||||
}
|
}
|
||||||
const mermaid = await loadRenderer('mermaid')
|
const mermaid = await loadRenderer('mermaid')
|
||||||
// We only export light theme, so set mermaid theme to `default`, in the future, we can choose whick theme to export.
|
// We only export light theme, so set mermaid theme to `default`, in the future, we can choose whick theme to export.
|
||||||
mermaid.initialize({
|
mermaid.initialize({
|
||||||
|
securityLevel: 'strict',
|
||||||
theme: 'default'
|
theme: 'default'
|
||||||
})
|
})
|
||||||
mermaid.init(undefined, this.exportContainer.querySelectorAll('div.mermaid'))
|
mermaid.init(undefined, this.exportContainer.querySelectorAll('div.mermaid'))
|
||||||
if (this.muya) {
|
if (this.muya) {
|
||||||
mermaid.initialize({
|
mermaid.initialize({
|
||||||
|
securityLevel: 'strict',
|
||||||
theme: this.muya.options.mermaidTheme
|
theme: this.muya.options.mermaidTheme
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -220,7 +220,8 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import fs from 'fs/promises'
|
import fs from 'fs'
|
||||||
|
import fsPromises from 'fs/promises'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { isDirectory, isFile } from 'common/filesystem'
|
import { isDirectory, isFile } from 'common/filesystem'
|
||||||
import bus from '../../bus'
|
import bus from '../../bus'
|
||||||
@ -420,7 +421,7 @@ export default {
|
|||||||
const fullname = path.join(themeDir, filename)
|
const fullname = path.join(themeDir, filename)
|
||||||
if (/.+\.css$/i.test(filename) && isFile(fullname)) {
|
if (/.+\.css$/i.test(filename) && isFile(fullname)) {
|
||||||
try {
|
try {
|
||||||
const content = await fs.readFile(fullname, 'utf8')
|
const content = await fsPromises.readFile(fullname, 'utf8')
|
||||||
|
|
||||||
// Match comment with theme name in first line only.
|
// Match comment with theme name in first line only.
|
||||||
const match = content.match(/^(?:\/\*+[ \t]*([A-z0-9 -]+)[ \t]*(?:\*+\/|[\n\r])?)/)
|
const match = content.match(/^(?:\/\*+[ \t]*([A-z0-9 -]+)[ \t]*(?:\*+\/|[\n\r])?)/)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import template from './index.html'
|
import template from './index.html'
|
||||||
import { getUniqueId } from '../../util'
|
import { getUniqueId } from '../../util'
|
||||||
|
import { sanitize, EXPORT_DOMPURIFY_CONFIG } from '../../util/dompurify'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
|
|
||||||
const INON_HASH = {
|
const INON_HASH = {
|
||||||
@ -15,6 +16,13 @@ const TYPE_HASH = {
|
|||||||
info: 'mt-info'
|
info: 'mt-info'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fillTemplate = (type, title, message) => {
|
||||||
|
return template
|
||||||
|
.replace(/\{\{icon\}\}/, INON_HASH[type])
|
||||||
|
.replace(/\{\{title\}\}/, sanitize(title, EXPORT_DOMPURIFY_CONFIG))
|
||||||
|
.replace(/\{\{message\}\}/, sanitize(message, EXPORT_DOMPURIFY_CONFIG))
|
||||||
|
}
|
||||||
|
|
||||||
const notification = {
|
const notification = {
|
||||||
name: 'notify',
|
name: 'notify',
|
||||||
noticeCache: {},
|
noticeCache: {},
|
||||||
@ -36,10 +44,7 @@ const notification = {
|
|||||||
const id = getUniqueId()
|
const id = getUniqueId()
|
||||||
|
|
||||||
const fragment = document.createElement('div')
|
const fragment = document.createElement('div')
|
||||||
fragment.innerHTML = template
|
fragment.innerHTML = fillTemplate(type, title, message)
|
||||||
.replace(/\{\{icon\}\}/, INON_HASH[type])
|
|
||||||
.replace(/\{\{title\}\}/, title)
|
|
||||||
.replace(/\{\{message\}\}/, message)
|
|
||||||
|
|
||||||
const noticeContainer = fragment.querySelector('.mt-notification')
|
const noticeContainer = fragment.querySelector('.mt-notification')
|
||||||
const bgNotice = noticeContainer.querySelector('.notice-bg')
|
const bgNotice = noticeContainer.querySelector('.notice-bg')
|
||||||
|
32
src/renderer/util/dompurify.js
Normal file
32
src/renderer/util/dompurify.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import runSanitize from 'muya/lib/utils/dompurify'
|
||||||
|
|
||||||
|
export const PREVIEW_DOMPURIFY_CONFIG = Object.freeze({
|
||||||
|
FORBID_ATTR: ['style', 'contenteditable'],
|
||||||
|
ALLOW_DATA_ATTR: false,
|
||||||
|
USE_PROFILES: {
|
||||||
|
html: true,
|
||||||
|
svg: true,
|
||||||
|
svgFilters: true,
|
||||||
|
mathMl: false
|
||||||
|
},
|
||||||
|
RETURN_TRUSTED_TYPE: false
|
||||||
|
})
|
||||||
|
|
||||||
|
export const EXPORT_DOMPURIFY_CONFIG = Object.freeze({
|
||||||
|
FORBID_ATTR: ['contenteditable'],
|
||||||
|
ALLOW_DATA_ATTR: false,
|
||||||
|
ADD_ATTR: ['data-align'],
|
||||||
|
USE_PROFILES: {
|
||||||
|
html: true,
|
||||||
|
svg: true,
|
||||||
|
svgFilters: true,
|
||||||
|
mathMl: false
|
||||||
|
},
|
||||||
|
RETURN_TRUSTED_TYPE: false,
|
||||||
|
// Allow "file" protocol to export images on Windows (#1997).
|
||||||
|
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|file):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
|
||||||
|
})
|
||||||
|
|
||||||
|
export const sanitize = (html, purifyOptions) => {
|
||||||
|
return runSanitize(html, purifyOptions)
|
||||||
|
}
|
@ -1,12 +1,12 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import Slugger from 'muya/lib/parser/marked/slugger'
|
import Slugger from 'muya/lib/parser/marked/slugger'
|
||||||
import sanitize from 'muya/lib/utils/dompurify'
|
|
||||||
import { isFile } from 'common/filesystem'
|
import { isFile } from 'common/filesystem'
|
||||||
import { escapeHtml, unescapeHtml } from 'muya/lib/utils'
|
import { escapeHtml, unescapeHtml } from 'muya/lib/utils'
|
||||||
import academicTheme from '@/assets/themes/export/academic.theme.css'
|
import academicTheme from '@/assets/themes/export/academic.theme.css'
|
||||||
import liberTheme from '@/assets/themes/export/liber.theme.css'
|
import liberTheme from '@/assets/themes/export/liber.theme.css'
|
||||||
import { cloneObj } from '../util'
|
import { cloneObj } from '../util'
|
||||||
|
import { sanitize, EXPORT_DOMPURIFY_CONFIG } from '../util/dompurify'
|
||||||
|
|
||||||
export const getCssForOptions = options => {
|
export const getCssForOptions = options => {
|
||||||
const {
|
const {
|
||||||
@ -130,17 +130,6 @@ export const getHtmlToc = (toc, options = {}) => {
|
|||||||
return sanitize(html, EXPORT_DOMPURIFY_CONFIG)
|
return sanitize(html, EXPORT_DOMPURIFY_CONFIG)
|
||||||
}
|
}
|
||||||
|
|
||||||
const EXPORT_DOMPURIFY_CONFIG = {
|
|
||||||
FORBID_ATTR: ['contenteditable'],
|
|
||||||
ALLOW_DATA_ATTR: false,
|
|
||||||
USE_PROFILES: {
|
|
||||||
html: true,
|
|
||||||
svg: true,
|
|
||||||
svgFilters: true,
|
|
||||||
mathMl: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't use "Noto Color Emoji" because it will result in PDF files with multiple MB and weird looking emojis.
|
// Don't use "Noto Color Emoji" because it will result in PDF files with multiple MB and weird looking emojis.
|
||||||
const FALLBACK_FONT_FAMILIES = '"Open Sans","Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"'
|
const FALLBACK_FONT_FAMILIES = '"Open Sans","Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"'
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user