mirror of
https://github.com/marktext/marktext.git
synced 2025-05-03 04:39:47 +08:00
Prism replace codemirror (#516)
* feat: basic use of code block by prism * opti: remove codemirror from muya * feat: add highlight to math and frontmatter * feat: import and export in math block, html block, frontmatter, code block * update: paragraph ctrl * feat: copy and paste in new math block and html block * feat: update code block style in dark theme * feat: search and replace in code block * fix: update menu item status when selection changed * opti: optimization of updateCtrl divide it into clickCtrl and inputCtrl * opti: search and replace in code block when no lang selected * opti: copy paste in code block * feat: insert paragraph before or after code block * opti: change emoji.js to emoji.json * feat: auto indent in code block * opti: auto indent in code block * opti: remove the use of snabbdom-virtualize * fix: do not show format float box in code block * opti: emoji picker * update: delete some unused codes * update: electron * use a temp prismjs2 instead of prismjs
This commit is contained in:
parent
4c9e6f643b
commit
39e1ea8081
@ -45,7 +45,7 @@ const rendererConfig = {
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /(katex|github\-markdown|highlight\.js\/styles\/default)\.css$/,
|
||||
test: /(katex|github\-markdown|prism[\-a-z]*)\.css$/,
|
||||
use: [
|
||||
'to-string-loader',
|
||||
'css-loader'
|
||||
@ -53,7 +53,7 @@ const rendererConfig = {
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
exclude: /(katex|github\-markdown|highlight\.js\/styles\/default)\.css$/,
|
||||
exclude: /(katex|github\-markdown|prism[\-a-z]*)\.css$/,
|
||||
use: [
|
||||
proMode ? MiniCssExtractPlugin.loader : 'style-loader',
|
||||
{ loader: 'css-loader', options: { importLoader: 1 } },
|
||||
|
@ -34,7 +34,7 @@ const webConfig = {
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /(katex|github\-markdown|highlight\.js\/styles\/default)\.css$/,
|
||||
test: /(katex|github\-markdown|prism[\-a-z]*)\.css$/,
|
||||
use: [
|
||||
'to-string-loader',
|
||||
'css-loader'
|
||||
@ -42,7 +42,7 @@ const webConfig = {
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
exclude: /(katex|github\-markdown|highlight\.js\/styles\/default)\.css$/,
|
||||
exclude: /(katex|github\-markdown|prism[\-a-z]*)\.css$/,
|
||||
use: [
|
||||
proMode ? MiniCssExtractPlugin.loader : 'style-loader',
|
||||
{ loader: 'css-loader', options: { importLoader: 1 } },
|
||||
|
@ -93,7 +93,7 @@
|
||||
<span>:es:</span>
|
||||
</a>
|
||||
<a href="https://github.com/marktext/marktext/blob/master/doc/i18n/pt.md#readme">
|
||||
<span>Portuguese</span>
|
||||
<span>:portugal:</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
88
package-lock.json
generated
88
package-lock.json
generated
@ -192,6 +192,12 @@
|
||||
"integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "8.10.36",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.36.tgz",
|
||||
"integrity": "sha512-SL6KhfM7PTqiFmbCW3eVNwVBZ+88Mrzbuvn9olPsfv43mbiWaFY+nRcz/TGGku0/lc2FepdMbImdMY1JrQ+zbw==",
|
||||
"dev": true
|
||||
},
|
||||
"@vue/component-compiler-utils": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-2.2.0.tgz",
|
||||
@ -2860,6 +2866,17 @@
|
||||
"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
|
||||
"dev": true
|
||||
},
|
||||
"clipboard": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.1.tgz",
|
||||
"integrity": "sha512-7yhQBmtN+uYZmfRjjVjKa0dZdWuabzpSKGtyQZN+9C8xlC788SSJjOHWh7tzurfwTqTD5UDYAhIv5fRJg3sHjQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"good-listener": "^1.2.2",
|
||||
"select": "^1.1.2",
|
||||
"tiny-emitter": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
|
||||
@ -3841,6 +3858,12 @@
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
||||
},
|
||||
"delegate": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
|
||||
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
|
||||
"optional": true
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
@ -4167,22 +4190,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"electron": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-3.0.3.tgz",
|
||||
"integrity": "sha512-5ypkMO368UbWd1e0ZwKaflYLXSHSw2wAvC5/yApv03pX/KV3uD/2/qF7rW841H9I3QPmS03YZ6UZmlQV/fNczw==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-3.0.5.tgz",
|
||||
"integrity": "sha512-rcHNbhSGfj80Av5p06LgIUxN8wQbrdx8yblikJamDezqxe0B11CJSEJuidz6TJoCRDZuWHt+P5xMAEhp92ZUcA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "^8.0.24",
|
||||
"electron-download": "^4.1.0",
|
||||
"extract-zip": "^1.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "8.10.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.34.tgz",
|
||||
"integrity": "sha512-alypNiaAEd0RBGXoWehJ2gchPYCITmw4CYBoB5nDlji8l8on7FsklfdfIs4DDmgpKLSX3OF3ha6SV+0W7cTzUA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"electron-builder": {
|
||||
@ -6514,6 +6529,15 @@
|
||||
"minimatch": "~3.0.2"
|
||||
}
|
||||
},
|
||||
"good-listener": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
|
||||
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"delegate": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"got": {
|
||||
"version": "6.7.1",
|
||||
"resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz",
|
||||
@ -6819,7 +6843,8 @@
|
||||
"highlight.js": {
|
||||
"version": "9.12.0",
|
||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.12.0.tgz",
|
||||
"integrity": "sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4="
|
||||
"integrity": "sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4=",
|
||||
"dev": true
|
||||
},
|
||||
"hmac-drbg": {
|
||||
"version": "1.0.1",
|
||||
@ -6895,14 +6920,6 @@
|
||||
"uglify-js": "3.3.x"
|
||||
}
|
||||
},
|
||||
"html-parse-stringify2": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz",
|
||||
"integrity": "sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o=",
|
||||
"requires": {
|
||||
"void-elements": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"html-tags": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz",
|
||||
@ -15100,6 +15117,14 @@
|
||||
"utila": "~0.4"
|
||||
}
|
||||
},
|
||||
"prismjs2": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs2/-/prismjs2-1.15.0.tgz",
|
||||
"integrity": "sha512-/DT77JC3sLzWSpD4WOpIanoMuirt1KkqeFKAmTO4budSjrMwTvQgeeNECPuUm0uYQbH8fDe39s6QMFs6VR1GhQ==",
|
||||
"requires": {
|
||||
"clipboard": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"private": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
|
||||
@ -16052,6 +16077,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
|
||||
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
|
||||
"optional": true
|
||||
},
|
||||
"select-hose": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||
@ -16324,14 +16355,6 @@
|
||||
"parse-sel": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"snabbdom-virtualize": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/snabbdom-virtualize/-/snabbdom-virtualize-0.7.0.tgz",
|
||||
"integrity": "sha1-MfaDM4tmRXve2MHiLN2O1DjCo4c=",
|
||||
"requires": {
|
||||
"html-parse-stringify2": "^2"
|
||||
}
|
||||
},
|
||||
"snake-case": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/snake-case/-/snake-case-2.1.0.tgz",
|
||||
@ -17614,6 +17637,12 @@
|
||||
"setimmediate": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"tiny-emitter": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz",
|
||||
"integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==",
|
||||
"optional": true
|
||||
},
|
||||
"title-case": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz",
|
||||
@ -18317,7 +18346,8 @@
|
||||
"void-elements": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
|
||||
"integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w="
|
||||
"integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=",
|
||||
"dev": true
|
||||
},
|
||||
"vue": {
|
||||
"version": "2.5.17",
|
||||
|
@ -132,14 +132,13 @@
|
||||
"fs-extra": "^7.0.0",
|
||||
"fuzzaldrin": "^2.1.0",
|
||||
"github-markdown-css": "^2.10.0",
|
||||
"highlight.js": "^9.12.0",
|
||||
"html-tags": "^2.0.0",
|
||||
"katex": "^0.10.0-rc.1",
|
||||
"markdown-toc": "^1.2.0",
|
||||
"popper.js": "^1.14.4",
|
||||
"prismjs2": "^1.15.0",
|
||||
"snabbdom": "^0.7.2",
|
||||
"snabbdom-to-html": "^5.1.1",
|
||||
"snabbdom-virtualize": "^0.7.0",
|
||||
"turndown": "^5.0.1",
|
||||
"turndown-plugin-gfm": "^1.0.2",
|
||||
"vue": "^2.5.17",
|
||||
@ -164,7 +163,7 @@
|
||||
"css-loader": "^1.0.0",
|
||||
"del": "^3.0.0",
|
||||
"devtron": "^1.4.0",
|
||||
"electron": "^3.0.3",
|
||||
"electron": "^3.0.5",
|
||||
"electron-builder": "^20.28.4",
|
||||
"electron-debug": "^2.0.0",
|
||||
"electron-devtools-installer": "^2.2.4",
|
||||
|
@ -66,10 +66,10 @@ const setCheckedMenuItem = affiliation => {
|
||||
} else if (b.type === 'pre' && b.functionType) {
|
||||
if (b.functionType === 'frontmatter') {
|
||||
return item.id === 'frontMatterMenuItem'
|
||||
} else if (b.functionType === 'code') {
|
||||
} else if (/code$/.test(b.functionType)) {
|
||||
return item.id === 'codeFencesMenuItem'
|
||||
} else if (b.functionType === 'html') {
|
||||
return false
|
||||
return item.id === 'htmlBlockMenuItem'
|
||||
} else if (b.functionType === 'multiplemath') {
|
||||
return item.id === 'mathBlockMenuItem'
|
||||
}
|
||||
@ -101,12 +101,8 @@ ipcMain.on('AGANI::selection-change', (e, { start, end, affiliation }) => {
|
||||
|
||||
if (
|
||||
(/th|td/.test(start.type) && /th|td/.test(end.type)) ||
|
||||
(start.type === 'span' && start.block.functionType === 'frontmatter') ||
|
||||
(end.type === 'span' && end.block.functionType === 'frontmatter') ||
|
||||
(start.type === 'span' && start.block.functionType === 'multiplemath') ||
|
||||
(end.type === 'span' && end.block.functionType === 'multiplemath') ||
|
||||
(start.type === 'pre' && start.block.functionType === 'html') ||
|
||||
(end.type === 'pre' && end.block.functionType === 'html')
|
||||
(start.type === 'span' && start.block.functionType === 'codeLine') ||
|
||||
(end.type === 'span' && end.block.functionType === 'codeLine')
|
||||
) {
|
||||
setParagraphMenuItemStatus(false)
|
||||
} else if (start.key !== end.key) {
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* Common CSS use by both light and dark themes */
|
||||
:root {
|
||||
--brandColor: #5b3cc4;
|
||||
--successColor: rgb(23, 201, 100);
|
||||
@ -119,6 +120,9 @@ div.ag-function-html pre.ag-html-block {
|
||||
opacity: 0;
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
div.ag-function-html.ag-active pre.ag-html-block,
|
||||
@ -431,12 +435,17 @@ pre.ag-front-matter {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
span.ag-front-matter-line:first-of-type:empty::after {
|
||||
pre.ag-front-matter span.ag-code-line:first-of-type:empty::after {
|
||||
content: 'Input YAML Front Matter...';
|
||||
color: var(--placeholerColor);
|
||||
}
|
||||
|
||||
span.ag-multiple-math-line:first-of-type:empty::after {
|
||||
pre[data-role$='code'] span.ag-language-input:empty::after {
|
||||
content: 'Input Language...';
|
||||
color: var(--placeholerColor);
|
||||
}
|
||||
|
||||
pre.ag-multiple-math span.ag-code-line:first-of-type:empty::after {
|
||||
content: 'Input Mathematical Formula...';
|
||||
color: var(--placeholerColor);
|
||||
}
|
||||
@ -444,7 +453,8 @@ span.ag-multiple-math-line:first-of-type:empty::after {
|
||||
figure,
|
||||
pre.ag-html-block,
|
||||
div.ag-function-html,
|
||||
pre.ag-code-block,
|
||||
pre.ag-fence-code,
|
||||
pre.ag-indent-code,
|
||||
li.ag-list-item > p.ag-paragraph {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
@ -460,9 +470,14 @@ li.ag-list-item > p.ag-paragraph > span {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
pre.ag-code-block {
|
||||
pre.ag-fence-code,
|
||||
pre.ag-indent-code {
|
||||
margin: 1rem 0;
|
||||
padding: 0 .5rem;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
pre.ag-active.ag-front-matter::before,
|
||||
@ -475,15 +490,19 @@ pre.ag-active.ag-multiple-math::after {
|
||||
content: '$$';
|
||||
}
|
||||
|
||||
pre.ag-active.ag-code-block::before,
|
||||
pre.ag-active.ag-code-block::after {
|
||||
pre.ag-active.ag-fence-code::before,
|
||||
pre.ag-active.ag-indent-code::after,
|
||||
pre.ag-active.ag-fence-code::after,
|
||||
pre.ag-active.ag-indent-code::before {
|
||||
content: '```';
|
||||
}
|
||||
|
||||
pre.ag-active.ag-front-matter::before,
|
||||
pre.ag-active.ag-front-matter::after,
|
||||
pre.ag-active.ag-code-block::before,
|
||||
pre.ag-active.ag-code-block::after,
|
||||
pre.ag-active.ag-fence-code::before,
|
||||
pre.ag-active.ag-fence-code::after,
|
||||
pre.ag-active.ag-indent-code::before,
|
||||
pre.ag-active.ag-indent-code::after,
|
||||
pre.ag-active.ag-multiple-math::before,
|
||||
pre.ag-active.ag-multiple-math::after {
|
||||
color: var(--regularColor);
|
||||
@ -495,13 +514,15 @@ pre.ag-active.ag-multiple-math::after {
|
||||
|
||||
pre.ag-active.ag-front-matter::before,
|
||||
pre.ag-active.ag-multiple-math::before,
|
||||
pre.ag-active.ag-code-block::before {
|
||||
pre.ag-active.ag-indent-code::before,
|
||||
pre.ag-active.ag-fence-code::before {
|
||||
top: -20px;
|
||||
}
|
||||
|
||||
pre.ag-active.ag-front-matter::after,
|
||||
pre.ag-active.ag-multiple-math::after,
|
||||
pre.ag-active.ag-code-block::after {
|
||||
pre.ag-active.ag-fence-code::after,
|
||||
pre.ag-active.ag-indent-code::after {
|
||||
bottom: -23px;
|
||||
}
|
||||
|
||||
@ -520,7 +541,7 @@ figure.ag-active div.ag-math-preview {
|
||||
top: calc(100% + 8px);
|
||||
left: 50%;
|
||||
width: auto;
|
||||
z-index: 1;
|
||||
z-index: 10000;
|
||||
transform: translateX(-50%);
|
||||
padding: .5rem;
|
||||
background: #fff;
|
||||
@ -534,10 +555,6 @@ div.ag-html-preview {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
pre .CodeMirror {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
@ -595,12 +612,12 @@ span.ag-emoji-marked-text {
|
||||
}
|
||||
|
||||
.ag-language-input {
|
||||
outline: none;
|
||||
padding: 0 1rem;
|
||||
display: none;
|
||||
min-width: 80px;
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 30px;
|
||||
top: -23px;
|
||||
left: 20px;
|
||||
font-size: 14px;
|
||||
font-family: monospace;
|
||||
font-weight: 600;
|
||||
@ -618,6 +635,13 @@ pre.ag-active .ag-language-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ag-language {
|
||||
color: var(--activeColor);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
span.ag-image-marked-text, span.ag-link-in-bracket, span.ag-link-in-bracket .ag-backlash {
|
||||
color: var(--regularColor);
|
||||
font-size: 16px;
|
||||
@ -625,11 +649,6 @@ span.ag-image-marked-text, span.ag-link-in-bracket, span.ag-link-in-bracket .ag-
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.ag-language {
|
||||
color: var(--dangerColor);
|
||||
text-decoration: none;
|
||||
font-family: monospace;
|
||||
}
|
||||
.ag-backlash {
|
||||
text-decoration: none;
|
||||
color: rgb(51, 51, 51);
|
||||
|
@ -1,18 +0,0 @@
|
||||
/**
|
||||
* check edit language
|
||||
*/
|
||||
export const checkEditLanguage = (paragraph, selectionState) => {
|
||||
const text = paragraph.textContent
|
||||
const { start } = selectionState
|
||||
const token = text.match(/(^`{3,})([^`]+)/)
|
||||
|
||||
if (token) {
|
||||
const len = token[1].length
|
||||
const lang = token[2].trim()
|
||||
if (start < len) return false
|
||||
if (!lang) return false
|
||||
return lang
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import voidHtmlTags from 'html-tags/void'
|
||||
// Electron 2.0.2 not support yet! So give a default value 4
|
||||
export const DEVICE_MEMORY = navigator.deviceMemory || 4 // Get the divice memory number(Chrome >= 63)
|
||||
export const UNDO_DEPTH = DEVICE_MEMORY >= 4 ? 100 : 50
|
||||
export const HAS_TEXT_BLOCK_REG = /^(h\d|span|th|td|hr|pre)/i
|
||||
export const HAS_TEXT_BLOCK_REG = /^(h\d|span|th|td|hr)/i
|
||||
export const VOID_HTML_TAGS = voidHtmlTags
|
||||
export const HTML_TAGS = htmlTags
|
||||
// TYPE1 ~ TYPE7 according to https://github.github.com/gfm/#html-blocks
|
||||
@ -68,21 +68,15 @@ export const CLASS_OR_ID = genUpper2LowerKeyHash([
|
||||
'AG_LINE',
|
||||
'AG_ACTIVE',
|
||||
'AG_EDITOR_ID',
|
||||
'AG_FLOAT_BOX_ID',
|
||||
'AG_FUNCTION_HTML',
|
||||
'AG_FLOAT_BOX',
|
||||
'AG_SHOW_FLOAT_BOX',
|
||||
'AG_FLOAT_ITEM', // LI element
|
||||
'AG_FLOAT_ITEM_ACTIVE',
|
||||
'AG_FLOAT_ITEM_ICON', // icon wrapper in li
|
||||
'AG_EMOJI_MARKED_TEXT',
|
||||
'AG_CODE_BLOCK',
|
||||
'AG_FENCE_CODE',
|
||||
'AG_INDENT_CODE',
|
||||
'AG_HTML_BLOCK',
|
||||
'AG_HTML_ESCAPE',
|
||||
'AG_FRONT_MATTER',
|
||||
'AG_FRONT_MATTER_LINE',
|
||||
'AG_MULTIPLE_MATH_LINE',
|
||||
'AG_CODEMIRROR_BLOCK',
|
||||
'AG_CODE_LINE',
|
||||
'AG_CODE_LINE_ADD',
|
||||
'AG_CODE_LINE_MINUS',
|
||||
'AG_SHOW_PREVIEW',
|
||||
'AG_HTML_PREVIEW',
|
||||
'AG_LANGUAGE',
|
||||
@ -134,20 +128,6 @@ export const CLASS_OR_ID = genUpper2LowerKeyHash([
|
||||
'AG_REFERENCE_LINK'
|
||||
])
|
||||
|
||||
export const codeMirrorConfig = {
|
||||
// theme: 'railscasts',
|
||||
lineWrapping: true,
|
||||
autoCloseBrackets: true,
|
||||
lineWiseCopyCut: false,
|
||||
autoCloseTags: true,
|
||||
autofocus: true,
|
||||
tabSize: 2,
|
||||
extraKeys: {
|
||||
'Cmd-Z': false,
|
||||
'Cmd-Y': false
|
||||
}
|
||||
}
|
||||
|
||||
export const DAED_REMOVE_SELECTOR = new Set([
|
||||
'.ag-image-marked-text::before',
|
||||
'.ag-image-marked-text.ag-image-fail::before',
|
||||
@ -209,7 +189,8 @@ export const HTML_TOOLS = [{
|
||||
export const LINE_BREAK = '\n'
|
||||
|
||||
export const PREVIEW_DOMPURIFY_CONFIG = {
|
||||
FORBID_ATTR: ['style', 'class', 'contenteditable'],
|
||||
// do not forbit `class` because `code` element use class to present language
|
||||
FORBID_ATTR: ['style', 'contenteditable'],
|
||||
ALLOW_DATA_ATTR: false,
|
||||
USE_PROFILES: {
|
||||
html: true,
|
||||
|
@ -1,12 +1,4 @@
|
||||
import { EVENT_KEYS, CLASS_OR_ID } from '../config'
|
||||
import {
|
||||
isCursorAtFirstLine,
|
||||
isCursorAtLastLine,
|
||||
isCursorAtBegin,
|
||||
isCursorAtEnd,
|
||||
getBeginPosition,
|
||||
getEndPosition
|
||||
} from '../codeMirror'
|
||||
import { findNearestParagraph } from '../selection/dom'
|
||||
import selection from '../selection'
|
||||
|
||||
@ -59,7 +51,6 @@ const arrowCtrl = ContentState => {
|
||||
const preBlock = this.findPreBlockInLocation(block)
|
||||
const nextBlock = this.findNextBlockInLocation(block)
|
||||
|
||||
const { left, right } = selection.getCaretOffsets(paragraph)
|
||||
const { start, end } = selection.getCursorRange()
|
||||
const { topOffset, bottomOffset } = selection.getCursorYOffset(paragraph)
|
||||
|
||||
@ -89,60 +80,6 @@ const arrowCtrl = ContentState => {
|
||||
}
|
||||
}
|
||||
|
||||
// handle `html` and `code` block when press arrow key
|
||||
if (block.type === 'pre' && /code|html/.test(block.functionType)) {
|
||||
// handle cursor in code block. the case at firstline or lastline.
|
||||
const cm = this.codeBlocks.get(id)
|
||||
const anchorBlock = block.functionType === 'html' ? this.getParent(this.getParent(block)) : block
|
||||
let activeBlock
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
switch (event.key) {
|
||||
case EVENT_KEYS.ArrowLeft: // fallthrough
|
||||
case EVENT_KEYS.ArrowUp:
|
||||
if (
|
||||
(event.key === EVENT_KEYS.ArrowUp && isCursorAtFirstLine(cm) && preBlock) ||
|
||||
(event.key === EVENT_KEYS.ArrowLeft && isCursorAtBegin(cm) && preBlock)
|
||||
) {
|
||||
activeBlock = preBlock
|
||||
}
|
||||
break
|
||||
case EVENT_KEYS.ArrowRight: // fallthrough
|
||||
case EVENT_KEYS.ArrowDown:
|
||||
if (
|
||||
(event.key === EVENT_KEYS.ArrowDown && isCursorAtLastLine(cm)) ||
|
||||
(event.key === EVENT_KEYS.ArrowRight && isCursorAtEnd(cm))
|
||||
) {
|
||||
if (nextBlock) {
|
||||
activeBlock = nextBlock
|
||||
} else {
|
||||
activeBlock = this.createBlockP()
|
||||
this.insertAfter(activeBlock, anchorBlock)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (activeBlock) {
|
||||
const cursorBlock = activeBlock.type === 'p' ? activeBlock.children[0] : activeBlock
|
||||
const offset = cursorBlock.text.length
|
||||
const key = cursorBlock.key
|
||||
this.cursor = {
|
||||
start: {
|
||||
key,
|
||||
offset
|
||||
},
|
||||
end: {
|
||||
key,
|
||||
offset
|
||||
}
|
||||
}
|
||||
|
||||
return this.partialRender()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (/th|td/.test(block.type)) {
|
||||
let activeBlock
|
||||
const cellInNextRow = this.findNextRowCell(block)
|
||||
@ -193,39 +130,6 @@ const arrowCtrl = ContentState => {
|
||||
}
|
||||
|
||||
if (
|
||||
(preBlock && preBlock.type === 'pre' && /code|html/.test(preBlock.functionType) && event.key === EVENT_KEYS.ArrowUp) ||
|
||||
(preBlock && preBlock.type === 'pre' && /code|html/.test(preBlock.functionType) && event.key === EVENT_KEYS.ArrowLeft && left === 0)
|
||||
) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
const key = preBlock.key
|
||||
const offset = 0
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
}
|
||||
|
||||
const cm = this.codeBlocks.get(preBlock.key)
|
||||
preBlock.selection = getEndPosition(cm)
|
||||
|
||||
return this.partialRender()
|
||||
} else if (
|
||||
(nextBlock && nextBlock.type === 'pre' && /code|html/.test(nextBlock.functionType) && event.key === EVENT_KEYS.ArrowDown) ||
|
||||
(nextBlock && nextBlock.type === 'pre' && /code|html/.test(nextBlock.functionType) && event.key === EVENT_KEYS.ArrowRight && right === 0)
|
||||
) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
const key = nextBlock.key
|
||||
const offset = 0
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
}
|
||||
|
||||
nextBlock.selection = getBeginPosition()
|
||||
|
||||
return this.partialRender()
|
||||
} else if (
|
||||
(event.key === EVENT_KEYS.ArrowUp) ||
|
||||
(event.key === EVENT_KEYS.ArrowLeft && start.offset === 0)
|
||||
) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import selection from '../selection'
|
||||
import { findNearestParagraph, findOutMostParagraph } from '../selection/dom'
|
||||
import { isCursorAtBegin, onlyHaveOneLine, getEndPosition } from '../codeMirror'
|
||||
|
||||
const backspaceCtrl = ContentState => {
|
||||
ContentState.prototype.checkBackspaceCase = function () {
|
||||
@ -128,6 +127,10 @@ const backspaceCtrl = ContentState => {
|
||||
return this.render()
|
||||
}
|
||||
|
||||
if (startBlock.functionType === 'languageInput' && start.offset === 0) {
|
||||
return event.preventDefault()
|
||||
}
|
||||
|
||||
// If select multiple paragraph or multiple characters in one paragraph, just let
|
||||
// updateCtrl to handle this case.
|
||||
if (start.key !== end.key || start.offset !== end.offset) {
|
||||
@ -151,19 +154,39 @@ const backspaceCtrl = ContentState => {
|
||||
return tHeadHasContent || tBodyHasContent
|
||||
}
|
||||
|
||||
if (block.type === 'pre' && /code|html/.test(block.functionType)) {
|
||||
const cm = this.codeBlocks.get(id)
|
||||
// if event.preventDefault(), you can not use backspace in language input.
|
||||
if (isCursorAtBegin(cm) && onlyHaveOneLine(cm)) {
|
||||
const anchorBlock = block.functionType === 'html' ? this.getParent(this.getParent(block)) : block
|
||||
event.preventDefault()
|
||||
const value = cm.getValue()
|
||||
const newBlock = this.createBlockP(value)
|
||||
this.insertBefore(newBlock, anchorBlock)
|
||||
this.removeBlock(anchorBlock)
|
||||
this.codeBlocks.delete(id)
|
||||
const key = newBlock.children[0].key
|
||||
if (
|
||||
block.type === 'span' &&
|
||||
block.functionType === 'codeLine' &&
|
||||
left === 0 &&
|
||||
!block.preSibling
|
||||
) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if (
|
||||
!block.nextSibling
|
||||
) {
|
||||
const preBlock = this.getParent(parent)
|
||||
const pBlock = this.createBlock('p')
|
||||
const lineBlock = this.createBlock('span', block.text)
|
||||
const key = lineBlock.key
|
||||
const offset = 0
|
||||
this.appendChild(pBlock, lineBlock)
|
||||
let referenceBlock = null
|
||||
switch (preBlock.functionType) {
|
||||
case 'fencecode':
|
||||
case 'indentcode':
|
||||
case 'frontmatter':
|
||||
referenceBlock = preBlock
|
||||
break
|
||||
case 'multiplemath':
|
||||
referenceBlock = this.getParent(preBlock)
|
||||
break
|
||||
case 'html':
|
||||
referenceBlock = this.getParent(this.getParent(preBlock))
|
||||
break
|
||||
}
|
||||
this.insertBefore(pBlock, referenceBlock)
|
||||
this.removeBlock(referenceBlock)
|
||||
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
@ -171,31 +194,6 @@ const backspaceCtrl = ContentState => {
|
||||
}
|
||||
this.partialRender()
|
||||
}
|
||||
} else if (
|
||||
block.type === 'span' && /frontmatter|multiplemath/.test(block.functionType) &&
|
||||
left === 0 && !block.preSibling
|
||||
) {
|
||||
const isMathLine = block.functionType === 'multiplemath'
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
const { key } = block
|
||||
const offset = 0
|
||||
const pBlock = this.createBlock('p')
|
||||
for (const line of parent.children) {
|
||||
delete line.functionType
|
||||
this.appendChild(pBlock, line)
|
||||
}
|
||||
if (isMathLine) {
|
||||
parent = this.getParent(parent)
|
||||
}
|
||||
this.insertBefore(pBlock, parent)
|
||||
this.removeBlock(parent)
|
||||
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
}
|
||||
this.partialRender()
|
||||
} else if (left === 0 && /th|td/.test(block.type)) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
@ -298,19 +296,7 @@ const backspaceCtrl = ContentState => {
|
||||
const { text } = block
|
||||
const key = preBlock.key
|
||||
const offset = preBlock.text.length
|
||||
if (preBlock.type === 'pre' && /code|html/.test(preBlock.functionType)) {
|
||||
const cm = this.codeBlocks.get(key)
|
||||
const value = cm.getValue() + text
|
||||
cm.setValue(value)
|
||||
const { line, ch } = getEndPosition(cm).anchor
|
||||
|
||||
preBlock.selection = {
|
||||
anchor: { line, ch: ch - text.length },
|
||||
head: { line, ch: ch - text.length }
|
||||
}
|
||||
} else {
|
||||
preBlock.text += text
|
||||
}
|
||||
preBlock.text += text
|
||||
// If block is a line block and its parent paragraph only has one text line,
|
||||
// also need to remove the paragrah
|
||||
if (this.isOnlyChild(block) && block.type === 'span') {
|
||||
|
@ -1,6 +1,59 @@
|
||||
import selection from '../selection'
|
||||
import { HAS_TEXT_BLOCK_REG } from '../config'
|
||||
|
||||
const clickCtrl = ContentState => {
|
||||
ContentState.prototype.clickHandler = function (event) {
|
||||
// todo
|
||||
const { eventCenter } = this.muya
|
||||
const { start, end } = selection.getCursorRange()
|
||||
const block = this.getBlock(start.key)
|
||||
let needRender = false
|
||||
// is show format float box?
|
||||
if (
|
||||
start.key === end.key &&
|
||||
start.offset !== end.offset &&
|
||||
HAS_TEXT_BLOCK_REG.test(block.type) &&
|
||||
block.functionType !== 'codeLine'
|
||||
) {
|
||||
const reference = this.getPositionReference()
|
||||
const { formats } = this.selectionFormats()
|
||||
eventCenter.dispatch('muya-format-picker', { reference, formats })
|
||||
}
|
||||
// bugfix: #67 problem 1
|
||||
if (block && block.icon) return event.preventDefault()
|
||||
// bugfix: figure block click
|
||||
if (event.type === 'click' && block.type === 'figure' && block.functionType === 'table') {
|
||||
// first cell in thead
|
||||
const cursorBlock = block.children[1].children[0].children[0].children[0]
|
||||
const offset = cursorBlock.text.length
|
||||
const key = cursorBlock.key
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
}
|
||||
needRender = true
|
||||
}
|
||||
|
||||
// update '```xxx' to code block when you click other place or use press arrow key.
|
||||
if (block && start.key !== this.cursor.start.key) {
|
||||
const oldBlock = this.getBlock(this.cursor.start.key)
|
||||
if (oldBlock) {
|
||||
needRender = needRender || this.codeBlockUpdate(oldBlock)
|
||||
}
|
||||
}
|
||||
|
||||
// change active status when paragraph changed
|
||||
if (
|
||||
start.key !== this.cursor.start.key ||
|
||||
end.key !== this.cursor.end.key
|
||||
) {
|
||||
needRender = true
|
||||
}
|
||||
|
||||
const needMarkedUpdate = this.checkNeedRender(this.cursor) || this.checkNeedRender({ start, end })
|
||||
this.cursor = { start, end }
|
||||
if (needMarkedUpdate || needRender) {
|
||||
return this.partialRender()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,44 +1,32 @@
|
||||
import codeMirror, { setMode, setCursorAtLastLine } from '../codeMirror'
|
||||
import { createInputInCodeBlock } from '../utils/domManipulate'
|
||||
import { sanitize, getParagraphReference } from '../utils'
|
||||
import { codeMirrorConfig, BLOCK_TYPE7, PREVIEW_DOMPURIFY_CONFIG, CLASS_OR_ID } from '../config'
|
||||
import { loadLanguage } from '../prism/index'
|
||||
|
||||
const CODE_UPDATE_REP = /^`{3,}(.*)/
|
||||
|
||||
const beautifyHtml = html => {
|
||||
const HTML_REG = /^<([a-zA-Z\d-]+)(?=\s|>).*?>/
|
||||
const HTML_NEWLINE_REG = /^<([a-zA-Z\d-]+)(?=\s|>).*?>\n/
|
||||
const match = HTML_REG.exec(html)
|
||||
const tag = match ? match[1] : null
|
||||
// no empty line in block html
|
||||
let result = html // .split(/\n/).filter(line => /\S/.test(line)).join('\n')
|
||||
// start inline tag must ends with `\n`
|
||||
if (tag) {
|
||||
if (BLOCK_TYPE7.indexOf(tag) > -1 && !HTML_NEWLINE_REG.test(html)) {
|
||||
result = result.replace(HTML_REG, (m, p) => `${m}\n`)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const codeBlockCtrl = ContentState => {
|
||||
ContentState.prototype.selectLanguage = function (paragraph, name) {
|
||||
const block = this.getBlock(paragraph.id)
|
||||
block.text = block.text.replace(/^(`+)([^`]+$)/g, `$1${name}`)
|
||||
this.codeBlockUpdate(block)
|
||||
this.partialRender()
|
||||
}
|
||||
// Fix bug: when click the edge at the code block, the code block will be not focused.
|
||||
ContentState.prototype.focusCodeBlock = function (event) {
|
||||
const key = event.target.id
|
||||
const offset = 0
|
||||
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
loadLanguage(name)
|
||||
if (block.functionType === 'languageInput') {
|
||||
block.text = name
|
||||
const preBlock = this.getParent(block)
|
||||
const nextSibling = this.getNextSibling(block)
|
||||
preBlock.lang = name
|
||||
preBlock.functionType = 'fencecode'
|
||||
nextSibling.lang = name
|
||||
nextSibling.children.forEach(c => (c.lang = name))
|
||||
const { key } = nextSibling.children[0]
|
||||
const offset = 0
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
}
|
||||
} else {
|
||||
block.text = block.text.replace(/^(`+)([^`]+$)/g, `$1${name}`)
|
||||
this.codeBlockUpdate(block)
|
||||
}
|
||||
this.partialRender()
|
||||
}
|
||||
|
||||
/**
|
||||
* [codeBlockUpdate if block updated to `pre` return true, else return false]
|
||||
*/
|
||||
@ -53,15 +41,26 @@ const codeBlockCtrl = ContentState => {
|
||||
const { text } = block.children[0]
|
||||
const match = CODE_UPDATE_REP.exec(text)
|
||||
if (match || lang) {
|
||||
const codeBlock = this.createBlock('code')
|
||||
const firstLine = this.createBlock('span', code)
|
||||
const language = lang || (match ? match[1] : '')
|
||||
const inputBlock = this.createBlock('span', language)
|
||||
loadLanguage(language)
|
||||
inputBlock.functionType = 'languageInput'
|
||||
block.type = 'pre'
|
||||
block.functionType = 'code'
|
||||
block.codeBlockStyle = 'fenced'
|
||||
block.text = code
|
||||
block.functionType = 'fencecode'
|
||||
block.lang = language
|
||||
block.text = ''
|
||||
block.history = null
|
||||
block.lang = lang || (match ? match[1] : '')
|
||||
block.children = []
|
||||
const { key } = block
|
||||
const offset = 0
|
||||
codeBlock.lang = language
|
||||
firstLine.lang = language
|
||||
firstLine.functionType = 'codeLine'
|
||||
this.appendChild(codeBlock, firstLine)
|
||||
this.appendChild(block, inputBlock)
|
||||
this.appendChild(block, codeBlock)
|
||||
const { key } = firstLine
|
||||
const offset = code.length
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
@ -70,138 +69,6 @@ const codeBlockCtrl = ContentState => {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
ContentState.prototype.pre2CodeMirror = function (isRenderCursor, blocks) {
|
||||
const { eventCenter } = this.muya
|
||||
let selector = ''
|
||||
if (blocks) {
|
||||
selector = blocks.map(({ type, key }) => {
|
||||
if (type === 'pre') {
|
||||
return `pre#${key}.${CLASS_OR_ID['AG_CODEMIRROR_BLOCK']}`
|
||||
} else {
|
||||
return `#${key} pre.${CLASS_OR_ID['AG_CODEMIRROR_BLOCK']}`
|
||||
}
|
||||
}).join(', ')
|
||||
} else {
|
||||
selector = `pre.${CLASS_OR_ID['AG_CODEMIRROR_BLOCK']}`
|
||||
}
|
||||
|
||||
const pres = document.querySelectorAll(selector)
|
||||
|
||||
Array.from(pres).forEach(pre => {
|
||||
// If pre element has children, means that this code block is not editing,
|
||||
// and don't need to update to codeMirror.
|
||||
if (pre.children.length) return
|
||||
const id = pre.id
|
||||
const block = this.getBlock(id)
|
||||
const value = block.text
|
||||
const autofocus = id === this.cursor.start.key && isRenderCursor
|
||||
const config = Object.assign(codeMirrorConfig, { autofocus, value })
|
||||
const codeBlock = codeMirror(pre, config)
|
||||
const mode = pre.getAttribute('data-lang')
|
||||
let input
|
||||
if (block.functionType === 'code') {
|
||||
input = createInputInCodeBlock(pre)
|
||||
}
|
||||
|
||||
const handler = ({ name }) => {
|
||||
setMode(codeBlock, name)
|
||||
.then(m => {
|
||||
pre.setAttribute('data-lang', m.name)
|
||||
block.lang = m.name.toLowerCase()
|
||||
// change indent code block to fence code block
|
||||
if (block.codeBlockStyle !== 'fenced') {
|
||||
block.codeBlockStyle = 'fenced'
|
||||
}
|
||||
if (input) {
|
||||
input.value = m.name
|
||||
input.blur()
|
||||
}
|
||||
if (this.cursor.start.key === block.key && isRenderCursor) {
|
||||
if (block.selection) {
|
||||
codeBlock.focus()
|
||||
const { anchor, head } = block.selection
|
||||
codeBlock.setSelection(anchor, head)
|
||||
} else {
|
||||
setCursorAtLastLine(codeBlock)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.warn(err)
|
||||
})
|
||||
}
|
||||
|
||||
this.codeBlocks.set(id, codeBlock)
|
||||
|
||||
if (mode) {
|
||||
handler({ name: mode })
|
||||
}
|
||||
|
||||
if (block.selection && this.cursor.start.key === block.key && isRenderCursor) {
|
||||
const { anchor, head } = block.selection
|
||||
codeBlock.focus()
|
||||
codeBlock.setSelection(anchor, head)
|
||||
}
|
||||
|
||||
if (block.history) {
|
||||
codeBlock.setHistory(block.history)
|
||||
}
|
||||
|
||||
if (input) {
|
||||
eventCenter.attachDOMEvent(input, 'input', () => {
|
||||
const value = input.value
|
||||
eventCenter.dispatch('muya-code-picker', {
|
||||
reference: getParagraphReference(input, id),
|
||||
lang: value.trim(),
|
||||
cb: handler
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
codeBlock.on('focus', (cm, event) => {
|
||||
block.selection = cm.listSelections()[0]
|
||||
})
|
||||
|
||||
codeBlock.on('blur', (cm, event) => {
|
||||
block.selection = cm.listSelections()[0]
|
||||
if (block.functionType === 'html') {
|
||||
const value = cm.getValue()
|
||||
|
||||
block.text = beautifyHtml(value)
|
||||
}
|
||||
})
|
||||
|
||||
codeBlock.on('cursorActivity', (cm, event) => {
|
||||
block.coords = cm.cursorCoords()
|
||||
block.selection = cm.listSelections()[0]
|
||||
})
|
||||
|
||||
let lastUndoLength = 0
|
||||
codeBlock.on('change', (cm, change) => {
|
||||
const value = cm.getValue()
|
||||
block.text = value
|
||||
block.history = cm.getHistory()
|
||||
if (block.functionType === 'html') {
|
||||
const preBlock = this.getNextSibling(block)
|
||||
const htmlBlock = this.getParent(this.getParent(block))
|
||||
const escapedHtml = sanitize(block.text, PREVIEW_DOMPURIFY_CONFIG)
|
||||
htmlBlock.text = block.text
|
||||
const preEle = document.querySelector(`#${preBlock.key}`)
|
||||
preEle.innerHTML = escapedHtml
|
||||
preBlock.htmlContent = escapedHtml
|
||||
}
|
||||
const { undo } = cm.historySize()
|
||||
if (undo > lastUndoLength) {
|
||||
this.history.push({
|
||||
type: 'codeBlock',
|
||||
id
|
||||
})
|
||||
lastUndoLength = undo
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default codeBlockCtrl
|
||||
|
@ -5,9 +5,6 @@ import ExportMarkdown from '../utils/exportMarkdown'
|
||||
|
||||
const copyCutCtrl = ContentState => {
|
||||
ContentState.prototype.cutHandler = function () {
|
||||
if (this.checkInCodeBlock()) {
|
||||
return
|
||||
}
|
||||
const { start, end } = this.cursor
|
||||
const startBlock = this.getBlock(start.key)
|
||||
const endBlock = this.getBlock(end.key)
|
||||
@ -22,16 +19,6 @@ const copyCutCtrl = ContentState => {
|
||||
this.partialRender()
|
||||
}
|
||||
|
||||
ContentState.prototype.checkInCodeBlock = function () {
|
||||
const { start, end } = selection.getCursorRange()
|
||||
const { type, functionType } = this.getBlock(start.key)
|
||||
|
||||
if (start.key === end.key && type === 'pre' && /code|html/.test(functionType)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
ContentState.prototype.getClipBoradData = function () {
|
||||
const html = selection.getSelectionHtml()
|
||||
const wrapper = document.createElement('div')
|
||||
@ -41,7 +28,9 @@ const copyCutCtrl = ContentState => {
|
||||
.${CLASS_OR_ID['AG_MATH_RENDER']},
|
||||
.${CLASS_OR_ID['AG_HTML_PREVIEW']},
|
||||
.${CLASS_OR_ID['AG_MATH_PREVIEW']},
|
||||
.${CLASS_OR_ID['AG_COPY_REMOVE']}`
|
||||
.${CLASS_OR_ID['AG_COPY_REMOVE']},
|
||||
.${CLASS_OR_ID['AG_LANGUAGE_INPUT']}`
|
||||
|
||||
)
|
||||
;[...removedElements].forEach(e => e.remove())
|
||||
|
||||
@ -78,31 +67,32 @@ const copyCutCtrl = ContentState => {
|
||||
l.replaceWith(span)
|
||||
})
|
||||
|
||||
const codefense = wrapper.querySelectorAll(`pre.${CLASS_OR_ID['AG_CODE_BLOCK']}`)
|
||||
const codefense = wrapper.querySelectorAll(`pre[data-role$='code']`)
|
||||
;[...codefense].forEach(cf => {
|
||||
const id = cf.id
|
||||
const language = cf.getAttribute('data-lang') || ''
|
||||
const cm = this.codeBlocks.get(id)
|
||||
const value = cm.getValue()
|
||||
cf.innerHTML = `<code class="language-${language}" lang="${language}">${value}</code>`
|
||||
const block = this.getBlock(id)
|
||||
const language = block.lang || ''
|
||||
const selectedCodeLines = cf.querySelectorAll('.ag-code-line')
|
||||
const value = [...selectedCodeLines].map(codeLine => codeLine.textContent).join('\n')
|
||||
cf.innerHTML = `<code class="language-${language}">${value}</code>`
|
||||
})
|
||||
|
||||
const htmlBlock = wrapper.querySelectorAll(`figure[data-role='HTML']`)
|
||||
;[...htmlBlock].forEach(hb => {
|
||||
const id = hb.id
|
||||
const { text } = this.getBlock(id)
|
||||
const selectedCodeLines = hb.querySelectorAll('span.ag-code-line')
|
||||
const value = [...selectedCodeLines].map(codeLine => codeLine.textContent).join('\n')
|
||||
const pre = document.createElement('pre')
|
||||
pre.textContent = text
|
||||
pre.textContent = value
|
||||
hb.replaceWith(pre)
|
||||
})
|
||||
|
||||
const mathBlock = wrapper.querySelectorAll(`figure.ag-multiple-math-block`)
|
||||
;[...mathBlock].forEach(mb => {
|
||||
const id = mb.id
|
||||
const { math } = this.getBlock(id).children[1]
|
||||
const selectedCodeLines = mb.querySelectorAll('span.ag-code-line')
|
||||
const value = [...selectedCodeLines].map(codeLine => codeLine.textContent).join('\n')
|
||||
const pre = document.createElement('pre')
|
||||
pre.classList.add('multiple-math')
|
||||
pre.textContent = math
|
||||
pre.textContent = value
|
||||
mb.replaceWith(pre)
|
||||
})
|
||||
|
||||
@ -113,9 +103,6 @@ const copyCutCtrl = ContentState => {
|
||||
}
|
||||
|
||||
ContentState.prototype.copyHandler = function (event, type) {
|
||||
if (this.checkInCodeBlock()) {
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
|
||||
const { html, text } = this.getClipBoradData()
|
||||
|
@ -1,5 +1,13 @@
|
||||
import selection from '../selection'
|
||||
|
||||
const checkAutoIndent = (text, offset) => {
|
||||
const pairStr = text.substring(offset - 1, offset + 1)
|
||||
return /^(\{\}|\[\]|\(\)|><)$/.test(pairStr)
|
||||
}
|
||||
const getIndentSpace = text => {
|
||||
return /^(\s*)\S/.exec(text)[1]
|
||||
}
|
||||
|
||||
const enterCtrl = ContentState => {
|
||||
ContentState.prototype.chopBlockByCursor = function (block, key, offset) {
|
||||
const newBlock = this.createBlock('p')
|
||||
@ -140,25 +148,15 @@ const enterCtrl = ContentState => {
|
||||
const endBlock = this.getBlock(end.key)
|
||||
let parent = this.getParent(block)
|
||||
|
||||
// handle cursor in code block
|
||||
if (block.type === 'pre' && block.functionType === 'code') {
|
||||
return
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
// handle select multiple blocks
|
||||
if (start.key !== end.key) {
|
||||
const key = start.key
|
||||
const offset = start.offset
|
||||
|
||||
const startRemainText = block.type === 'pre'
|
||||
? block.text.substring(0, start.offset - 1)
|
||||
: block.text.substring(0, start.offset)
|
||||
const startRemainText = block.text.substring(0, start.offset)
|
||||
|
||||
const endRemainText = endBlock.type === 'pre'
|
||||
? endBlock.text.substring(end.offset - 1)
|
||||
: endBlock.text.substring(end.offset)
|
||||
const endRemainText = endBlock.text.substring(end.offset)
|
||||
|
||||
block.text = startRemainText + endRemainText
|
||||
|
||||
@ -186,18 +184,31 @@ const enterCtrl = ContentState => {
|
||||
|
||||
// handle `shift + enter` insert `soft line break` or `hard line break`
|
||||
// only cursor in `line block` can create `soft line break` and `hard line break`
|
||||
// handle line in code block
|
||||
if (
|
||||
(event.shiftKey && block.type === 'span') ||
|
||||
(block.type === 'span' && /frontmatter|multiplemath/.test(block.functionType))
|
||||
(block.type === 'span' && block.functionType === 'codeLine')
|
||||
) {
|
||||
const { text } = block
|
||||
const newLineText = text.substring(start.offset)
|
||||
const autoIndent = checkAutoIndent(text, start.offset)
|
||||
const indent = getIndentSpace(text)
|
||||
block.text = text.substring(0, start.offset)
|
||||
const newLine = this.createBlock('span', newLineText)
|
||||
const newLine = this.createBlock('span', `${indent}${newLineText}`)
|
||||
newLine.functionType = block.functionType
|
||||
newLine.lang = block.lang
|
||||
this.insertAfter(newLine, block)
|
||||
const { key } = newLine
|
||||
const offset = 0
|
||||
let { key } = newLine
|
||||
let offset = indent.length
|
||||
if (autoIndent) {
|
||||
const emptyLine = this.createBlock('span', indent + ' '.repeat(this.tabSize))
|
||||
emptyLine.functionType = block.functionType
|
||||
emptyLine.lang = block.lang
|
||||
this.insertAfter(emptyLine, block)
|
||||
key = emptyLine.key
|
||||
offset = indent.length + this.tabSize
|
||||
}
|
||||
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
@ -374,7 +385,7 @@ const enterCtrl = ContentState => {
|
||||
cursorBlock = tableNeedFocus
|
||||
break
|
||||
case !!htmlNeedFocus:
|
||||
cursorBlock = htmlNeedFocus
|
||||
cursorBlock = htmlNeedFocus.children[0].children[1] // the second line
|
||||
break
|
||||
case !!mathNeedFocus:
|
||||
cursorBlock = mathNeedFocus
|
||||
|
@ -13,22 +13,12 @@ export class History {
|
||||
this.index = this.index - 1
|
||||
|
||||
const state = deepCopy(this.stack[this.index])
|
||||
switch (state.type) {
|
||||
case 'normal':
|
||||
const { blocks, cursor, renderRange } = state
|
||||
cursor.noHistory = true
|
||||
this.contentState.blocks = blocks
|
||||
this.contentState.renderRange = renderRange
|
||||
this.contentState.cursor = cursor
|
||||
this.contentState.render()
|
||||
break
|
||||
case 'codeBlock':
|
||||
const id = state.id
|
||||
const codeBlock = this.contentState.codeBlocks.get(id)
|
||||
codeBlock.focus()
|
||||
codeBlock.undo()
|
||||
break
|
||||
}
|
||||
const { blocks, cursor, renderRange } = state
|
||||
cursor.noHistory = true
|
||||
this.contentState.blocks = blocks
|
||||
this.contentState.renderRange = renderRange
|
||||
this.contentState.cursor = cursor
|
||||
this.contentState.render()
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,22 +28,12 @@ export class History {
|
||||
if (index < len - 1) {
|
||||
this.index = index + 1
|
||||
const state = deepCopy(stack[this.index])
|
||||
switch (state.type) {
|
||||
case 'normal':
|
||||
const { blocks, cursor, renderRange } = state
|
||||
cursor.noHistory = true
|
||||
this.contentState.blocks = blocks
|
||||
this.contentState.renderRange = renderRange
|
||||
this.contentState.cursor = cursor
|
||||
this.contentState.render()
|
||||
break
|
||||
case 'codeBlock':
|
||||
const id = state.id
|
||||
const codeBlock = this.contentState.codeBlocks.get(id)
|
||||
codeBlock.focus()
|
||||
codeBlock.redo()
|
||||
break
|
||||
}
|
||||
const { blocks, cursor, renderRange } = state
|
||||
cursor.noHistory = true
|
||||
this.contentState.blocks = blocks
|
||||
this.contentState.renderRange = renderRange
|
||||
this.contentState.cursor = cursor
|
||||
this.contentState.render()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { sanitize } from '../utils'
|
||||
import { VOID_HTML_TAGS, HTML_TAGS, HTML_TOOLS, PREVIEW_DOMPURIFY_CONFIG } from '../config'
|
||||
import { VOID_HTML_TAGS, HTML_TAGS, HTML_TOOLS } from '../config'
|
||||
|
||||
const HTML_BLOCK_REG = /^<([a-zA-Z\d-]+)(?=\s|>)[^<>]*?>$/
|
||||
const LINE_BREAKS = /\n/
|
||||
|
||||
const htmlBlock = ContentState => {
|
||||
ContentState.prototype.createToolBar = function (tools, toolBarType) {
|
||||
@ -22,28 +22,36 @@ const htmlBlock = ContentState => {
|
||||
return toolBar
|
||||
}
|
||||
|
||||
ContentState.prototype.createCodeInHtml = function (code, selection) {
|
||||
ContentState.prototype.createCodeInHtml = function (code) {
|
||||
const codeContainer = this.createBlock('div')
|
||||
codeContainer.functionType = 'html'
|
||||
const preview = this.createBlock('div', '', false)
|
||||
preview.htmlContent = sanitize(code, PREVIEW_DOMPURIFY_CONFIG)
|
||||
preview.functionType = 'preview'
|
||||
const codePre = this.createBlock('pre')
|
||||
codePre.lang = 'html'
|
||||
codePre.functionType = 'html'
|
||||
codePre.text = code
|
||||
if (selection) {
|
||||
codePre.selection = selection
|
||||
}
|
||||
this.appendChild(codeContainer, codePre)
|
||||
const preBlock = this.createBlock('pre')
|
||||
const codeBlock = this.createBlock('code')
|
||||
code.split(LINE_BREAKS).forEach(line => {
|
||||
const codeLine = this.createBlock('span', line)
|
||||
codeLine.functionType = 'codeLine'
|
||||
codeLine.lang = 'markup'
|
||||
this.appendChild(codeBlock, codeLine)
|
||||
})
|
||||
this.codeBlocks.set(preBlock.key, code)
|
||||
preBlock.lang = 'markup'
|
||||
codeBlock.lang = 'markup'
|
||||
preBlock.functionType = 'html'
|
||||
this.codeBlocks.set(preBlock.key, code)
|
||||
this.appendChild(preBlock, codeBlock)
|
||||
this.appendChild(codeContainer, preBlock)
|
||||
this.appendChild(codeContainer, preview)
|
||||
return codeContainer
|
||||
}
|
||||
|
||||
ContentState.prototype.htmlToolBarClick = function (type) {
|
||||
const { start: { key } } = this.cursor
|
||||
const block = this.getBlock(key)
|
||||
const codeBlockContainer = this.getParent(block)
|
||||
const codeLine = this.getBlock(key)
|
||||
const codeBlock = this.getParent(codeLine)
|
||||
const preBlock = this.getParent(codeBlock)
|
||||
const codeBlockContainer = this.getParent(preBlock)
|
||||
const htmlBlock = this.getParent(codeBlockContainer)
|
||||
|
||||
switch (type) {
|
||||
@ -79,7 +87,6 @@ const htmlBlock = ContentState => {
|
||||
ContentState.prototype.createHtmlBlock = function (code) {
|
||||
const block = this.createBlock('figure')
|
||||
block.functionType = 'html'
|
||||
block.text = code
|
||||
const toolBar = this.createToolBar(HTML_TOOLS, 'html')
|
||||
const htmlBlock = this.createCodeInHtml(code)
|
||||
this.appendChild(block, toolBar)
|
||||
@ -91,24 +98,15 @@ const htmlBlock = ContentState => {
|
||||
const isVoidTag = VOID_HTML_TAGS.indexOf(tagName) > -1
|
||||
const { text } = block.children[0]
|
||||
const htmlContent = isVoidTag ? text : `${text}\n\n</${tagName}>`
|
||||
|
||||
const pos = {
|
||||
line: isVoidTag ? 0 : 1,
|
||||
ch: isVoidTag ? text.length : 0
|
||||
}
|
||||
const range = {
|
||||
anchor: pos,
|
||||
head: pos
|
||||
}
|
||||
block.type = 'figure'
|
||||
block.functionType = 'html'
|
||||
block.text = htmlContent
|
||||
block.children = []
|
||||
const toolBar = this.createToolBar(HTML_TOOLS, 'html')
|
||||
const codeContainer = this.createCodeInHtml(htmlContent, range)
|
||||
const codeContainer = this.createCodeInHtml(htmlContent)
|
||||
this.appendChild(block, toolBar)
|
||||
this.appendChild(block, codeContainer)
|
||||
return codeContainer.children[0]
|
||||
return codeContainer.children[0] // preBlock
|
||||
}
|
||||
|
||||
ContentState.prototype.updateHtmlBlock = function (block) {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { HAS_TEXT_BLOCK_REG, DEFAULT_TURNDOWN_CONFIG } from '../config'
|
||||
import { setCursorAtLastLine } from '../codeMirror'
|
||||
import { getUniqueId } from '../utils'
|
||||
import selection from '../selection'
|
||||
import StateRender from '../parser/render'
|
||||
@ -21,6 +20,8 @@ import searchCtrl from './searchCtrl'
|
||||
import mathCtrl from './mathCtrl'
|
||||
import imagePathCtrl from './imagePathCtrl'
|
||||
import htmlBlockCtrl from './htmlBlock'
|
||||
import clickCtrl from './clickCtrl'
|
||||
import inputCtrl from './inputCtrl'
|
||||
import importMarkdown from '../utils/importMarkdown'
|
||||
|
||||
const prototypes = [
|
||||
@ -41,12 +42,13 @@ const prototypes = [
|
||||
mathCtrl,
|
||||
imagePathCtrl,
|
||||
htmlBlockCtrl,
|
||||
clickCtrl,
|
||||
inputCtrl,
|
||||
importMarkdown
|
||||
]
|
||||
|
||||
class ContentState {
|
||||
constructor (muya, options) {
|
||||
const { eventCenter } = muya
|
||||
const { bulletListMarker } = options
|
||||
|
||||
this.muya = muya
|
||||
@ -55,7 +57,7 @@ class ContentState {
|
||||
// Use to cache the keys which you don't want to remove.
|
||||
this.exemption = new Set()
|
||||
this.blocks = [ this.createBlockP() ]
|
||||
this.stateRender = new StateRender(eventCenter)
|
||||
this.stateRender = new StateRender(muya)
|
||||
this.codeBlocks = new Map()
|
||||
this.renderRange = [ null, null ]
|
||||
this.currentCursor = null
|
||||
@ -69,22 +71,9 @@ class ContentState {
|
||||
}
|
||||
|
||||
set cursor (cursor) {
|
||||
// if (this.currentCursor) {
|
||||
// const { start, end } = this.currentCursor
|
||||
// if (
|
||||
// start.key === cursor.start.key &&
|
||||
// start.offset === cursor.start.offset &&
|
||||
// end.key === cursor.end.key &&
|
||||
// end.offset === cursor.end.offset
|
||||
// ) {
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
const handler = () => {
|
||||
const { blocks, renderRange, currentCursor } = this
|
||||
this.history.push({
|
||||
type: 'normal',
|
||||
blocks,
|
||||
renderRange,
|
||||
cursor: currentCursor
|
||||
@ -136,34 +125,20 @@ class ContentState {
|
||||
}
|
||||
|
||||
setCursor () {
|
||||
const { start: { key } } = this.cursor
|
||||
const block = this.getBlock(key)
|
||||
if (block.type === 'pre' && /code|html/.test(block.functionType)) {
|
||||
const cm = this.codeBlocks.get(key)
|
||||
const { selection: codeSel } = block
|
||||
if (codeSel) {
|
||||
const { anchor, head } = codeSel
|
||||
cm.focus()
|
||||
cm.setSelection(anchor, head)
|
||||
} else {
|
||||
setCursorAtLastLine(cm)
|
||||
}
|
||||
} else {
|
||||
selection.setCursorRange(this.cursor)
|
||||
}
|
||||
selection.setCursorRange(this.cursor)
|
||||
}
|
||||
|
||||
setNextRenderRange () {
|
||||
const { start, end } = this.cursor
|
||||
// console.log(JSON.stringify(this.cursor, null, 2))
|
||||
const startBlock = this.getBlock(start.key)
|
||||
const endBlock = this.getBlock(end.key)
|
||||
const startOutMostBlock = this.findOutMostBlock(startBlock)
|
||||
const endOutMostBlock = this.findOutMostBlock(endBlock)
|
||||
|
||||
this.renderRange = [ startOutMostBlock.preSibling, endOutMostBlock.nextSibling ]
|
||||
}
|
||||
|
||||
render (isRenderCursor = true, refreshCodeBlock = false) {
|
||||
render (isRenderCursor = true) {
|
||||
const { blocks, cursor, searchMatches: { matches, index } } = this
|
||||
const activeBlocks = this.getActiveBlocks()
|
||||
matches.forEach((m, i) => {
|
||||
@ -171,15 +146,13 @@ class ContentState {
|
||||
})
|
||||
this.setNextRenderRange()
|
||||
this.stateRender.collectLabels(blocks)
|
||||
this.stateRender.render(blocks, cursor, activeBlocks, matches, refreshCodeBlock)
|
||||
this.pre2CodeMirror(isRenderCursor)
|
||||
this.stateRender.render(blocks, cursor, activeBlocks, matches)
|
||||
if (isRenderCursor) this.setCursor()
|
||||
}
|
||||
|
||||
partialRender () {
|
||||
const { blocks, cursor, searchMatches: { matches, index } } = this
|
||||
const activeBlocks = this.getActiveBlocks()
|
||||
const cursorOutMostBlock = activeBlocks[activeBlocks.length - 1]
|
||||
const [ startKey, endKey ] = this.renderRange
|
||||
matches.forEach((m, i) => {
|
||||
m.active = i === index
|
||||
@ -191,7 +164,6 @@ class ContentState {
|
||||
this.setNextRenderRange()
|
||||
this.stateRender.collectLabels(blocks)
|
||||
this.stateRender.partialRender(needRenderBlocks, cursor, activeBlocks, matches, startKey, endKey)
|
||||
this.pre2CodeMirror(true, [...new Set([cursorOutMostBlock, ...needRenderBlocks])])
|
||||
this.setCursor()
|
||||
}
|
||||
|
||||
@ -299,6 +271,7 @@ class ContentState {
|
||||
}
|
||||
|
||||
removeTextOrBlock (block) {
|
||||
if (block.functionType === 'languageInput') return
|
||||
const checkerIn = block => {
|
||||
if (this.exemption.has(block.key)) {
|
||||
return true
|
||||
@ -382,7 +355,7 @@ class ContentState {
|
||||
if (!afterEnd) {
|
||||
const parent = this.getParent(after)
|
||||
if (parent) {
|
||||
const removeAfter = isRemoveAfter && this.isOnlyEditableChild(after)
|
||||
const removeAfter = isRemoveAfter && (this.isOnlyRemoveableChild(after))
|
||||
this.removeBlocks(before, parent, removeAfter, true)
|
||||
}
|
||||
}
|
||||
@ -395,12 +368,6 @@ class ContentState {
|
||||
}
|
||||
|
||||
removeBlock (block, fromBlocks = this.blocks) {
|
||||
if (block.type === 'pre') {
|
||||
const codeBlockId = block.key
|
||||
if (this.codeBlocks.has(codeBlockId)) {
|
||||
this.codeBlocks.delete(codeBlockId)
|
||||
}
|
||||
}
|
||||
const remove = (blocks, block) => {
|
||||
const len = blocks.length
|
||||
let i
|
||||
@ -531,11 +498,11 @@ class ContentState {
|
||||
return !block.nextSibling && !block.preSibling
|
||||
}
|
||||
|
||||
isOnlyEditableChild (block) {
|
||||
isOnlyRemoveableChild (block) {
|
||||
if (block.editable === false) return false
|
||||
const parent = this.getParent(block)
|
||||
if (!parent) throw new Error('isOnlyEditableChild method only apply for child block')
|
||||
return parent.children.filter(child => child.editable).length === 1
|
||||
if (!parent) throw new Error('isOnlyRemoveableChild method only apply for child block')
|
||||
return parent.children.filter(child => child.editable && child.functionType !== 'languageInput').length === 1
|
||||
}
|
||||
|
||||
getLastChild (block) {
|
||||
@ -553,7 +520,11 @@ class ContentState {
|
||||
if (block.children.length === 0 && HAS_TEXT_BLOCK_REG.test(block.type)) {
|
||||
return block
|
||||
} else if (children.length) {
|
||||
if (children[0].type === 'input' || (children[0].type === 'div' && children[0].editable === false)) { // handle task item
|
||||
if (
|
||||
children[0].type === 'input' ||
|
||||
(children[0].type === 'div' && children[0].editable === false) ||
|
||||
(children[0].type === 'span' && children[0].functionType === 'languageInput')
|
||||
) { // handle task item
|
||||
return this.firstInDescendant(children[1])
|
||||
} else {
|
||||
return this.firstInDescendant(children[0])
|
||||
@ -577,7 +548,13 @@ class ContentState {
|
||||
findPreBlockInLocation (block) {
|
||||
const parent = this.getParent(block)
|
||||
const preBlock = this.getPreSibling(block)
|
||||
if (block.preSibling && preBlock.type !== 'input' && preBlock.type !== 'div' && preBlock.editable !== false) { // handle task item and table
|
||||
if (
|
||||
block.preSibling &&
|
||||
preBlock.type !== 'input' &&
|
||||
preBlock.type !== 'div' &&
|
||||
preBlock.editable !== false &&
|
||||
preBlock.functionType !== 'languageInput'
|
||||
) { // handle task item and table
|
||||
return this.lastInDescendant(preBlock)
|
||||
} else if (parent) {
|
||||
return this.findPreBlockInLocation(parent)
|
||||
@ -590,7 +567,9 @@ class ContentState {
|
||||
const parent = this.getParent(block)
|
||||
const nextBlock = this.getNextSibling(block)
|
||||
|
||||
if (nextBlock && nextBlock.editable !== false) {
|
||||
if (
|
||||
nextBlock && nextBlock.editable !== false
|
||||
) {
|
||||
return this.firstInDescendant(nextBlock)
|
||||
} else if (parent) {
|
||||
return this.findNextBlockInLocation(parent)
|
||||
@ -627,7 +606,7 @@ class ContentState {
|
||||
}
|
||||
|
||||
clear () {
|
||||
this.codeBlocks.clear()
|
||||
this.history.clearHistory()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,123 @@
|
||||
import selection from '../selection'
|
||||
import { getTextContent } from '../selection/dom'
|
||||
import { beginRules } from '../parser/rules'
|
||||
import { CLASS_OR_ID } from '../config'
|
||||
|
||||
const BRACKET_HASH = {
|
||||
'{': '}',
|
||||
'[': ']',
|
||||
'(': ')',
|
||||
'*': '*',
|
||||
'_': '_',
|
||||
'"': '"',
|
||||
'\'': '\''
|
||||
}
|
||||
|
||||
const inputCtrl = ContentState => {
|
||||
// Input @ to quick insert paragraph
|
||||
ContentState.prototype.checkQuickInsert = function (block) {
|
||||
const { type, text, functionType } = block
|
||||
if (type !== 'span' || functionType) return false
|
||||
return /^@[a-zA-Z\d]*$/.test(text)
|
||||
}
|
||||
ContentState.prototype.inputHandler = function (event) {
|
||||
// todo
|
||||
const { start, end } = selection.getCursorRange()
|
||||
const { start: oldStart, end: oldEnd } = this.cursor
|
||||
const key = start.key
|
||||
const block = this.getBlock(key)
|
||||
const paragraph = document.querySelector(`#${key}`)
|
||||
let text = getTextContent(paragraph, [ CLASS_OR_ID['AG_MATH_RENDER'] ])
|
||||
let needRender = false
|
||||
let needRenderAll = false
|
||||
|
||||
if (oldStart.key !== oldEnd.key) {
|
||||
const startBlock = this.getBlock(oldStart.key)
|
||||
const endBlock = this.getBlock(oldEnd.key)
|
||||
this.removeBlocks(startBlock, endBlock)
|
||||
needRender = true
|
||||
}
|
||||
|
||||
// auto pair (not need to auto pair in math block)
|
||||
if (block && block.text !== text) {
|
||||
if (
|
||||
start.key === end.key &&
|
||||
start.offset === end.offset
|
||||
) {
|
||||
const { offset } = start
|
||||
const { autoPairBracket, autoPairMarkdownSyntax, autoPairQuote } = this
|
||||
const inputChar = text.charAt(+offset - 1)
|
||||
const preInputChar = text.charAt(+offset - 2)
|
||||
const postInputChar = text.charAt(+offset)
|
||||
/* eslint-disable no-useless-escape */
|
||||
if (
|
||||
(event.inputType.indexOf('delete') === -1) &&
|
||||
(inputChar === postInputChar) &&
|
||||
(
|
||||
(autoPairQuote && /[']{1}/.test(inputChar)) ||
|
||||
(autoPairQuote && /["]{1}/.test(inputChar)) ||
|
||||
(autoPairBracket && /[\}\]\)]{1}/.test(inputChar)) ||
|
||||
(autoPairMarkdownSyntax && /[*_]{1}/.test(inputChar))
|
||||
)
|
||||
) {
|
||||
text = text.substring(0, offset) + text.substring(offset + 1)
|
||||
} else {
|
||||
/* eslint-disable no-useless-escape */
|
||||
// Not Unicode aware, since things like \p{Alphabetic} or \p{L} are not supported yet
|
||||
if (
|
||||
(autoPairQuote && /[']{1}/.test(inputChar) && !(/[a-zA-Z\d]{1}/.test(preInputChar))) ||
|
||||
(autoPairQuote && /["]{1}/.test(inputChar)) ||
|
||||
(autoPairBracket && /[\{\[\(]{1}/.test(inputChar)) ||
|
||||
(block.functionType !== 'codeLine' && autoPairMarkdownSyntax && /[*_]{1}/.test(inputChar))
|
||||
) {
|
||||
needRender = true
|
||||
text = BRACKET_HASH[event.data]
|
||||
? text.substring(0, offset) + BRACKET_HASH[inputChar] + text.substring(offset)
|
||||
: text
|
||||
}
|
||||
/* eslint-enable no-useless-escape */
|
||||
if (/\s/.test(event.data) && preInputChar === '*' && postInputChar === '*') {
|
||||
text = text.substring(0, offset) + text.substring(offset + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
block.text = text
|
||||
if (beginRules['reference_definition'].test(text)) {
|
||||
needRenderAll = true
|
||||
}
|
||||
}
|
||||
|
||||
// show quick insert
|
||||
const rect = paragraph.getBoundingClientRect()
|
||||
const checkQuickInsert = this.checkQuickInsert(block)
|
||||
const reference = this.getPositionReference()
|
||||
reference.getBoundingClientRect = function () {
|
||||
const { x, y, left, top, height, bottom } = rect
|
||||
|
||||
return Object.assign({}, {
|
||||
left,
|
||||
x,
|
||||
top,
|
||||
y,
|
||||
bottom,
|
||||
height,
|
||||
width: 0,
|
||||
right: left
|
||||
})
|
||||
}
|
||||
this.muya.eventCenter.dispatch('muya-quick-insert', reference, block, checkQuickInsert)
|
||||
|
||||
// Update preview content of math block
|
||||
if (block && block.type === 'span' && block.functionType === 'codeLine') {
|
||||
needRender = true
|
||||
this.updateCodeBlocks(block)
|
||||
}
|
||||
|
||||
this.cursor = { start, end }
|
||||
const checkMarkedUpdate = this.checkNeedRender()
|
||||
const inlineUpdatedBlock = this.isCollapse() && this.checkInlineUpdate(block)
|
||||
if (checkMarkedUpdate || inlineUpdatedBlock || needRender) {
|
||||
return needRenderAll ? this.render() : this.partialRender()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,44 +5,54 @@ const mathCtrl = ContentState => {
|
||||
ContentState.prototype.createMathBlock = function (value = '') {
|
||||
const FUNCTION_TYPE = 'multiplemath'
|
||||
const mathBlock = this.createBlock('figure')
|
||||
const textArea = this.createBlock('pre')
|
||||
const mathPreview = this.createBlock('div', '', false)
|
||||
mathBlock.functionType = FUNCTION_TYPE
|
||||
const { preBlock, mathPreview } = this.createMathAndPreview(value)
|
||||
this.appendChild(mathBlock, preBlock)
|
||||
this.appendChild(mathBlock, mathPreview)
|
||||
this.codeBlocks.set(preBlock.key, value)
|
||||
return mathBlock
|
||||
}
|
||||
|
||||
ContentState.prototype.createMathAndPreview = function (value = '') {
|
||||
const FUNCTION_TYPE = 'multiplemath'
|
||||
const preBlock = this.createBlock('pre')
|
||||
const codeBlock = this.createBlock('code')
|
||||
preBlock.functionType = FUNCTION_TYPE
|
||||
preBlock.lang = codeBlock.lang = 'latex'
|
||||
this.appendChild(preBlock, codeBlock)
|
||||
|
||||
if (typeof value === 'string' && value) {
|
||||
const lines = value.replace(/^\s+/, '').split(LINE_BREAKS_REG).map(line => this.createBlock('span', line))
|
||||
for (const line of lines) {
|
||||
line.functionType = FUNCTION_TYPE
|
||||
this.appendChild(textArea, line)
|
||||
}
|
||||
value.replace(/^\s+/, '').split(LINE_BREAKS_REG).forEach(line => {
|
||||
const codeLine = this.createBlock('span', line)
|
||||
codeLine.functionType = 'codeLine'
|
||||
codeLine.lang = 'latex'
|
||||
this.appendChild(codeBlock, codeLine)
|
||||
})
|
||||
} else {
|
||||
const emptyLine = this.createBlock('span')
|
||||
emptyLine.functionType = FUNCTION_TYPE
|
||||
this.appendChild(textArea, emptyLine)
|
||||
emptyLine.functionType = 'codeLine'
|
||||
emptyLine.lang = 'latex'
|
||||
this.appendChild(codeBlock, emptyLine)
|
||||
}
|
||||
|
||||
mathBlock.functionType = textArea.functionType = mathPreview.functionType = FUNCTION_TYPE
|
||||
mathPreview.math = value
|
||||
this.appendChild(mathBlock, textArea)
|
||||
this.appendChild(mathBlock, mathPreview)
|
||||
return mathBlock
|
||||
const mathPreview = this.createBlock('div', '', false)
|
||||
this.codeBlocks.set(preBlock.key, '')
|
||||
mathPreview.functionType = FUNCTION_TYPE
|
||||
|
||||
return { preBlock, mathPreview }
|
||||
}
|
||||
|
||||
ContentState.prototype.initMathBlock = function (block) { // p block
|
||||
const FUNCTION_TYPE = 'multiplemath'
|
||||
const textArea = this.createBlock('pre')
|
||||
const emptyLine = this.createBlock('span')
|
||||
textArea.functionType = emptyLine.functionType = FUNCTION_TYPE
|
||||
this.appendChild(textArea, emptyLine)
|
||||
block.type = 'figure'
|
||||
block.functionType = FUNCTION_TYPE
|
||||
block.children = []
|
||||
|
||||
const mathPreview = this.createBlock('div', '', false)
|
||||
mathPreview.math = ''
|
||||
mathPreview.functionType = FUNCTION_TYPE
|
||||
const { preBlock, mathPreview } = this.createMathAndPreview()
|
||||
|
||||
this.appendChild(block, textArea)
|
||||
this.appendChild(block, preBlock)
|
||||
this.appendChild(block, mathPreview)
|
||||
return emptyLine
|
||||
return preBlock.children[0].children[0]
|
||||
}
|
||||
|
||||
ContentState.prototype.handleMathBlockClick = function (mathFigure) {
|
||||
|
@ -17,7 +17,6 @@ const getCurrentLevel = type => {
|
||||
|
||||
const paragraphCtrl = ContentState => {
|
||||
ContentState.prototype.selectionChange = function (cursor) {
|
||||
const { fontSize, lineHeight } = this
|
||||
const { start, end } = cursor || selection.getCursorRange()
|
||||
const cursorCoords = selection.getCursorCoords()
|
||||
const startBlock = this.getBlock(start.key)
|
||||
@ -33,13 +32,6 @@ const paragraphCtrl = ContentState => {
|
||||
end.type = endBlock.type
|
||||
end.block = endBlock
|
||||
|
||||
if (start.type === 'pre' && end.type === 'pre' && startBlock.functionType !== 'frontmatter') {
|
||||
const preElement = document.querySelector(`#${start.key}`)
|
||||
const { top } = preElement.getBoundingClientRect()
|
||||
const { line } = start.block.selection.anchor
|
||||
cursorCoords.y = top + line * lineHeight * fontSize
|
||||
}
|
||||
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
@ -73,10 +65,13 @@ const paragraphCtrl = ContentState => {
|
||||
const firstBlock = this.blocks[0]
|
||||
if (firstBlock.type === 'pre' && firstBlock.functionType === 'frontmatter') return
|
||||
const frontMatter = this.createBlock('pre')
|
||||
const codeBlock = this.createBlock('code')
|
||||
const emptyLine = this.createBlock('span')
|
||||
emptyLine.functionType = 'frontmatter'
|
||||
frontMatter.lang = codeBlock.lang = emptyLine.lang = 'yaml'
|
||||
emptyLine.functionType = 'codeLine'
|
||||
frontMatter.functionType = 'frontmatter'
|
||||
this.appendChild(frontMatter, emptyLine)
|
||||
this.appendChild(codeBlock, emptyLine)
|
||||
this.appendChild(frontMatter, codeBlock)
|
||||
this.insertBefore(frontMatter, firstBlock)
|
||||
const { key } = emptyLine
|
||||
const offset = 0
|
||||
@ -197,70 +192,75 @@ const paragraphCtrl = ContentState => {
|
||||
const startParents = this.getParents(startBlock)
|
||||
const endParents = this.getParents(endBlock)
|
||||
const hasFencedCodeBlockParent = () => {
|
||||
return startParents.some(b => b.type === 'pre' && b.functionType === 'code') ||
|
||||
endParents.some(b => b.type === 'pre' && b.functionType === 'code')
|
||||
return startParents.some(b => b.type === 'pre' && /code/.test(b.functionType)) ||
|
||||
endParents.some(b => b.type === 'pre' && /code/.test(b.functionType))
|
||||
}
|
||||
// change fenced code block to p paragraph
|
||||
if (affiliation.length && affiliation[0].type === 'pre' && affiliation[0].functionType === 'code') {
|
||||
const codeBlock = affiliation[0]
|
||||
this.codeBlocks.delete(codeBlock.key)
|
||||
codeBlock.type = 'p'
|
||||
codeBlock.children = []
|
||||
const lines = codeBlock.text.split(LINE_BREAKS_REG).map(line => this.createBlock('span', line))
|
||||
for (const line of lines) {
|
||||
this.appendChild(codeBlock, line)
|
||||
if (affiliation.length && affiliation[0].type === 'pre' && /code/.test(affiliation[0].functionType)) {
|
||||
const preBlock = affiliation[0]
|
||||
const codeLines = preBlock.children[1].children
|
||||
this.codeBlocks.delete(preBlock.key)
|
||||
preBlock.type = 'p'
|
||||
preBlock.children = []
|
||||
|
||||
for (const line of codeLines) {
|
||||
delete line.lang
|
||||
delete line.functionType
|
||||
this.appendChild(preBlock, line)
|
||||
}
|
||||
|
||||
const { key } = codeBlock.children[codeBlock.selection.anchor.line]
|
||||
const offset = codeBlock.selection.anchor.ch
|
||||
delete codeBlock.selection
|
||||
delete codeBlock.history
|
||||
delete codeBlock.lang
|
||||
delete codeBlock.coords
|
||||
delete codeBlock.text
|
||||
delete codeBlock.codeBlockStyle
|
||||
delete codeBlock.functionType
|
||||
delete preBlock.lang
|
||||
delete preBlock.functionType
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
start: this.cursor.start,
|
||||
end: this.cursor.end
|
||||
}
|
||||
} else {
|
||||
if (start.key === end.key) {
|
||||
if (startBlock.type === 'span') {
|
||||
startBlock = this.getParent(startBlock)
|
||||
startBlock.text = startBlock.children.map(line => line.text).join('\n')
|
||||
const line = startBlock.children.findIndex(line => line.key === start.key)
|
||||
const ch = start.offset
|
||||
startBlock.selection = {
|
||||
anchor: { line, ch },
|
||||
head: { line, ch }
|
||||
}
|
||||
startBlock.type = 'pre'
|
||||
const codeBlock = this.createBlock('code')
|
||||
const inputBlock = this.createBlock('span', '')
|
||||
inputBlock.functionType = 'languageInput'
|
||||
startBlock.functionType = 'fencecode'
|
||||
startBlock.lang = codeBlock.lang = ''
|
||||
const codeLines = startBlock.children
|
||||
startBlock.children = []
|
||||
codeLines.forEach(line => {
|
||||
line.functionType = 'codeLine'
|
||||
line.lang = ''
|
||||
this.appendChild(codeBlock, line)
|
||||
})
|
||||
this.appendChild(startBlock, inputBlock)
|
||||
this.appendChild(startBlock, codeBlock)
|
||||
}
|
||||
const { key } = startBlock
|
||||
const offset = 0
|
||||
startBlock.type = 'pre'
|
||||
startBlock.codeBlockStyle = 'fenced'
|
||||
startBlock.functionType = 'code'
|
||||
startBlock.history = null
|
||||
startBlock.lang = ''
|
||||
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
start: this.cursor.start,
|
||||
end: this.cursor.end
|
||||
}
|
||||
} else if (!hasFencedCodeBlockParent()) {
|
||||
const { parent, startIndex, endIndex } = this.getCommonParent()
|
||||
const children = parent ? parent.children : this.blocks
|
||||
const referBlock = children[endIndex]
|
||||
const codeBlock = this.createBlock('pre')
|
||||
codeBlock.codeBlockStyle = 'fenced'
|
||||
codeBlock.functionType = 'code'
|
||||
codeBlock.history = null
|
||||
codeBlock.lang = ''
|
||||
const preBlock = this.createBlock('pre')
|
||||
const codeBlock = this.createBlock('code')
|
||||
preBlock.functionType = 'fencecode'
|
||||
preBlock.lang = codeBlock.lang = ''
|
||||
const markdown = new ExportMarkdown(children.slice(startIndex, endIndex + 1)).generate()
|
||||
|
||||
codeBlock.text = markdown
|
||||
this.insertAfter(codeBlock, referBlock)
|
||||
markdown.split(LINE_BREAKS_REG).forEach(text => {
|
||||
const codeLine = this.createBlock('span', text)
|
||||
codeLine.lang = ''
|
||||
codeLine.functionType = 'codeLine'
|
||||
this.appendChild(codeBlock, codeLine)
|
||||
})
|
||||
const inputBlock = this.createBlock('span', '')
|
||||
inputBlock.functionType = 'languageInput'
|
||||
this.appendChild(preBlock, inputBlock)
|
||||
this.appendChild(preBlock, codeBlock)
|
||||
this.insertAfter(preBlock, referBlock)
|
||||
let i
|
||||
const removeCache = []
|
||||
for (i = startIndex; i <= endIndex; i++) {
|
||||
@ -268,7 +268,7 @@ const paragraphCtrl = ContentState => {
|
||||
removeCache.push(child)
|
||||
}
|
||||
removeCache.forEach(b => this.removeBlock(b))
|
||||
const key = codeBlock.key
|
||||
const key = codeBlock.children[0].key
|
||||
const offset = 0
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
@ -353,8 +353,8 @@ const paragraphCtrl = ContentState => {
|
||||
ContentState.prototype.insertHtmlBlock = function (block) {
|
||||
const parentBlock = this.getParent(block)
|
||||
block.text = '<div>'
|
||||
const cursorBlock = this.initHtmlBlock(parentBlock, 'div')
|
||||
const key = cursorBlock.key
|
||||
const preBlock = this.initHtmlBlock(parentBlock, 'div')
|
||||
const key = preBlock.children[0].children[1].key
|
||||
const offset = 0
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
@ -519,8 +519,30 @@ const paragraphCtrl = ContentState => {
|
||||
// if cursor is not in one line or paragraph, can not insert paragraph
|
||||
if (start.key !== end.key) return
|
||||
let block = this.getBlock(start.key)
|
||||
if (block.type === 'span') {
|
||||
if (block.type === 'span' && !block.functionType) {
|
||||
block = this.getParent(block)
|
||||
} else if (block.type === 'span' && block.functionType === 'codeLine') {
|
||||
const preBlock = this.getParent(this.getParent(block))
|
||||
switch (preBlock.functionType) {
|
||||
case 'fencecode':
|
||||
case 'indentcode':
|
||||
case 'frontmatter': {
|
||||
// You can not insert paragraph before frontmatter
|
||||
if (preBlock.functionType === 'frontmatter' && location === 'before') {
|
||||
return
|
||||
}
|
||||
block = preBlock
|
||||
break
|
||||
}
|
||||
case 'html': {
|
||||
block = this.getParent(this.getParent(preBlock))
|
||||
break
|
||||
}
|
||||
case 'multiplemath': {
|
||||
block = this.getParent(preBlock)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if (/th|td/.test(block.type)) {
|
||||
// get figure block from table cell
|
||||
block = this.getParent(this.getParent(this.getParent(this.getParent(block))))
|
||||
|
@ -43,6 +43,7 @@ const pasteCtrl = ContentState => {
|
||||
const sanitizedHtml = sanitize(html, PREVIEW_DOMPURIFY_CONFIG)
|
||||
const tempWrapper = document.createElement('div')
|
||||
tempWrapper.innerHTML = sanitizedHtml
|
||||
// special process for Number app in macOs
|
||||
const tables = Array.from(tempWrapper.querySelectorAll('table'))
|
||||
for (const table of tables) {
|
||||
const row = table.querySelector('tr')
|
||||
@ -66,15 +67,11 @@ const pasteCtrl = ContentState => {
|
||||
|
||||
// handle `normal` and `pasteAsPlainText` paste
|
||||
ContentState.prototype.pasteHandler = function (event, type) {
|
||||
if (this.checkInCodeBlock()) {
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
const text = event.clipboardData.getData('text/plain')
|
||||
let html = event.clipboardData.getData('text/html')
|
||||
|
||||
html = this.standardizeHTML(html)
|
||||
// console.log(text)
|
||||
// console.log(html)
|
||||
const copyType = this.checkCopyType(html, text)
|
||||
const { start, end } = this.cursor
|
||||
const startBlock = this.getBlock(start.key)
|
||||
@ -95,6 +92,31 @@ const pasteCtrl = ContentState => {
|
||||
}
|
||||
}
|
||||
|
||||
if (startBlock.type === 'span' && startBlock.functionType === 'codeLine') {
|
||||
let referenceBlock = startBlock
|
||||
const textList = text.split(LINE_BREAKS_REG)
|
||||
textList.forEach((line, i) => {
|
||||
if (i === 0) {
|
||||
startBlock.text += line
|
||||
} else {
|
||||
const lineBlock = this.createBlock('span', line)
|
||||
lineBlock.functionType = startBlock.functionType
|
||||
lineBlock.lang = startBlock.lang
|
||||
this.insertAfter(lineBlock, referenceBlock)
|
||||
referenceBlock = lineBlock
|
||||
if (i === textList.length - 1) {
|
||||
const { key } = lineBlock
|
||||
const offset = line.length
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return this.partialRender()
|
||||
}
|
||||
|
||||
// handle copyAsHtml
|
||||
if (copyType === 'copyAsHtml') {
|
||||
switch (type) {
|
||||
@ -196,6 +218,7 @@ const pasteCtrl = ContentState => {
|
||||
startBlock.text += firstFragment.children[0].text
|
||||
firstFragment.children.slice(1).forEach(line => {
|
||||
if (startBlock.functionType) line.functionType = startBlock.functionType
|
||||
if (startBlock.lang) line.lang = startBlock.lang
|
||||
this.appendChild(parent, line)
|
||||
})
|
||||
} else if (/^h\d$/.test(firstFragment.type)) {
|
||||
@ -234,8 +257,8 @@ const pasteCtrl = ContentState => {
|
||||
cursorBlock = startBlock
|
||||
}
|
||||
// TODO @Jocs duplicate with codes in updateCtrl.js
|
||||
if (cursorBlock && cursorBlock.type === 'span' && cursorBlock.functionType === 'multiplemath') {
|
||||
this.updateMathContent(cursorBlock)
|
||||
if (cursorBlock && cursorBlock.type === 'span' && cursorBlock.functionType === 'codeLine') {
|
||||
this.updateCodeBlocks(cursorBlock)
|
||||
}
|
||||
this.cursor = {
|
||||
start: {
|
||||
|
@ -123,7 +123,7 @@ const tabCtrl = ContentState => {
|
||||
|
||||
ContentState.prototype.insertTab = function () {
|
||||
const tabSize = this.tabSize
|
||||
const tabCharacter = ' '.repeat(tabSize)
|
||||
const tabCharacter = String.fromCharCode(160).repeat(tabSize)
|
||||
const { start, end } = this.cursor
|
||||
const startBlock = this.getBlock(start.key)
|
||||
const endBlock = this.getBlock(end.key)
|
||||
|
@ -1,9 +1,6 @@
|
||||
import selection from '../selection'
|
||||
import { tokenizer } from '../parser/parse'
|
||||
import { conflict } from '../utils'
|
||||
import { getTextContent } from '../selection/dom'
|
||||
import { CLASS_OR_ID, DEFAULT_TURNDOWN_CONFIG } from '../config'
|
||||
import { beginRules } from '../parser/rules'
|
||||
|
||||
const INLINE_UPDATE_FRAGMENTS = [
|
||||
'^([*+-]\\s)', // Bullet list
|
||||
@ -16,8 +13,6 @@ const INLINE_UPDATE_FRAGMENTS = [
|
||||
|
||||
const INLINE_UPDATE_REG = new RegExp(INLINE_UPDATE_FRAGMENTS.join('|'), 'i')
|
||||
|
||||
let lastCursor = null
|
||||
|
||||
const updateCtrl = ContentState => {
|
||||
// handle task list item checkbox click
|
||||
ContentState.prototype.listItemCheckBoxClick = function (checkbox) {
|
||||
@ -32,29 +27,41 @@ const updateCtrl = ContentState => {
|
||||
return list.children[0].isLooseListItem === isLooseType
|
||||
}
|
||||
|
||||
ContentState.prototype.checkNeedRender = function (block) {
|
||||
const { start: cStart, end: cEnd } = this.cursor
|
||||
ContentState.prototype.checkNeedRender = function (cursor = this.cursor) {
|
||||
const { start: cStart, end: cEnd } = cursor
|
||||
const startBlock = this.getBlock(cStart.key)
|
||||
const endBlock = this.getBlock(cEnd.key)
|
||||
const startOffset = cStart.offset
|
||||
const endOffset = cEnd.offset
|
||||
const tokens = tokenizer(block.text)
|
||||
const textLen = block.text.length
|
||||
|
||||
for (const token of tokens) {
|
||||
for (const token of tokenizer(startBlock.text)) {
|
||||
if (token.type === 'text') continue
|
||||
const { start, end } = token.range
|
||||
const textLen = startBlock.text.length
|
||||
if (
|
||||
conflict([Math.max(0, start - 1), Math.min(textLen, end + 1)], [startOffset, startOffset])
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for (const token of tokenizer(endBlock.text)) {
|
||||
if (token.type === 'text') continue
|
||||
const { start, end } = token.range
|
||||
const textLen = endBlock.text.length
|
||||
if (
|
||||
conflict([Math.max(0, start - 1), Math.min(textLen, end + 1)], [startOffset, startOffset]) ||
|
||||
conflict([Math.max(0, start - 1), Math.min(textLen, end + 1)], [endOffset, endOffset])
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
ContentState.prototype.checkInlineUpdate = function (block) {
|
||||
// table cell can not have blocks in it
|
||||
if (/th|td|figure/.test(block.type)) return false
|
||||
if (/codeLine|languageInput/.test(block.functionType)) return false
|
||||
// only first line block can update to other block
|
||||
if (block.type === 'span' && block.preSibling) return false
|
||||
if (block.type === 'span') {
|
||||
@ -90,13 +97,6 @@ const updateCtrl = ContentState => {
|
||||
}
|
||||
}
|
||||
|
||||
// Input @ to quick insert paragraph
|
||||
ContentState.prototype.checkQuickInsert = function (block) {
|
||||
const { type, text, functionType } = block
|
||||
if (type !== 'span' || functionType) return false
|
||||
return /^@[a-zA-Z\d]*$/.test(text)
|
||||
}
|
||||
|
||||
// thematic break
|
||||
ContentState.prototype.updateHr = function (block, marker) {
|
||||
if (block.type !== 'hr') {
|
||||
@ -293,192 +293,11 @@ const updateCtrl = ContentState => {
|
||||
return null
|
||||
}
|
||||
|
||||
ContentState.prototype.updateMathContent = function (block) {
|
||||
const preBlock = this.getParent(block)
|
||||
const mathPreview = this.getNextSibling(preBlock)
|
||||
const math = preBlock.children.map(line => line.text).join('\n')
|
||||
mathPreview.math = math
|
||||
}
|
||||
|
||||
ContentState.prototype.updateState = function (event) {
|
||||
const { start, end } = selection.getCursorRange()
|
||||
const key = start.key
|
||||
const block = this.getBlock(key)
|
||||
|
||||
// bugfix: #67 problem 1
|
||||
if (block && block.icon) return event.preventDefault()
|
||||
|
||||
if (event.type === 'click' && start.key !== end.key) {
|
||||
setTimeout(() => {
|
||||
this.updateState(event)
|
||||
})
|
||||
}
|
||||
|
||||
const { start: oldStart, end: oldEnd } = this.cursor
|
||||
if (event.type === 'input' && oldStart.key !== oldEnd.key) {
|
||||
const startBlock = this.getBlock(oldStart.key)
|
||||
const endBlock = this.getBlock(oldEnd.key)
|
||||
this.removeBlocks(startBlock, endBlock)
|
||||
// there still has little bug, when the oldstart block is `pre`, the input value will be ignored.
|
||||
// and act as `backspace`
|
||||
if (startBlock.type === 'pre' && /code|html/.test(startBlock.functionType)) {
|
||||
event.preventDefault()
|
||||
const startRemainText = startBlock.type === 'pre'
|
||||
? startBlock.text.substring(0, oldStart.offset - 1)
|
||||
: startBlock.text.substring(0, oldStart.offset)
|
||||
|
||||
const endRemainText = endBlock.type === 'pre'
|
||||
? endBlock.text.substring(oldEnd.offset - 1)
|
||||
: endBlock.text.substring(oldEnd.offset)
|
||||
|
||||
startBlock.text = startRemainText + endRemainText
|
||||
const key = oldStart.key
|
||||
const offset = oldStart.offset
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
}
|
||||
return this.partialRender()
|
||||
}
|
||||
}
|
||||
|
||||
if (start.key !== end.key) {
|
||||
if (
|
||||
start.key !== oldStart.key ||
|
||||
end.key !== oldEnd.key ||
|
||||
start.offset !== oldStart.offset ||
|
||||
end.offset !== oldEnd.offset
|
||||
) {
|
||||
this.cursor = { start, end }
|
||||
return this.partialRender()
|
||||
}
|
||||
}
|
||||
|
||||
const oldKey = lastCursor ? lastCursor.start.key : null
|
||||
const paragraph = document.querySelector(`#${key}`)
|
||||
let text = getTextContent(paragraph, [ CLASS_OR_ID['AG_MATH_RENDER'] ])
|
||||
let needRender = false
|
||||
let needRenderAll = false
|
||||
if (event.type === 'click' && block.type === 'figure' && block.functionType === 'table') {
|
||||
// first cell in thead
|
||||
const cursorBlock = block.children[1].children[0].children[0].children[0]
|
||||
const offset = cursorBlock.text.length
|
||||
const key = cursorBlock.key
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
}
|
||||
return this.partialRender()
|
||||
}
|
||||
|
||||
// update '```xxx' to code block when you click other place or use press arrow key.
|
||||
if (block && key !== oldKey) {
|
||||
const oldBlock = this.getBlock(oldKey)
|
||||
if (oldBlock) this.codeBlockUpdate(oldBlock)
|
||||
}
|
||||
|
||||
if (block && block.type === 'pre' && /code|html/.test(block.functionType)) {
|
||||
if (block.key !== oldKey) {
|
||||
this.cursor = lastCursor = { start, end }
|
||||
if (event.type === 'click' && oldKey) {
|
||||
this.partialRender()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// auto pair (not need to auto pair in math block)
|
||||
if (block && block.text !== text) {
|
||||
const BRACKET_HASH = {
|
||||
'{': '}',
|
||||
'[': ']',
|
||||
'(': ')',
|
||||
'*': '*',
|
||||
'_': '_',
|
||||
'"': '"',
|
||||
'\'': '\''
|
||||
}
|
||||
|
||||
if (start.key === end.key && start.offset === end.offset && event.type === 'input' && block.functionType !== 'multiplemath') {
|
||||
const { offset } = start
|
||||
const { autoPairBracket, autoPairMarkdownSyntax, autoPairQuote } = this
|
||||
const inputChar = text.charAt(+offset - 1)
|
||||
const preInputChar = text.charAt(+offset - 2)
|
||||
const postInputChar = text.charAt(+offset)
|
||||
/* eslint-disable no-useless-escape */
|
||||
if (
|
||||
(event.inputType.indexOf('delete') === -1) &&
|
||||
(inputChar === postInputChar) &&
|
||||
(
|
||||
(autoPairQuote && /[']{1}/.test(inputChar)) ||
|
||||
(autoPairQuote && /["]{1}/.test(inputChar)) ||
|
||||
(autoPairBracket && /[\}\]\)]{1}/.test(inputChar)) ||
|
||||
(autoPairMarkdownSyntax && /[*_]{1}/.test(inputChar))
|
||||
)
|
||||
) {
|
||||
text = text.substring(0, offset) + text.substring(offset + 1)
|
||||
} else {
|
||||
/* eslint-disable no-useless-escape */
|
||||
// Not Unicode aware, since things like \p{Alphabetic} or \p{L} are not supported yet
|
||||
if (
|
||||
(autoPairQuote && /[']{1}/.test(inputChar) && !(/[a-zA-Z\d]{1}/.test(preInputChar))) ||
|
||||
(autoPairQuote && /["]{1}/.test(inputChar)) ||
|
||||
(autoPairBracket && /[\{\[\(]{1}/.test(inputChar)) ||
|
||||
(autoPairMarkdownSyntax && /[*_]{1}/.test(inputChar))
|
||||
) {
|
||||
text = BRACKET_HASH[event.data]
|
||||
? text.substring(0, offset) + BRACKET_HASH[inputChar] + text.substring(offset)
|
||||
: text
|
||||
}
|
||||
/* eslint-enable no-useless-escape */
|
||||
if (/\s/.test(event.data) && preInputChar === '*' && postInputChar === '*') {
|
||||
text = text.substring(0, offset) + text.substring(offset + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
block.text = text
|
||||
if (beginRules['reference_definition'].test(text)) {
|
||||
needRenderAll = true
|
||||
}
|
||||
}
|
||||
|
||||
// Update preview content of math block
|
||||
if (block && block.type === 'span' && block.functionType === 'multiplemath') {
|
||||
this.updateMathContent(block)
|
||||
}
|
||||
|
||||
if (oldKey !== key || oldStart.offset !== start.offset || oldEnd.offset !== end.offset) {
|
||||
needRender = true
|
||||
}
|
||||
|
||||
this.cursor = lastCursor = { start, end }
|
||||
const checkMarkedUpdate = this.checkNeedRender(block)
|
||||
|
||||
if (event.type === 'input') {
|
||||
const rect = paragraph.getBoundingClientRect()
|
||||
const checkQuickInsert = this.checkQuickInsert(block)
|
||||
const reference = this.getPositionReference()
|
||||
reference.getBoundingClientRect = function () {
|
||||
const { x, y, left, top, height, bottom } = rect
|
||||
|
||||
return Object.assign({}, {
|
||||
left,
|
||||
x,
|
||||
top,
|
||||
y,
|
||||
bottom,
|
||||
height,
|
||||
width: 0,
|
||||
right: left
|
||||
})
|
||||
}
|
||||
this.muya.eventCenter.dispatch('muya-quick-insert', reference, block, checkQuickInsert)
|
||||
}
|
||||
|
||||
const inlineUpdatedBlock = this.isCollapse() && !/frontmatter|multiplemath/.test(block.functionType) && this.checkInlineUpdate(block)
|
||||
if (checkMarkedUpdate || inlineUpdatedBlock || needRender) {
|
||||
needRenderAll ? this.render() : this.partialRender()
|
||||
}
|
||||
ContentState.prototype.updateCodeBlocks = function (block) {
|
||||
const codeBlock = this.getParent(block)
|
||||
const preBlock = this.getParent(codeBlock)
|
||||
const code = codeBlock.children.map(line => line.text).join('\n')
|
||||
this.codeBlocks.set(preBlock.key, code)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,10 +12,6 @@ class ClickEvent {
|
||||
const { container, eventCenter, contentState } = this.muya
|
||||
const handler = event => {
|
||||
const { target } = event
|
||||
// handler code block click.
|
||||
if (target.tagName === 'PRE' && target.classList.contains(CLASS_OR_ID['AG_CODE_BLOCK'])) {
|
||||
contentState.focusCodeBlock(event)
|
||||
}
|
||||
// handler table | html toolbar click
|
||||
const toolItem = getToolItem(target)
|
||||
if (toolItem) {
|
||||
@ -56,13 +52,7 @@ class ClickEvent {
|
||||
if (target.tagName === 'INPUT' && target.classList.contains(CLASS_OR_ID['AG_TASK_LIST_ITEM_CHECKBOX'])) {
|
||||
contentState.listItemCheckBoxClick(target)
|
||||
}
|
||||
// is show format float box?
|
||||
const { start, end } = selection.getCursorRange()
|
||||
if (start.key === end.key && start.offset !== end.offset) {
|
||||
const reference = contentState.getPositionReference()
|
||||
const { formats } = contentState.selectionFormats()
|
||||
eventCenter.dispatch('muya-format-picker', { reference, formats })
|
||||
}
|
||||
contentState.clickHandler(event)
|
||||
}
|
||||
|
||||
eventCenter.attachDOMEvent(container, 'click', handler)
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { EVENT_KEYS, CLASS_OR_ID } from '../config'
|
||||
import { EVENT_KEYS } from '../config'
|
||||
import selection from '../selection'
|
||||
import { findNearestParagraph } from '../selection/dom'
|
||||
import { getParagraphReference } from '../utils'
|
||||
import { checkEditLanguage } from '../codeMirror/language'
|
||||
import { checkEditLanguage } from '../prism/index'
|
||||
import { checkEditEmoji } from '../ui/emojis'
|
||||
|
||||
class Keyboard {
|
||||
@ -11,7 +11,7 @@ class Keyboard {
|
||||
this._isEditChinese = false
|
||||
this.shownFloat = new Set()
|
||||
this.recordEditChinese()
|
||||
this.dispatchUpdateState()
|
||||
this.dispatchEditorState()
|
||||
this.keydownBinding()
|
||||
this.keyupBinding()
|
||||
this.inputBinding()
|
||||
@ -39,32 +39,25 @@ class Keyboard {
|
||||
eventCenter.attachDOMEvent(container, 'compositionstart', handler)
|
||||
}
|
||||
|
||||
dispatchUpdateState () {
|
||||
dispatchEditorState () {
|
||||
const { container, eventCenter, contentState } = this.muya
|
||||
|
||||
let timer = null
|
||||
const changeHandler = event => {
|
||||
const target = event.target
|
||||
if (event.type === 'click' && target.classList.contains(CLASS_OR_ID['AG_FUNCTION_HTML'])) return
|
||||
if (event.type === 'keyup' && (event.key === EVENT_KEYS.ArrowUp || event.key === EVENT_KEYS.ArrowDown) && this.shownFloat.size > 0) return
|
||||
if (!this._isEditChinese) {
|
||||
contentState.updateState(event)
|
||||
}
|
||||
if (event.type === 'click' || event.type === 'keyup') {
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
const selectionChanges = contentState.selectionChange()
|
||||
const { formats } = contentState.selectionFormats()
|
||||
eventCenter.dispatch('selectionChange', selectionChanges)
|
||||
eventCenter.dispatch('selectionFormats', formats)
|
||||
this.muya.dispatchChange()
|
||||
})
|
||||
}
|
||||
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
const selectionChanges = contentState.selectionChange()
|
||||
const { formats } = contentState.selectionFormats()
|
||||
eventCenter.dispatch('selectionChange', selectionChanges)
|
||||
eventCenter.dispatch('selectionFormats', formats)
|
||||
this.muya.dispatchChange()
|
||||
})
|
||||
}
|
||||
|
||||
eventCenter.attachDOMEvent(container, 'click', changeHandler)
|
||||
eventCenter.attachDOMEvent(container, 'keyup', changeHandler)
|
||||
eventCenter.attachDOMEvent(container, 'input', changeHandler)
|
||||
}
|
||||
|
||||
keydownBinding () {
|
||||
@ -123,7 +116,7 @@ class Keyboard {
|
||||
|
||||
inputBinding () {
|
||||
const { container, eventCenter, contentState } = this.muya
|
||||
const inputHandler = _ => {
|
||||
const inputHandler = event => {
|
||||
const node = selection.getSelectionStart()
|
||||
const paragraph = findNearestParagraph(node)
|
||||
const selectionState = selection.exportSelection(paragraph)
|
||||
@ -136,6 +129,12 @@ class Keyboard {
|
||||
contentState.selectLanguage(paragraph, item.name)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// hide code picker float box
|
||||
eventCenter.dispatch('muya-code-picker', { reference: null })
|
||||
}
|
||||
if (!this._isEditChinese) {
|
||||
contentState.inputHandler(event)
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,7 +153,9 @@ class Keyboard {
|
||||
emojiNode &&
|
||||
event.key !== EVENT_KEYS.Enter &&
|
||||
event.key !== EVENT_KEYS.ArrowDown &&
|
||||
event.key !== EVENT_KEYS.ArrowUp
|
||||
event.key !== EVENT_KEYS.ArrowUp &&
|
||||
event.key !== EVENT_KEYS.Tab &&
|
||||
event.key !== EVENT_KEYS.Escape
|
||||
) {
|
||||
const reference = getParagraphReference(emojiNode, paragraph.id)
|
||||
eventCenter.dispatch('muya-emoji-picker', {
|
||||
@ -169,7 +170,8 @@ class Keyboard {
|
||||
}
|
||||
// is show format float box?
|
||||
const { start, end } = selection.getCursorRange()
|
||||
if (start.key === end.key && start.offset !== end.offset) {
|
||||
const block = contentState.getBlock(start.key)
|
||||
if (start.key === end.key && start.offset !== end.offset && block.functionType !== 'codeLine') {
|
||||
const reference = contentState.getPositionReference()
|
||||
const { formats } = contentState.selectionFormats()
|
||||
eventCenter.dispatch('muya-format-picker', { reference, formats })
|
||||
|
@ -3,7 +3,7 @@ import EventCenter from './eventHandler/event'
|
||||
import Clipboard from './eventHandler/clipboard'
|
||||
import Keyboard from './eventHandler/keyboard'
|
||||
import ClickEvent from './eventHandler/clickEvent'
|
||||
import { CLASS_OR_ID, codeMirrorConfig } from './config'
|
||||
import { CLASS_OR_ID } from './config'
|
||||
import { wordCount } from './utils'
|
||||
import ExportMarkdown from './utils/exportMarkdown'
|
||||
import ExportHtml from './utils/exportHtml'
|
||||
@ -137,14 +137,10 @@ class Muya {
|
||||
setTheme (name) {
|
||||
if (!name) return
|
||||
const { eventCenter } = this
|
||||
if (name === 'dark') {
|
||||
codeMirrorConfig.theme = 'railscasts'
|
||||
} else {
|
||||
delete codeMirrorConfig.theme
|
||||
}
|
||||
this.theme = name
|
||||
// Render cursor and refresh code block
|
||||
this.contentState.render(true, true)
|
||||
this.contentState.render(true)
|
||||
// notice the ui components to change theme
|
||||
eventCenter.dispatch('theme-change', name)
|
||||
}
|
||||
|
||||
|
@ -7,9 +7,9 @@ import renderInlines from './renderInlines'
|
||||
import renderBlock from './renderBlock'
|
||||
|
||||
class StateRender {
|
||||
constructor (eventCenter) {
|
||||
this.eventCenter = eventCenter
|
||||
this.refreshCodeBlock = false
|
||||
constructor (muya) {
|
||||
this.muya = muya
|
||||
this.eventCenter = muya.eventCenter
|
||||
this.loadImageMap = new Map()
|
||||
this.loadMathMap = new Map()
|
||||
this.tokenCache = new Map()
|
||||
@ -81,14 +81,10 @@ class StateRender {
|
||||
if (type === 'span') {
|
||||
selector += `.${CLASS_OR_ID['AG_LINE']}`
|
||||
}
|
||||
if (block.temp) {
|
||||
selector += `.${CLASS_OR_ID['AG_TEMP']}`
|
||||
}
|
||||
return selector
|
||||
}
|
||||
|
||||
render (blocks, cursor, activeBlocks, matches, refreshCodeBlock) {
|
||||
this.refreshCodeBlock = refreshCodeBlock
|
||||
render (blocks, cursor, activeBlocks, matches) {
|
||||
const selector = `div#${CLASS_OR_ID['AG_EDITOR_ID']}`
|
||||
|
||||
const children = blocks.map(block => {
|
||||
|
@ -1,6 +1,14 @@
|
||||
import { CLASS_OR_ID } from '../../../config'
|
||||
import { h } from '../snabbdom'
|
||||
|
||||
const PRE_BLOCK_HASH = {
|
||||
'fencecode': `.${CLASS_OR_ID['AG_FENCE_CODE']}`,
|
||||
'indentcode': `.${CLASS_OR_ID['AG_INDENT_CODE']}`,
|
||||
'html': `.${CLASS_OR_ID['AG_HTML_BLOCK']}`,
|
||||
'frontmatter': `.${CLASS_OR_ID['AG_FRONT_MATTER']}`,
|
||||
'multiplemath': `.${CLASS_OR_ID['AG_MULTIPLE_MATH']}`
|
||||
}
|
||||
|
||||
export default function renderContainerBlock (block, cursor, activeBlocks, matches, useCache = false) {
|
||||
let selector = this.getSelector(block, cursor, activeBlocks)
|
||||
const data = {
|
||||
@ -76,11 +84,19 @@ export default function renderContainerBlock (block, cursor, activeBlocks, match
|
||||
if (block.type === 'ol') {
|
||||
Object.assign(data.attrs, { start: block.start })
|
||||
}
|
||||
if (block.type === 'pre' && /frontmatter|multiplemath/.test(block.functionType)) {
|
||||
const role = block.functionType === 'frontmatter' ? 'YAML' : 'MATH'
|
||||
const className = block.functionType === 'frontmatter' ? CLASS_OR_ID['AG_FRONT_MATTER'] : CLASS_OR_ID['AG_MULTIPLE_MATH']
|
||||
Object.assign(data.dataset, { role })
|
||||
selector += `.${className}`
|
||||
if (block.type === 'code') {
|
||||
const { lang } = block
|
||||
if (lang) {
|
||||
selector += `.language-${lang}`
|
||||
}
|
||||
}
|
||||
if (block.type === 'pre') {
|
||||
const { lang, functionType } = block
|
||||
if (lang) {
|
||||
selector += `.language-${lang}`
|
||||
}
|
||||
Object.assign(data.dataset, { role: functionType })
|
||||
selector += PRE_BLOCK_HASH[block.functionType]
|
||||
}
|
||||
|
||||
return h(selector, data, block.children.map(child => this.renderBlock(child, cursor, activeBlocks, matches, useCache)))
|
||||
|
@ -1,17 +1,28 @@
|
||||
import katex from 'katex'
|
||||
import { CLASS_OR_ID, DEVICE_MEMORY, isInElectron } from '../../../config'
|
||||
import prism, { loadedCache } from '../../../prism/'
|
||||
import { CLASS_OR_ID, DEVICE_MEMORY, isInElectron, PREVIEW_DOMPURIFY_CONFIG } from '../../../config'
|
||||
import { tokenizer } from '../../parse'
|
||||
import { snakeToCamel } from '../../../utils'
|
||||
import { snakeToCamel, sanitize, escapeHtml } from '../../../utils'
|
||||
import { h, htmlToVNode } from '../snabbdom'
|
||||
|
||||
const PRE_BLOCK_HASH = {
|
||||
'code': `.${CLASS_OR_ID['AG_CODE_BLOCK']}`,
|
||||
'html': `.${CLASS_OR_ID['AG_HTML_BLOCK']}`,
|
||||
'frontmatter': `.${CLASS_OR_ID['AG_FRONT_MATTER']}`
|
||||
const getHighlightHtml = (text, highlights) => {
|
||||
let code = ''
|
||||
let pos = 0
|
||||
for (const highlight of highlights) {
|
||||
const { start, end, active } = highlight
|
||||
code += text.substring(pos, start)
|
||||
const className = active ? 'ag-highlight' : 'ag-selection'
|
||||
code += `<span class="${className}">${text.substring(start, end)}</span>`
|
||||
pos = end
|
||||
}
|
||||
if (pos !== text.length) {
|
||||
code += text.substring(pos)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
export default function renderLeafBlock (block, cursor, activeBlocks, matches, useCache = false) {
|
||||
const { loadMathMap, refreshCodeBlock } = this
|
||||
const { loadMathMap } = this
|
||||
let selector = this.getSelector(block, cursor, activeBlocks)
|
||||
// highlight search key in block
|
||||
const highlights = matches.filter(m => m.key === block.key)
|
||||
@ -20,17 +31,15 @@ export default function renderLeafBlock (block, cursor, activeBlocks, matches, u
|
||||
type,
|
||||
headingStyle,
|
||||
align,
|
||||
htmlContent,
|
||||
icon,
|
||||
checked,
|
||||
key,
|
||||
lang,
|
||||
functionType,
|
||||
codeBlockStyle,
|
||||
math,
|
||||
editable
|
||||
} = block
|
||||
const data = {
|
||||
props: {},
|
||||
attrs: {},
|
||||
dataset: {}
|
||||
}
|
||||
@ -57,15 +66,17 @@ export default function renderLeafBlock (block, cursor, activeBlocks, matches, u
|
||||
style: `text-align:${align}`
|
||||
})
|
||||
} else if (type === 'div') {
|
||||
if (typeof htmlContent === 'string') {
|
||||
if (functionType === 'preview') {
|
||||
selector += `.${CLASS_OR_ID['AG_HTML_PREVIEW']}`
|
||||
const htmlContent = sanitize(this.muya.contentState.codeBlocks.get(block.preSibling), PREVIEW_DOMPURIFY_CONFIG)
|
||||
// handle empty html bock
|
||||
if (/<([a-z][a-z\d]*).*>\s*<\/\1>/.test(htmlContent)) {
|
||||
children = htmlToVNode('<div class="ag-empty"><Empty HTML Block></div>')
|
||||
} else {
|
||||
children = htmlToVNode(htmlContent)
|
||||
}
|
||||
} else if (typeof math === 'string') {
|
||||
} else if (functionType === 'multiplemath') {
|
||||
const math = this.muya.contentState.codeBlocks.get(block.preSibling)
|
||||
const key = `${math}_display_math`
|
||||
selector += `.${CLASS_OR_ID['AG_MATH_PREVIEW']}`
|
||||
if (math === '') {
|
||||
@ -122,42 +133,32 @@ export default function renderLeafBlock (block, cursor, activeBlocks, matches, u
|
||||
selector += `.${CLASS_OR_ID['AG_CHECKBOX_CHECKED']}`
|
||||
}
|
||||
children = ''
|
||||
} else if (type === 'pre') {
|
||||
selector += `.${CLASS_OR_ID['AG_CODEMIRROR_BLOCK']}`
|
||||
selector += PRE_BLOCK_HASH[functionType]
|
||||
Object.assign(data.attrs, { contenteditable: 'false' })
|
||||
data.hook = {
|
||||
prepatch (oldvnode, vnode) {
|
||||
// cheat snabbdom that the pre block is not changed!!!
|
||||
if (!refreshCodeBlock) {
|
||||
vnode.children = oldvnode.children
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lang) {
|
||||
Object.assign(data.dataset, {
|
||||
lang
|
||||
})
|
||||
} else if (type === 'span' && functionType === 'codeLine') {
|
||||
let code
|
||||
if (lang && lang === 'markup') {
|
||||
code = getHighlightHtml(escapeHtml(text), highlights)
|
||||
} else {
|
||||
code = getHighlightHtml(text, highlights)
|
||||
}
|
||||
|
||||
if (codeBlockStyle) {
|
||||
Object.assign(data.dataset, {
|
||||
codeBlockStyle
|
||||
})
|
||||
}
|
||||
selector += `.${CLASS_OR_ID['AG_CODE_LINE']}`
|
||||
|
||||
if (/code|html/.test(functionType)) {
|
||||
// do not set it to '' (empty string)
|
||||
children = []
|
||||
if (lang && /\S/.test(code) && loadedCache.has(lang)) {
|
||||
const wrapper = document.createElement('div')
|
||||
wrapper.classList.add(`language-${lang}`)
|
||||
wrapper.innerHTML = code
|
||||
prism.highlightElement(wrapper, false, function () {
|
||||
const highlightedCode = this.innerHTML
|
||||
selector += `.language-${lang}`
|
||||
children = htmlToVNode(highlightedCode)
|
||||
})
|
||||
} else {
|
||||
children = htmlToVNode(code)
|
||||
}
|
||||
} else if (type === 'span' && /frontmatter|multiplemath/.test(functionType)) {
|
||||
if (functionType === 'frontmatter') {
|
||||
selector += `.${CLASS_OR_ID['AG_FRONT_MATTER_LINE']}`
|
||||
}
|
||||
if (functionType === 'multiplemath') {
|
||||
selector += `.${CLASS_OR_ID['AG_MULTIPLE_MATH_LINE']}`
|
||||
}
|
||||
children = text
|
||||
} else if (type === 'span' && functionType === 'languageInput') {
|
||||
const html = getHighlightHtml(text, highlights)
|
||||
selector += `.${CLASS_OR_ID['AG_LANGUAGE_INPUT']}`
|
||||
children = htmlToVNode(html)
|
||||
}
|
||||
|
||||
return h(selector, data, children)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import virtualize from 'snabbdom-virtualize/strings'
|
||||
// import virtualize from 'snabbdom-virtualize/strings'
|
||||
const snabbdom = require('snabbdom')
|
||||
|
||||
export const patch = snabbdom.init([ // Init patch function with chosen modules
|
||||
@ -10,6 +10,10 @@ export const patch = snabbdom.init([ // Init patch function with chosen modules
|
||||
require('snabbdom/modules/eventlisteners').default // attaches event listeners
|
||||
])
|
||||
export const h = require('snabbdom/h').default // helper function for creating vnodes
|
||||
export const toHTML = require('snabbdom-to-html') // helper function for convert DOM to HTML string
|
||||
export const toHTML = require('snabbdom-to-html') // helper function for convert vnode to HTML string
|
||||
export const toVNode = require('snabbdom/tovnode').default // helper function for convert DOM to vnode
|
||||
export const htmlToVNode = virtualize // helper function for convert HTML string to vnode
|
||||
export const htmlToVNode = html => { // helper function for convert html to vnode
|
||||
const wrapper = document.createElement('div')
|
||||
wrapper.innerHTML = html
|
||||
return toVNode(wrapper).children
|
||||
}
|
||||
|
49
src/muya/lib/prism/index.js
Normal file
49
src/muya/lib/prism/index.js
Normal file
@ -0,0 +1,49 @@
|
||||
import Prism from 'prismjs2'
|
||||
import { filter } from 'fuzzaldrin'
|
||||
import initLoadLanguage, { loadedCache } from './loadLanguage'
|
||||
import languages from './languages'
|
||||
|
||||
const prism = Prism
|
||||
window.Prism = Prism
|
||||
import('prismjs2/plugins/keep-markup/prism-keep-markup')
|
||||
const langs = Object.keys(languages).map(name => (languages[name]))
|
||||
const loadLanguage = initLoadLanguage(Prism)
|
||||
|
||||
/**
|
||||
* check edit language
|
||||
*/
|
||||
const checkEditLanguage = (paragraph, selectionState) => {
|
||||
const text = paragraph.textContent
|
||||
const { start } = selectionState
|
||||
const token = text.match(/(^`{3,})([^`]+)/)
|
||||
if (paragraph.tagName !== 'SPAN') return false
|
||||
if (token) {
|
||||
const len = token[1].length
|
||||
const lang = token[2].trim()
|
||||
if (start < len) return false
|
||||
if (!lang) return false
|
||||
return lang
|
||||
} else if (paragraph.classList.contains('ag-language-input')) {
|
||||
return text.trim()
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const search = text => {
|
||||
return filter(langs, text, { key: 'name' })
|
||||
}
|
||||
|
||||
// pre load latex and yaml and html for `math block` \ `front matter` and `html block`
|
||||
loadLanguage('latex')
|
||||
loadLanguage('yaml')
|
||||
|
||||
export {
|
||||
search,
|
||||
loadLanguage,
|
||||
loadedCache,
|
||||
languages,
|
||||
checkEditLanguage
|
||||
}
|
||||
|
||||
export default prism
|
1135
src/muya/lib/prism/languages.json
Normal file
1135
src/muya/lib/prism/languages.json
Normal file
File diff suppressed because it is too large
Load Diff
87
src/muya/lib/prism/loadLanguage.js
Normal file
87
src/muya/lib/prism/loadLanguage.js
Normal file
@ -0,0 +1,87 @@
|
||||
import languages from './languages'
|
||||
let peerDependentsMap = null
|
||||
export const loadedCache = new Set(['markup', 'css', 'clike', 'javascript'])
|
||||
|
||||
function getPeerDependentsMap () {
|
||||
const peerDependentsMap = {}
|
||||
Object.keys(languages).forEach(function (language) {
|
||||
if (language === 'meta') {
|
||||
return false
|
||||
}
|
||||
if (languages[language].peerDependencies) {
|
||||
let peerDependencies = languages[language].peerDependencies
|
||||
if (!Array.isArray(peerDependencies)) {
|
||||
peerDependencies = [peerDependencies]
|
||||
}
|
||||
peerDependencies.forEach(function (peerDependency) {
|
||||
if (!peerDependentsMap[peerDependency]) {
|
||||
peerDependentsMap[peerDependency] = []
|
||||
}
|
||||
peerDependentsMap[peerDependency].push(language)
|
||||
})
|
||||
}
|
||||
})
|
||||
return peerDependentsMap
|
||||
}
|
||||
|
||||
function getPeerDependents (mainLanguage) {
|
||||
if (!peerDependentsMap) {
|
||||
peerDependentsMap = getPeerDependentsMap()
|
||||
}
|
||||
return peerDependentsMap[mainLanguage] || []
|
||||
}
|
||||
|
||||
function initLoadLanguage (Prism) {
|
||||
return function loadLanguages (arr, withoutDependencies) {
|
||||
// If no argument is passed, load all components
|
||||
if (!arr) {
|
||||
arr = Object.keys(languages).filter(function (language) {
|
||||
return language !== 'meta'
|
||||
})
|
||||
}
|
||||
if (arr && !arr.length) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!Array.isArray(arr)) {
|
||||
arr = [arr]
|
||||
}
|
||||
|
||||
arr.forEach(function (language) {
|
||||
if (!languages[language]) {
|
||||
console.warn('Language does not exist ' + language)
|
||||
return
|
||||
}
|
||||
if (loadedCache.has(language)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Load dependencies first
|
||||
if (!withoutDependencies && languages[language].require) {
|
||||
loadLanguages(languages[language].require)
|
||||
}
|
||||
|
||||
delete Prism.languages[language]
|
||||
import('prismjs2/components/prism-' + language)
|
||||
.then(_ => {
|
||||
loadedCache.add(language)
|
||||
})
|
||||
|
||||
// Reload dependents
|
||||
const dependents = getPeerDependents(language).filter(function (dependent) {
|
||||
// If dependent language was already loaded,
|
||||
// we want to reload it.
|
||||
if (Prism.languages[dependent]) {
|
||||
delete Prism.languages[dependent]
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if (dependents.length) {
|
||||
loadLanguages(dependents, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default initLoadLanguage
|
@ -842,7 +842,6 @@ class Selection {
|
||||
if (range.getClientRects) {
|
||||
range.collapse(true)
|
||||
const rects = range.getClientRects()
|
||||
|
||||
if (rects.length) {
|
||||
const { left, top, x: rectX, y: rectY } = rects[0]
|
||||
x = rectX || left
|
||||
|
@ -1,6 +1,6 @@
|
||||
import BaseScrollFloat from '../baseScrollFloat'
|
||||
import { patch, h } from '../../parser/render/snabbdom'
|
||||
import { search } from '../../codeMirror'
|
||||
import { search } from '../../prism/index'
|
||||
import fileIcons from '../fileIcons'
|
||||
|
||||
import './index.css'
|
||||
@ -19,10 +19,8 @@ class CodePicker extends BaseScrollFloat {
|
||||
super.listen()
|
||||
const { eventCenter } = this.muya
|
||||
eventCenter.subscribe('muya-code-picker', ({ reference, lang, cb }) => {
|
||||
const modes = search(lang).map(mode => {
|
||||
return Object.assign(mode, { text: mode.name })
|
||||
})
|
||||
if (modes.length) {
|
||||
const modes = search(lang)
|
||||
if (modes.length && reference) {
|
||||
this.show(reference, cb)
|
||||
this.renderArray = modes
|
||||
this.activeItem = modes[0]
|
||||
@ -37,19 +35,19 @@ class CodePicker extends BaseScrollFloat {
|
||||
const { renderArray, oldVnode, scrollElement, activeItem } = this
|
||||
let children = renderArray.map(item => {
|
||||
let iconClassNames
|
||||
if (item.mode.ext && Array.isArray(item.mode.ext)) {
|
||||
for (const ext of item.mode.ext) {
|
||||
if (item.ext && Array.isArray(item.ext)) {
|
||||
for (const ext of item.ext) {
|
||||
iconClassNames = fileIcons.getClassWithColor(`fackname.${ext}`)
|
||||
if (iconClassNames) break
|
||||
}
|
||||
} else if (item.mode.name) {
|
||||
iconClassNames = fileIcons.getClassWithColor(item.mode.name)
|
||||
} else if (item.name) {
|
||||
iconClassNames = fileIcons.getClassWithColor(item.name)
|
||||
}
|
||||
|
||||
// Because `markdown mode in Codemirror` don't have extensions.
|
||||
// if still can not get the className, add a common className 'atom-icon light-cyan'
|
||||
if (!iconClassNames) {
|
||||
iconClassNames = item.text === 'markdown' ? fileIcons.getClassWithColor('fackname.md') : 'atom-icon light-cyan'
|
||||
iconClassNames = item.name === 'markdown' ? fileIcons.getClassWithColor('fackname.md') : 'atom-icon light-cyan'
|
||||
}
|
||||
const iconSelector = 'span' + iconClassNames.split(/\s/).map(s => `.${s}`).join('')
|
||||
const icon = h('div.icon-wrapper', h(iconSelector))
|
||||
|
File diff suppressed because it is too large
Load Diff
13958
src/muya/lib/ui/emojis/emojisJson.json
Normal file
13958
src/muya/lib/ui/emojis/emojisJson.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -29,11 +29,8 @@ export const validEmoji = text => {
|
||||
*/
|
||||
|
||||
export const checkEditEmoji = node => {
|
||||
const preSibling = node.previousElementSibling
|
||||
if (node.classList.contains(CLASS_OR_ID['AG_EMOJI_MARKED_TEXT'])) {
|
||||
return node
|
||||
} else if (preSibling && preSibling.classList.contains(CLASS_OR_ID['AG_EMOJI_MARKED_TEXT'])) {
|
||||
return preSibling
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import marked from '../parser/marked'
|
||||
import highlight from 'highlight.js'
|
||||
import Prism from 'prismjs2'
|
||||
import katex from 'katex'
|
||||
import githubMarkdownCss from 'github-markdown-css/github-markdown.css'
|
||||
import highlightCss from 'highlight.js/styles/default.css'
|
||||
import highlightCss from 'prismjs2/themes/prism.css'
|
||||
import katexCss from 'katex/dist/katex.css'
|
||||
import { EXPORT_DOMPURIFY_CONFIG } from '../config'
|
||||
import { sanitize } from '../utils'
|
||||
@ -20,8 +20,8 @@ class ExportHtml {
|
||||
// render pure html by marked
|
||||
renderHtml () {
|
||||
return marked(this.markdown, {
|
||||
highlight (code) {
|
||||
return highlight.highlightAuto(code).value
|
||||
highlight (code, lang) {
|
||||
return Prism.highlight(code, Prism.languages[lang], lang)
|
||||
},
|
||||
emojiRenderer (emoji) {
|
||||
const validate = validEmoji(emoji)
|
||||
|
@ -7,7 +7,7 @@
|
||||
* and GitHub Flavored Markdown Spec: https://github.github.com/gfm/
|
||||
* The output markdown needs to obey the standards of the two Spec.
|
||||
*/
|
||||
const LINE_BREAKS = /\n/
|
||||
// const LINE_BREAKS = /\n/
|
||||
|
||||
class ExportMarkdown {
|
||||
constructor (blocks) {
|
||||
@ -153,10 +153,10 @@ class ExportMarkdown {
|
||||
return this.translateBlocks2Markdown(children, newIndent)
|
||||
}
|
||||
|
||||
normalizeFrontMatter (block, indent) {
|
||||
normalizeFrontMatter (block, indent) { // preBlock
|
||||
const result = []
|
||||
result.push('---\n')
|
||||
for (const line of block.children) {
|
||||
for (const line of block.children[0].children) {
|
||||
result.push(`${line.text}\n`)
|
||||
}
|
||||
result.push('---\n')
|
||||
@ -166,7 +166,7 @@ class ExportMarkdown {
|
||||
normalizeMultipleMath (block, /* figure */ indent) {
|
||||
const result = []
|
||||
result.push('$$\n')
|
||||
for (const line of block.children[0].children) {
|
||||
for (const line of block.children[0].children[0].children) {
|
||||
result.push(`${line.text}\n`)
|
||||
}
|
||||
result.push('$$\n')
|
||||
@ -175,9 +175,9 @@ class ExportMarkdown {
|
||||
|
||||
normalizeCodeBlock (block, indent) {
|
||||
const result = []
|
||||
const textList = block.text.split(LINE_BREAKS)
|
||||
const { codeBlockStyle } = block
|
||||
if (codeBlockStyle === 'fenced') {
|
||||
const textList = block.children[1].children.map(codeLine => codeLine.text)
|
||||
const { functionType } = block
|
||||
if (functionType === 'fencecode') {
|
||||
result.push(`${indent}${block.lang ? '```' + block.lang + '\n' : '```\n'}`)
|
||||
textList.forEach(text => {
|
||||
result.push(`${indent}${text}\n`)
|
||||
@ -192,12 +192,11 @@ class ExportMarkdown {
|
||||
return result.join('')
|
||||
}
|
||||
|
||||
normalizeHTML (block, indent) {
|
||||
normalizeHTML (block, indent) { // figure
|
||||
const result = []
|
||||
const codeContent = block.children[1].children[0].text
|
||||
const textList = codeContent.split(LINE_BREAKS)
|
||||
for (const text of textList) {
|
||||
result.push(`${indent}${text}\n`)
|
||||
const codeLines = block.children[1].children[0].children[0].children
|
||||
for (const line of codeLines) {
|
||||
result.push(`${indent}${line.text}\n`)
|
||||
}
|
||||
return result.join('')
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
import { Lexer } from '../parser/marked'
|
||||
import ExportMarkdown from './exportMarkdown'
|
||||
import TurndownService, { usePluginAddRules } from './turndownService'
|
||||
import { loadLanguage } from '../prism/index'
|
||||
|
||||
// To be disabled rules when parse markdown, Because content state don't need to parse inline rules
|
||||
import { CURSOR_DNA, TABLE_TOOLS } from '../config'
|
||||
@ -27,7 +28,6 @@ const importRegister = ContentState => {
|
||||
}
|
||||
|
||||
const tokens = new Lexer({ disableInline: true }).lex(markdown)
|
||||
console.log(JSON.stringify(tokens, null, 2))
|
||||
|
||||
let token
|
||||
let block
|
||||
@ -39,15 +39,21 @@ const importRegister = ContentState => {
|
||||
case 'frontmatter': {
|
||||
value = token.text
|
||||
block = this.createBlock('pre')
|
||||
const lines = value
|
||||
const codeBlock = this.createBlock('code')
|
||||
value
|
||||
.replace(/^\s+/, '')
|
||||
.replace(/\s$/, '')
|
||||
.split(LINE_BREAKS_REG).map(line => this.createBlock('span', line))
|
||||
for (const line of lines) {
|
||||
line.functionType = token.type
|
||||
this.appendChild(block, line)
|
||||
}
|
||||
.split(LINE_BREAKS_REG).forEach(line => {
|
||||
const codeLine = this.createBlock('span', line)
|
||||
codeLine.functionType = 'codeLine'
|
||||
codeLine.lang = 'yaml'
|
||||
this.appendChild(codeBlock, codeLine)
|
||||
})
|
||||
|
||||
block.functionType = token.type
|
||||
block.lang = codeBlock.lang = 'yaml'
|
||||
this.codeBlocks.set(block.key, value)
|
||||
this.appendChild(block, codeBlock)
|
||||
this.appendChild(parentList[0], block)
|
||||
break
|
||||
}
|
||||
@ -72,14 +78,29 @@ const importRegister = ContentState => {
|
||||
break
|
||||
}
|
||||
case 'code': {
|
||||
const { codeBlockStyle, text, lang, type } = token
|
||||
const { codeBlockStyle, text, lang = '' } = token
|
||||
value = text
|
||||
if (value.endsWith('\n')) {
|
||||
value = value.replace(/\n+$/, '')
|
||||
}
|
||||
block = this.createBlock('pre', value)
|
||||
block.functionType = type
|
||||
Object.assign(block, { lang, codeBlockStyle })
|
||||
block = this.createBlock('pre')
|
||||
const codeBlock = this.createBlock('code')
|
||||
value.split(LINE_BREAKS_REG).forEach(line => {
|
||||
const codeLine = this.createBlock('span', line)
|
||||
codeLine.lang = lang
|
||||
codeLine.functionType = 'codeLine'
|
||||
this.appendChild(codeBlock, codeLine)
|
||||
})
|
||||
const inputBlock = this.createBlock('span', lang)
|
||||
if (lang) {
|
||||
loadLanguage(lang)
|
||||
}
|
||||
inputBlock.functionType = 'languageInput'
|
||||
this.codeBlocks.set(block.key, value)
|
||||
block.functionType = codeBlockStyle === 'fenced' ? 'fencecode' : 'indentcode'
|
||||
block.lang = codeBlock.lang = lang
|
||||
this.appendChild(block, inputBlock)
|
||||
this.appendChild(block, codeBlock)
|
||||
this.appendChild(parentList[0], block)
|
||||
break
|
||||
}
|
||||
@ -249,12 +270,14 @@ const importRegister = ContentState => {
|
||||
end: { key, offset }
|
||||
}
|
||||
// handle cursor in Math block, need to remove `CURSOR_DNA` in preview block
|
||||
if (type === 'span' && functionType === 'multiplemath') {
|
||||
const mathPreview = this.getNextSibling(this.getParent(block))
|
||||
const { math } = mathPreview
|
||||
const offset = math.indexOf(CURSOR_DNA)
|
||||
if (type === 'span' && functionType === 'codeLine') {
|
||||
const preBlock = this.getParent(this.getParent(block))
|
||||
const code = this.codeBlocks.get(preBlock.key)
|
||||
if (!code) return
|
||||
const offset = code.indexOf(CURSOR_DNA)
|
||||
if (offset > -1) {
|
||||
mathPreview.math = math.substring(0, offset) + math.substring(offset + CURSOR_DNA.length)
|
||||
const newCode = code.substring(0, offset) + code.substring(offset + CURSOR_DNA.length)
|
||||
this.codeBlocks.set(preBlock.key, newCode)
|
||||
}
|
||||
}
|
||||
return
|
||||
@ -279,7 +302,6 @@ const importRegister = ContentState => {
|
||||
}
|
||||
|
||||
ContentState.prototype.importMarkdown = function (markdown) {
|
||||
// empty the blocks and codeBlocks
|
||||
this.codeBlocks = new Map()
|
||||
this.blocks = this.markdownToState(markdown)
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
// DOTO: Don't use Node API in editor folder, remove `path` @jocs
|
||||
// todo@jocs: remove the use of `axios` in muya
|
||||
import axios from 'axios'
|
||||
import createDOMPurify from 'dompurify'
|
||||
|
||||
@ -161,17 +162,6 @@ export const checkImageContentType = async url => {
|
||||
}
|
||||
}
|
||||
|
||||
export const collectImportantComments = css => {
|
||||
const once = new Set()
|
||||
const cleaned = css.replace(/(\/\*![\s\S]*?\*\/)\n*/gm, (match, p1) => {
|
||||
once.add(p1)
|
||||
return ''
|
||||
})
|
||||
const combined = Array.from(once)
|
||||
combined.push(cleaned)
|
||||
return combined.join('\n')
|
||||
}
|
||||
|
||||
export const getImageInfo = src => {
|
||||
const EXT_REG = /\.(jpeg|jpg|png|gif|svg|webp)(?=\?|$)/i
|
||||
// http[s] (domain or IPv4 or localhost or IPv6) [port] /not-white-space
|
||||
|
@ -64,11 +64,6 @@ body {
|
||||
caret-color: #efefef;
|
||||
}
|
||||
|
||||
#ag-editor-id>ul:first-child,
|
||||
#ag-editor-id>ol:first-child {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.ag-float-box {
|
||||
background: #303133;
|
||||
border: 1px solid #303133;
|
||||
@ -350,26 +345,13 @@ table tr td:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
}
|
||||
|
||||
code,
|
||||
tt {
|
||||
border: 1px solid #ddd;
|
||||
background-color: #606266;
|
||||
border-radius: 3px;
|
||||
padding: 0;
|
||||
font-family: Consolas, "Liberation Mono", Courier, monospace;
|
||||
padding: 2px 4px 0px 4px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
|
||||
/* custom add */
|
||||
code {
|
||||
span.ag-line code,
|
||||
th code,
|
||||
td code {
|
||||
border: none;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
font-size: 90%;
|
||||
color: #efefef;
|
||||
background-color: #606266;
|
||||
@ -428,7 +410,6 @@ code {
|
||||
border-color: #333;
|
||||
}
|
||||
|
||||
#ag-editor-id pre.ag-code-block,
|
||||
#ag-editor-id pre.ag-html-block {
|
||||
font-size: 90%;
|
||||
line-height: 1.6;
|
||||
@ -436,11 +417,134 @@ code {
|
||||
color: #777777;
|
||||
}
|
||||
|
||||
#ag-editor-id pre.ag-code-block .cm-s-railscasts.CodeMirror,
|
||||
#ag-editor-id pre.ag-html-block .cm-s-railscasts.CodeMirror {
|
||||
background: var(--primaryColor);
|
||||
}
|
||||
|
||||
.ag-color-dark {
|
||||
color: #c6c6c6;
|
||||
}
|
||||
|
||||
/**
|
||||
* okaidia theme for JavaScript, CSS and HTML
|
||||
* Loosely based on Monokai textmate theme by http://www.monokai.nl/
|
||||
* @author ocodia
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre.ag-paragraph {
|
||||
color: #f8f8f2;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre.ag-paragraph {
|
||||
padding: 1em;
|
||||
margin: 1em 0;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre.ag-paragraph {
|
||||
background: #272822;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.constant,
|
||||
.token.symbol {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
.token.boolean,
|
||||
.token.number {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin {
|
||||
color: #a6e22e;
|
||||
}
|
||||
|
||||
.token.inserted {
|
||||
color: #22863a;
|
||||
background: #f0fff4;
|
||||
}
|
||||
|
||||
.token.deleted {
|
||||
color: #b31d28;
|
||||
background: #ffeef0;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string,
|
||||
.token.variable {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.function,
|
||||
.token.class-name {
|
||||
color: #e6db74;
|
||||
}
|
||||
|
||||
.token.keyword {
|
||||
color: #66d9ef;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important {
|
||||
color: #fd971f;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
|
@ -4,28 +4,28 @@
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: local('Open Sans Regular'),url('./github/400.woff') format('woff')
|
||||
src: local('Open Sans Regular'),url('./github/400.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
src: local('Open Sans Italic'),url('./github/400i.woff') format('woff')
|
||||
src: local('Open Sans Italic'),url('./github/400i.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: local('Open Sans Bold'),url('./github/700.woff') format('woff')
|
||||
src: local('Open Sans Bold'),url('./github/700.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
src: local('Open Sans Bold Italic'),url('./github/700i.woff') format('woff')
|
||||
src: local('Open Sans Bold Italic'),url('./github/700i.woff') format('woff');
|
||||
}
|
||||
|
||||
html, body {
|
||||
@ -65,11 +65,6 @@ body {
|
||||
caret-color: #000000;
|
||||
}
|
||||
|
||||
#ag-editor-id>ul:first-child,
|
||||
#ag-editor-id>ol:first-child {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.ag-gray {
|
||||
color: #C0C4CC;
|
||||
text-decoration: none;
|
||||
@ -322,32 +317,28 @@ table tr td:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
}
|
||||
|
||||
code,
|
||||
tt {
|
||||
border: 1px solid #ddd;
|
||||
background-color: #f8f8f8;
|
||||
span code,
|
||||
td code,
|
||||
th code {
|
||||
background-color: rgba(27, 31, 35, 0.05);
|
||||
border-radius: 3px;
|
||||
padding: 0;
|
||||
font-family: Consolas, "Liberation Mono", Courier, monospace;
|
||||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
padding: 2px 4px 0px 4px;
|
||||
font-size: 0.9em;
|
||||
font-size: 85%;
|
||||
margin: 0;
|
||||
padding: 0.2em 0.4em;
|
||||
color: #24292e;
|
||||
}
|
||||
|
||||
|
||||
/* custom add */
|
||||
code {
|
||||
padding: 0.2em 0.4em;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
background-color: rgba(27,31,35,0.05);
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre {
|
||||
font-size: 90%;
|
||||
line-height: 1.6;
|
||||
background: #f6f8fa;
|
||||
border: 0;
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
color: #24292e;
|
||||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
color: #777777;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@ -376,16 +367,6 @@ code {
|
||||
border-bottom-color: #333333;
|
||||
}
|
||||
|
||||
#ag-editor-id pre.ag-code-block,
|
||||
#ag-editor-id pre.ag-html-block {
|
||||
font-size: 90%;
|
||||
line-height: 1.6;
|
||||
background: #f6f8fa;
|
||||
border: 0;
|
||||
border-radius: 3px;
|
||||
color: #777777;
|
||||
}
|
||||
|
||||
#ag-editor-id pre.ag-html-block {
|
||||
background: transparent;
|
||||
padding: 0 .5rem;
|
||||
@ -403,3 +384,116 @@ p:not(.ag-active)[data-role="hr"]::before {
|
||||
.fg-color-dark {
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* prismjs default theme */
|
||||
|
||||
code[class*="language-"],
|
||||
pre.ag-paragraph {
|
||||
color: black;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre.ag-paragraph {
|
||||
padding: 1em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.token.inserted {
|
||||
color: #22863a;
|
||||
background: #f0fff4;
|
||||
}
|
||||
|
||||
.token.deleted {
|
||||
color: #b31d28;
|
||||
background: #ffeef0;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #9a6e3a;
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.token.function,
|
||||
.token.class-name {
|
||||
color: #DD4A68;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
@ -240,7 +240,6 @@
|
||||
|
||||
this.editor.on('selectionChange', changes => {
|
||||
const { y } = changes.cursorCoords
|
||||
|
||||
if (this.typewriter) {
|
||||
animatedScrollTo(container, container.scrollTop + y - STANDAR_Y, 100)
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import codeMirror, { setMode, setCursorAtLastLine, setTextDirection } from 'muya/lib/codeMirror'
|
||||
import codeMirror, { setMode, setCursorAtLastLine, setTextDirection } from '../../codeMirror'
|
||||
import { wordCount as getWordCount } from 'muya/lib/utils'
|
||||
import { adjustCursor } from '../../util'
|
||||
import bus from '../../bus'
|
||||
|
Loading…
Reference in New Issue
Block a user