From 37b96c88330ffb8c6ca6eeb569cab713b3a0e3ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E5=86=89?= Date: Sun, 27 Oct 2019 16:18:04 +0800 Subject: [PATCH 01/29] feat: footnote identifer --- src/muya/lib/assets/styles/index.css | 11 +++++ src/muya/lib/config/index.js | 4 +- src/muya/lib/parser/index.js | 49 ++++++++++++------- .../renderInlines/footnoteIdentifier.js | 23 +++++++++ .../lib/parser/render/renderInlines/index.js | 4 +- src/muya/lib/parser/rules.js | 3 +- 6 files changed, 73 insertions(+), 21 deletions(-) create mode 100644 src/muya/lib/parser/render/renderInlines/footnoteIdentifier.js diff --git a/src/muya/lib/assets/styles/index.css b/src/muya/lib/assets/styles/index.css index 3dcf2f98..d20c659d 100644 --- a/src/muya/lib/assets/styles/index.css +++ b/src/muya/lib/assets/styles/index.css @@ -1107,3 +1107,14 @@ span.ag-reference-link { .vega-embed { padding-right: 0; } + +.ag-inline-footnote-identifier { + background: var(--codeBlockBgColor); + padding: 0em 0.3em; + border-radius: 1px; + font-size: .7em; +} + +.ag-inline-footnote-identifier a { + color: var(--editorColor); +} diff --git a/src/muya/lib/config/index.js b/src/muya/lib/config/index.js index 22eb6db1..53d1e54f 100644 --- a/src/muya/lib/config/index.js +++ b/src/muya/lib/config/index.js @@ -107,6 +107,7 @@ export const CLASS_OR_ID = genUpper2LowerKeyHash([ 'AG_INLINE_IMAGE_SELECTED', 'AG_INLINE_IMAGE_IS_EDIT', 'AG_INDENT_CODE', + 'AG_INLINE_FOOTNOTE_IDENTIFIER', 'AG_INLINE_RULE', 'AG_LANGUAGE', 'AG_LANGUAGE_INPUT', @@ -275,7 +276,8 @@ export const MUYA_DEFAULT_OPTION = { imagePathAutoComplete: () => [], // Markdown extensions - superSubScript: false + superSubScript: false, + footnote: true } // export const DIAGRAM_TEMPLATE = { diff --git a/src/muya/lib/parser/index.js b/src/muya/lib/parser/index.js index ec7dc9a9..6fe34794 100644 --- a/src/muya/lib/parser/index.js +++ b/src/muya/lib/parser/index.js @@ -32,12 +32,12 @@ const correctUrl = token => { } } -const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top, labels) => { +const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top, labels, options) => { const originSrc = src const tokens = [] let pending = '' let pendingStartPos = pos - + const { superSubScript, footnote } = options const pushPending = () => { if (pending) { tokens.push({ @@ -151,7 +151,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top, labels) => { range, marker, parent: tokens, - children: tokenizerFac(to[2], undefined, inlineRules, pos + to[1].length, false, labels), + children: tokenizerFac(to[2], undefined, inlineRules, pos + to[1].length, false, labels, options), backlash: to[3] }) src = src.substring(to[0].length) @@ -192,7 +192,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top, labels) => { range, marker, parent: tokens, - children: tokenizerFac(to[2], undefined, inlineRules, pos + to[1].length, false, labels), + children: tokenizerFac(to[2], undefined, inlineRules, pos + to[1].length, false, labels, options), backlash: to[3] }) } @@ -203,7 +203,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top, labels) => { } if (inChunk) continue // superscript and subscript - if (inlineRules.superscript && inlineRules.subscript) { + if (superSubScript) { const superSubTo = inlineRules.superscript.exec(src) || inlineRules.subscript.exec(src) if (superSubTo) { pushPending() @@ -223,6 +223,27 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top, labels) => { continue } } + // footnote identifier + if (pos !== 0 && footnote) { + const footnoteTo = inlineRules.footnote_identifier.exec(src) + if (footnoteTo) { + pushPending() + tokens.push({ + type: 'footnote_identifier', + raw: footnoteTo[0], + marker: footnoteTo[1], + range: { + start: pos, + end: pos + footnoteTo[0].length + }, + parent: tokens, + content: footnoteTo[2] + }) + src = src.substring(footnoteTo[0].length) + pos = pos + footnoteTo[0].length + continue + } + } // image const imageTo = inlineRules.image.exec(src) correctUrl(imageTo) @@ -276,7 +297,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top, labels) => { start: pos, end: pos + linkTo[0].length }, - children: tokenizerFac(linkTo[2], undefined, inlineRules, pos + linkTo[1].length, false, labels), + children: tokenizerFac(linkTo[2], undefined, inlineRules, pos + linkTo[1].length, false, labels, options), backlash: { first: linkTo[3], second: linkTo[5] @@ -306,7 +327,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top, labels) => { start: pos, end: pos + rLinkTo[0].length }, - children: tokenizerFac(rLinkTo[1], undefined, inlineRules, pos + 1, false, labels) + children: tokenizerFac(rLinkTo[1], undefined, inlineRules, pos + 1, false, labels, options) }) src = src.substring(rLinkTo[0].length) @@ -442,7 +463,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top, labels) => { parent: tokens, attrs, content: htmlTo[4], - children: htmlTo[4] ? tokenizerFac(htmlTo[4], undefined, inlineRules, pos + htmlTo[2].length, false, labels) : '', + children: htmlTo[4] ? tokenizerFac(htmlTo[4], undefined, inlineRules, pos + htmlTo[2].length, false, labels, options) : '', range: { start: pos, end: pos + len @@ -530,16 +551,8 @@ export const tokenizer = (src, { labels = new Map(), options = {} } = {}) => { - const { superSubScript } = options - - if (superSubScript) { - inlineRules.superscript = inlineExtensionRules.superscript - inlineRules.subscript = inlineExtensionRules.subscript - } else { - delete inlineRules.superscript - delete inlineRules.subscript - } - const tokens = tokenizerFac(src, hasBeginRules ? beginRules : null, inlineRules, 0, true, labels) + const rules = Object.assign({}, inlineRules, inlineExtensionRules) + const tokens = tokenizerFac(src, hasBeginRules ? beginRules : null, rules, 0, true, labels, options) const postTokenizer = tokens => { for (const token of tokens) { diff --git a/src/muya/lib/parser/render/renderInlines/footnoteIdentifier.js b/src/muya/lib/parser/render/renderInlines/footnoteIdentifier.js new file mode 100644 index 00000000..4c35fc59 --- /dev/null +++ b/src/muya/lib/parser/render/renderInlines/footnoteIdentifier.js @@ -0,0 +1,23 @@ +import { CLASS_OR_ID } from '../../../config' + +export default function footnoteIdentifier (h, cursor, block, token, outerClass) { + const className = this.getClassName(outerClass, block, token, cursor) + const { marker } = token + const { start, end } = token.range + + const startMarker = this.highlight(h, block, start, start + marker.length, token) + const endMarker = this.highlight(h, block, end - 1, end, token) + const content = this.highlight(h, block, start + marker.length, end - 1, token) + + return [ + h(`sup.${CLASS_OR_ID.AG_INLINE_FOOTNOTE_IDENTIFIER}.${CLASS_OR_ID.AG_INLINE_RULE}`, [ + h(`span.${className}.${CLASS_OR_ID.AG_REMOVE}`, startMarker), + h('a', { + attrs: { + spellcheck: 'false' + } + }, content), + h(`span.${className}.${CLASS_OR_ID.AG_REMOVE}`, endMarker) + ]) + ] +} diff --git a/src/muya/lib/parser/render/renderInlines/index.js b/src/muya/lib/parser/render/renderInlines/index.js index e06e46ad..e266a58a 100644 --- a/src/muya/lib/parser/render/renderInlines/index.js +++ b/src/muya/lib/parser/render/renderInlines/index.js @@ -28,6 +28,7 @@ import htmlRuby from './htmlRuby' import referenceLink from './referenceLink' import referenceImage from './referenceImage' import superSubScript from './superSubScript' +import footnoteIdentifier from './footnoteIdentifier' export default { backlashInToken, @@ -59,5 +60,6 @@ export default { htmlRuby, referenceLink, referenceImage, - superSubScript + superSubScript, + footnoteIdentifier } diff --git a/src/muya/lib/parser/rules.js b/src/muya/lib/parser/rules.js index 3f2b761b..ff2deb49 100644 --- a/src/muya/lib/parser/rules.js +++ b/src/muya/lib/parser/rules.js @@ -41,6 +41,7 @@ export const inlineRules = { export const inlineExtensionRules = { // This is not the best regexp, because it not support `2^2\\^`. superscript: /^(\^)((?:[^\^\s]|(?<=\\)\1|(?<=\\) )+?)(? Date: Sun, 27 Oct 2019 18:16:29 +0800 Subject: [PATCH 02/29] feat: add update to footnote --- src/muya/lib/assets/pngicon/footnote/2.png | Bin 0 -> 1675 bytes src/muya/lib/assets/styles/index.css | 50 ++++++++++++++++++ src/muya/lib/contentState/footnoteCtrl.js | 43 +++++++++++++++ src/muya/lib/contentState/index.js | 2 + src/muya/lib/contentState/updateCtrl.js | 9 +++- .../parser/render/renderBlock/renderIcon.js | 4 +- src/muya/lib/parser/rules.js | 2 +- src/renderer/assets/styles/index.css | 1 + src/renderer/assets/themes/dark.theme.css | 1 + src/renderer/assets/themes/graphite.theme.css | 1 + .../assets/themes/material-dark.theme.css | 1 + src/renderer/assets/themes/one-dark.theme.css | 1 + src/renderer/assets/themes/ulysses.theme.css | 1 + 13 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 src/muya/lib/assets/pngicon/footnote/2.png create mode 100644 src/muya/lib/contentState/footnoteCtrl.js diff --git a/src/muya/lib/assets/pngicon/footnote/2.png b/src/muya/lib/assets/pngicon/footnote/2.png new file mode 100644 index 0000000000000000000000000000000000000000..f5be4b5e3f9c7cfada36dc825a84256e161fe2e3 GIT binary patch literal 1675 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST~P>fi1|>#WAEJ?(N-+d6xqOTrYn3 zFVDN`h_u3;oql_-M%q+gGX7ohe3z?N-JZDh^Vg^U`&;qsc-`#UuiseBFC6#f`Xb&? zw>PeQzv7Fx+*Qga#2xA-Vtb<^G1UrkHofIX^9nk6Lf$Fk$tQe|2~7sz1DH z(*Na|;Oi{g=hyGnCfgPr-t$mpkJq<}wVXv;=Y4(+v~Gez`X4=id!}E!@AuqK?O%P= zcGG&{Cm;=$j^`v`mRmdOeSf?@;$K&3&E}dt2g(KiYW28(``S?oGQ}oot|81B-#SWn z%J)aeZS2bz>yOy3@aKPb#gSk3i+8X4&{694Y2xJh3h7I0(`G5mU-M~^O{mfD}$q!?}>>j4ii#zokBST zmJAW8eQZ{d-2v<@dH;VkN5_BoT4gzv@0Mwm&x32fZ*mshX&3JRhRhm)!YCGwORt<~ zNia4Y;SyZ#+Mux3Zb|20mU>X;81>q-*`cGsKZ$>;qE|nIW7S*_5haHS0g{Wp03+yn zr;8#J3+K>~I?%4Ly)LFE-?dX0R>ymUJzqqxp`@etvk>Mhte{*8Jlu4Z_QI?Vv$TV-U7^A+e~uL3o>R= z)SwVOYuod|YuyQMqg$s0m|U6O|CsmX(L5tXgMsz_4_3uT5 { + ContentState.prototype.updateFootnote = function (block, line) { + const { start, end } = this.cursor + const { text } = line + const match = FOOTNOTE_REG.exec(text) + const footnoteIdentifer = match[1] + const sectionWrapper = this.createBlock('figure', { + functionType: 'footnote' + }) + const footnoteInput = this.createBlock('span', { + text: footnoteIdentifer, + functionType: 'footnoteInput' + }) + const pBlock = this.createBlockP(text.substring(match[0].length)) + this.appendChild(sectionWrapper, footnoteInput) + this.appendChild(sectionWrapper, pBlock) + this.insertBefore(sectionWrapper, block) + this.removeBlock(block) + + const { key } = pBlock.children[0] + this.cursor = { + start: { + key, + offset: Math.max(0, start.offset - footnoteIdentifer.length) + }, + end: { + key, + offset: Math.max(0, end.offset - footnoteIdentifer.length) + } + } + + if (this.isCollapse()) { + this.checkInlineUpdate(pBlock.children[0]) + } + + return this.partialRender() + } +} + +export default footnoteCtrl diff --git a/src/muya/lib/contentState/index.js b/src/muya/lib/contentState/index.js index 47ce4214..735dd93b 100644 --- a/src/muya/lib/contentState/index.js +++ b/src/muya/lib/contentState/index.js @@ -28,6 +28,7 @@ import emojiCtrl from './emojiCtrl' import imageCtrl from './imageCtrl' import linkCtrl from './linkCtrl' import dragDropCtrl from './dragDropCtrl' +import footnoteCtrl from './footnoteCtrl' import importMarkdown from '../utils/importMarkdown' import Cursor from '../selection/cursor' import escapeCharactersMap, { escapeCharacters } from '../parser/escapeCharacter' @@ -58,6 +59,7 @@ const prototypes = [ imageCtrl, linkCtrl, dragDropCtrl, + footnoteCtrl, importMarkdown ] diff --git a/src/muya/lib/contentState/updateCtrl.js b/src/muya/lib/contentState/updateCtrl.js index fadccfab..baedb065 100644 --- a/src/muya/lib/contentState/updateCtrl.js +++ b/src/muya/lib/contentState/updateCtrl.js @@ -10,6 +10,7 @@ const INLINE_UPDATE_FRAGMENTS = [ '^(?:[\\s\\S]+?)\\n {0,3}(\\={3,}|\\-{3,})(?= {1,}|$)', // Setext headings **match from beginning** '(?:^|\n) {0,3}(>).+', // Block quote '^( {4,})', // Indent code **match from beginning** + '^(\\[\\^[^\\^\\[\\]\\s]+?(? { if (/figure/.test(block.type)) { return false } - if (/cellContent|codeContent|languageInput/.test(block.functionType)) { + if (/cellContent|codeContent|languageInput|footnoteInput/.test(block.functionType)) { return false } @@ -89,8 +90,9 @@ const updateCtrl = ContentState => { const listItem = this.getParent(block) const [ match, bullet, tasklist, order, atxHeader, - setextHeader, blockquote, indentCode, hr + setextHeader, blockquote, indentCode, footnote, hr ] = text.match(INLINE_UPDATE_REG) || [] + const { footnote: isSupportFootnote } = this.muya.options switch (true) { case (!!hr && new Set(hr.split('').filter(i => /\S/.test(i))).size === 1): @@ -118,6 +120,9 @@ const updateCtrl = ContentState => { case !!indentCode: return this.updateIndentCode(block, line) + case !!footnote && block.type === 'p' && !block.parent && isSupportFootnote: + return this.updateFootnote(block, line) + case !match: default: return this.updateToParagraph(block, line) diff --git a/src/muya/lib/parser/render/renderBlock/renderIcon.js b/src/muya/lib/parser/render/renderBlock/renderIcon.js index 761e1e43..bd9ae3e0 100644 --- a/src/muya/lib/parser/render/renderBlock/renderIcon.js +++ b/src/muya/lib/parser/render/renderBlock/renderIcon.js @@ -21,6 +21,7 @@ import flowchartIcon from '../../../assets/pngicon/flowchart/2.png' import sequenceIcon from '../../../assets/pngicon/sequence/2.png' import mermaidIcon from '../../../assets/pngicon/mermaid/2.png' import vegaIcon from '../../../assets/pngicon/chart/2.png' +import footnoteIcon from '../../../assets/pngicon/footnote/2.png' const FUNCTION_TYPE_HASH = { mermaid: mermaidIcon, @@ -32,7 +33,8 @@ const FUNCTION_TYPE_HASH = { multiplemath: mathblockIcon, fencecode: codeIcon, indentcode: codeIcon, - frontmatter: frontMatterIcon + frontmatter: frontMatterIcon, + footnote: footnoteIcon } export default function renderIcon (block) { diff --git a/src/muya/lib/parser/rules.js b/src/muya/lib/parser/rules.js index ff2deb49..070a817f 100644 --- a/src/muya/lib/parser/rules.js +++ b/src/muya/lib/parser/rules.js @@ -42,6 +42,6 @@ export const inlineExtensionRules = { // This is not the best regexp, because it not support `2^2\\^`. superscript: /^(\^)((?:[^\^\s]|(?<=\\)\1|(?<=\\) )+?)(? Date: Sun, 27 Oct 2019 18:25:52 +0800 Subject: [PATCH 03/29] modify some style --- src/muya/lib/assets/styles/index.css | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/muya/lib/assets/styles/index.css b/src/muya/lib/assets/styles/index.css index 4f8d4ffc..c4ec6e53 100644 --- a/src/muya/lib/assets/styles/index.css +++ b/src/muya/lib/assets/styles/index.css @@ -183,7 +183,7 @@ figure[data-role="HTML"].ag-active .ag-html-preview { figure[data-role="FOOTNOTE"] { position: relative; background: var(--footnoteBgColor); - padding: .5em 1em; + padding: 1em 1em .05em 1em; font-size: .8em; opacity: .8; border-radius: 3px; @@ -193,8 +193,8 @@ figure[data-role="FOOTNOTE"].ag-active::before { content: attr(data-role); text-transform: lowercase; position: absolute; - top: 15px; - right: 10px; + top: .15em; + right: 1em; color: var(--editorColor30); font-size: 12px; } @@ -207,7 +207,7 @@ figure[data-role="FOOTNOTE"] .ag-footnote-input { padding: 0 1em; min-width: 80px; position: absolute; - top: -23px; + top: 0px; left: 0; font-size: 14px; font-family: monospace; @@ -215,11 +215,6 @@ figure[data-role="FOOTNOTE"] .ag-footnote-input { color: var(--editorColor); background: transparent; z-index: 1; - display: none; -} - -figure[data-role="FOOTNOTE"].ag-active .ag-footnote-input { - display: inline-block; } figure[data-role="FOOTNOTE"] .ag-footnote-input::before { From ec91b31d49eed4cc6c2599fedc1346f365a203be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E5=86=89?= Date: Sun, 27 Oct 2019 23:52:23 +0800 Subject: [PATCH 04/29] Create footnote from identifier and backlink --- src/muya/lib/assets/pngicon/warning/2.png | Bin 0 -> 6091 bytes src/muya/lib/assets/styles/index.css | 25 ++- src/muya/lib/contentState/footnoteCtrl.js | 22 ++- src/muya/lib/eventHandler/clickEvent.js | 15 ++ src/muya/lib/eventHandler/mouseEvent.js | 48 +++++- .../renderBlock/renderContainerBlock.js | 5 +- .../render/renderBlock/renderFootnoteJump.js | 5 + .../renderInlines/footnoteIdentifier.js | 2 +- src/muya/lib/ui/footnoteTool/index.css | 53 +++++++ src/muya/lib/ui/footnoteTool/index.js | 148 ++++++++++++++++++ src/muya/lib/utils/index.js | 12 ++ .../components/editorWithTabs/editor.vue | 2 + 12 files changed, 323 insertions(+), 14 deletions(-) create mode 100644 src/muya/lib/assets/pngicon/warning/2.png create mode 100644 src/muya/lib/parser/render/renderBlock/renderFootnoteJump.js create mode 100644 src/muya/lib/ui/footnoteTool/index.css create mode 100644 src/muya/lib/ui/footnoteTool/index.js diff --git a/src/muya/lib/assets/pngicon/warning/2.png b/src/muya/lib/assets/pngicon/warning/2.png new file mode 100644 index 0000000000000000000000000000000000000000..8d899f6f325906009524642a86022aee4a141faa GIT binary patch literal 6091 zcmZ{IcRUpE|Nl96*WuLRWJIpB4rgbtv&qWJIE0LZ?3HwF?fy5Qkc+w8 zydoC>U`p0jQ#J{-*|K(h!e*9sroSJUqxPJu?1;js;FbAgmBPsW{m=f3`#<^78n)M| zsHVWJ_Lr#AW0CmDR}R{3Q$RUlM4(QGQ@pJih<;KU6CdY4ajj*!LUSOn4CbA#m9#*9 z)M|H|{#nsxSxFH&S94%*^Uf@8wy5yWm4Q!dA!~96nN(8hTxK}*bonCTw8wk9PJ18OqBuDV!OxeE-kczBSTw(hz zVIU1&U|Bqj7lFMTqUn;~MiC$B$5;oXFy51ktEEI|Wt8Z`O-Ui>TF?7jP=fQ~kSA}b zAo1I`;bRqioj5UW5z_9HCeg$RVk^0`KG>}#)6l!OQnU@K^52?jbZ z^Yg<;<2c196oiTU*Ceezxbe>!GIiBticU`xY_uMG+mU1gH&Ujsv&d&V3%A%Ma+bguiSM^gyGZR=Uy zFX3Musudvn>7Svc+pT!(CARfDjlGI+7fmS@; zIJQ_fc0oI8XBChi8)pFpp;yF6O;QPw!MbsMP&|VDzz(=|6Ay2-g;2s2Z&O80`{fS_ z!KZg~K|7MOYH{J5EQgMWV~>e=-BPUE1P#g4mmb+n>l^{sz;AD;6zd^tmrgH}uK9w6 z!hAYO+xdgx`IkE^LH2SsKkxW$Xb}!`K4THbna?Kr*rx~?2$Qf6Tz8qjN8snxUcW?u z2Y#d!W8J2MfhGdOJREkf#39VFdEExRZBD;>bT8_s2+fldi@%k#CrR0N<>C*VuPQsC z;N)FJGAW}9iuO>1LhW~$#|HMbc6isGh>%MK2W27E3az4gyQQh0C4c>UIl_VWLRL4Ys z?EHO+p_(A7vi$Pe?R9@X6J0}ocxHeVYnZODGdEf(wKD?qu~07Vs1*~E`BQa2L@g8R zFemEz9iICHs5HVzS-Z)eT!gD(L%(>2W{B&P6QXuVfP3hjj6XON4m!QFDFO&GXuB=R zVTgkB4T!tezwzXl*>Hc_cfPu;tS$4p0xV`NkP$xjfIj#WJARHaW+i-tu4pNBu4ln& ztXVIO_f0?mj;0=mCvMdb+`6&;wZ&?zG#@RS@nDfj^x^a$(^o2Y*($!WwuIcIjuM_Jx}bP z>OTv{0r{@#T(-#3?OS2{rGZJrPO${bPBr(5x)+=D_M|kYE4ov4_eO5jtUk z#&@TbI^7&U7XrE@p-=lQL-WQ=kgBSxW=s%BN-4SKxLu455AE0i_2Umz{2mOmv$I=! zdpR59r3nWtkmlr~?ixEr{Qdl91~Mo_5_bAdXe>Y*UuNCt@9BB8(ld6?nip_f@eIo4 z!N-v;f1VJ0ytm%d86nI^%)=c+MZ={$3|-p=fI}LgMTE@n`Dw5Iej;1|=$tWfX0-Ts z^CmB_=WZ{Hfo4)mxjP1q`%V-OqSJ&fJl)g4LFO1wqwpTRv*l)a53%6l0 zio}8P9&4~`*e~{TDtm@BZ^r;D-2)6x-(O`n|GjApN?w}TokLUqbkRc+m?+SzQkHt% z>d34(v&CBa=(ETNA~Y39!qQCl`>^s3#}*bk{IcxQ<3m9^W<_ziI54o%dfU}DgnuGw z11Jm}>TF_ddHhZjf4*J_UJUH{ZEImUOtpd{>YIxf511p4H<=i_J&UWSO1yRC%5@WZ znK3B%J)q9+yBs8OW>(_EE?KL7SV74h=&+b$G{TdJfR`~_11C9nPHZ@nHJ zrgyC-d>Q;YuPFhGpevs5>!WGU!a77&pl_hy>S`NO;^KzyE_yRrA;^LJ%JYBAbK0&lGLXYDHu`1=L~dlDq{QDx6*Tn?imjOV_(=)4eDTV*pH5b zWoNp$x8GC!=@0Eb5LXaSpEgur^+ZyeXl_jJh5;8O82~HSqo7;|d^t>{{2s2;d8)tT zHi^1A@+i1qbq*13eV>=3Ns+(oMriCKjs#=^ys5Nxm0QUGDvqo@egMKLi z=0;tUT;erBdC`9mFU}y~MI^RRp7*pO%%49>AO#z?vlv7>VP(-VucNB-eQSfL! z?!j_9^RrBq6!&cJoZ(>RzazDBw_-J5qaakBc?smwkb>duxzb)MUVpbbU#aH@zQbn6 z7~5C=iQFl^>YP!n&rI4Dq7wT29Nv7dSK=DHB)frmu_}H$yWsOZ8+FR42qY}jAgO`S zCrwxr_pGdS)6^&!fl=0wn||fp{uW`bDDAlt&%k?DFzE34jpW0b!>QjX#=pw=~>SPHrr0hqh0rub5Hvp zdd_)6GyP}oQrolPy`gutuIkqcei(dQf615sGMx!WDA!7~MDm?*MlTI71$2qsXtvoe z_@v0El%;rlwtriIE#tB9X5}yk=s#>+&}KX&IIG-urQ|)g&3gDlh3t2~PXYf{g89`CU58r#n4IDq?%V=tJqE;V4f_8p3KvX`w9 z`4KngOaF!USpxv{8T zH(!7Xfn9g4w(A9!r`+25>VW2%lcxufar9cJk~)8ii&CELqnsQ=SPCWJg$p6Y9JD$^5Y7+!q_y_Jb8j4w<+@OL z9ku)e2elwj;d?}JDyyj+V3D8p>62808gAlx&&LA6wvG;Ux`N(W$q{zU)pNn|xa4HT9DHLE1owRT)9&*~#<{Nf`Nu0@1yu6S@r8@gI zzpe_bOJ5jCKRa8Ih6Jl=mM4loxG#NQzOgQBQp7VmG&H2-&rLiwU8aG}yo`NIOG0bD zXCXdzfhgB2DGwx=<)6ENM*9#aR$-{YZkDwlWr+5ysX61ELR`ne&MER%2Q z%*7#Yjny0-0TSYq+u<+yEQ+Hpk(21F1b4seVG(HoHN&<4l~xzem`nK9?A|_Bnr-T4 zkeFRSFaP%avVxywvya0;S|oVl;MDTF@a58h1^)}8nq%3nt@hlh--JCBTHodD- z3~f<^K<5d-^QH3Dw}N;?B6loC|Ej3XP)StS1M-3#vW?R5sqJ%Lpv5Q;&a#3&+@lWKeUkX3=I?R z*qRyp8o4tum-+sg_da-pR2ip$KF<#`M|#K0Ild`S({DXjCV)HxUTzmURiEJ3=0ye; z8%#P*e8Y^VoUYh?4}QU=V#-0@4f#qAi|;S=r>SiVG6QOVVXb68u!yMe=(JF(wzz

aF1y4Zca@Z6_Kht6{C3WGreR0?A+dcJ*!mC*oEG{-)1frL5wwnU`o{s-&&q1* z6SEq+Ew^~9*eo3%1t8KIWj{5z;&qkAytgTbKFJi^R&cN=dz(9=kKyl)qQ~ndb_By7 zXUoMGl8?K+17@il<*%!s3G&;b8MIEnKVBYRlw~A&yQ@m-uItYK2GdMCao7#gGCKmb zu?Xt}C;LZb*RTpBQtbh#s;1@Pw|0xTe-6CFtH1;)#*vwkj_5t@GiXXZ&+LrL3nIG_ z(rDCHIelW^r=A+_cJ3`gX9HMlt2QrOL8^vN9hE9l-^Fxga5xD|oye-$@A|nBHomWlN9Uo(pZ5=EU zN>W$n^`|~-PT6%c<3~N}6hV1WTOg!+@Dqwvc08NIU+{KRX#M6S0}Vafd0i6}U~Q4Zy`CuAD%OyL8LhTgcRh;H2oBYiE$655)y zHc5-LBvwIq!-`SngC#eXARUR9+)H3oomCg@kDnAQAv$R~_-;Y7gL}iwG%@o=E0)NS zXOStEq+KDt=k9$(4>~9Z7{7s z*8H%ftO#6@Iom{CaMi4`-j2O&=lP%pc?MX?j#l@%sjE5P0^50i5; zwHlXg4H;rgBJsVpbMrl!5pUE)qVjn`$)J>+=0^L@H1RrXZ$e{k(jRYcIf;Kkn7={F z7-e08nXolwo6@)pVlvD13wl#MUHY z%P#NtYdMkiuk&iv8d$7-d2n!WjFK`+Zd1h~gQfk`%6$x7*XZan{vXo+xpV(%{`xh1 z-{Y45BZa)tV@>Y<-&4qmKi&?nwn1WYQtj=9`_VuT=TU0S8Df5uBgHkz)seG4^>3q>9~hS# rp>qE3FuP;!{~cyqF%Ii2o>OWTsil`%MI>B=^Z;#jeYFY|+sOX~U1551 literal 0 HcmV?d00001 diff --git a/src/muya/lib/assets/styles/index.css b/src/muya/lib/assets/styles/index.css index c4ec6e53..9c90c0c1 100644 --- a/src/muya/lib/assets/styles/index.css +++ b/src/muya/lib/assets/styles/index.css @@ -186,7 +186,11 @@ figure[data-role="FOOTNOTE"] { padding: 1em 1em .05em 1em; font-size: .8em; opacity: .8; - border-radius: 3px; +} + +figure[data-role="FOOTNOTE"] .ag-paragraph-content:first-of-type:empty::after { + content: 'Input the footnote definition...'; + color: var(--editorColor30); } figure[data-role="FOOTNOTE"].ag-active::before { @@ -1155,11 +1159,26 @@ span.ag-reference-link { .ag-inline-footnote-identifier { background: var(--codeBlockBgColor); - padding: 0em 0.3em; - border-radius: 1px; + padding: 0 0.4em; + border-radius: 3px; font-size: .7em; + color: var(--editorColor80); } .ag-inline-footnote-identifier a { color: var(--editorColor); } + +i.ag-footnote-backlink { + width: 20px; + height: 20px; + text-align: center; + line-height: 20px; + display: block; + position: absolute; + right: 1em; + bottom: .15em; + font-family: sans-serif; + cursor: pointer; + z-index: 100; +} diff --git a/src/muya/lib/contentState/footnoteCtrl.js b/src/muya/lib/contentState/footnoteCtrl.js index 94f8f366..f6088b3a 100644 --- a/src/muya/lib/contentState/footnoteCtrl.js +++ b/src/muya/lib/contentState/footnoteCtrl.js @@ -36,7 +36,27 @@ const footnoteCtrl = ContentState => { this.checkInlineUpdate(pBlock.children[0]) } - return this.partialRender() + this.partialRender() + return sectionWrapper + } + + ContentState.prototype.createFootnote = function (identifier) { + const { blocks } = this + const lastBlock = blocks[blocks.length - 1] + const newBlock = this.createBlockP(`[^${identifier}]: `) + this.insertAfter(newBlock, lastBlock) + const key = newBlock.children[0].key + const offset = newBlock.children[0].text.length + this.cursor = { + start: { key, offset }, + end: { key, offset } + } + const sectionWrapper = this.updateFootnote(newBlock, newBlock.children[0]) + const id = sectionWrapper.key + const footnoteEle = document.querySelector(`#${id}`) + if (footnoteEle) { + footnoteEle.scrollIntoView({ behavior: 'smooth' }) + } } } diff --git a/src/muya/lib/eventHandler/clickEvent.js b/src/muya/lib/eventHandler/clickEvent.js index 8e2d9c64..d16c0368 100644 --- a/src/muya/lib/eventHandler/clickEvent.js +++ b/src/muya/lib/eventHandler/clickEvent.js @@ -100,6 +100,7 @@ class ClickEvent { const mathRender = target.closest(`.${CLASS_OR_ID.AG_MATH_RENDER}`) const rubyRender = target.closest(`.${CLASS_OR_ID.AG_RUBY_RENDER}`) const imageWrapper = target.closest(`.${CLASS_OR_ID.AG_INLINE_IMAGE}`) + const footnoteBackLink = target.closest('.ag-footnote-backlink') const imageDelete = target.closest('.ag-image-icon-delete') || target.closest('.ag-image-icon-close') const mathText = mathRender && mathRender.previousElementSibling const rubyText = rubyRender && rubyRender.previousElementSibling @@ -125,6 +126,20 @@ class ClickEvent { return contentState.deleteImage(imageInfo) } + if (footnoteBackLink) { + event.preventDefault() + event.stopPropagation() + const figure = event.target.closest('figure') + const identifier = figure.querySelector('span.ag-footnote-input').textContent + if (identifier) { + const footnoteIdentifier = document.querySelector(`#noteref-${identifier}`) + if (footnoteIdentifier) { + footnoteIdentifier.scrollIntoView({ behavior: 'smooth' }) + } + } + return + } + // Handle image click, to select the current image if (target.tagName === 'IMG' && imageWrapper) { // Handle select image diff --git a/src/muya/lib/eventHandler/mouseEvent.js b/src/muya/lib/eventHandler/mouseEvent.js index 3aed62b7..4611bb45 100644 --- a/src/muya/lib/eventHandler/mouseEvent.js +++ b/src/muya/lib/eventHandler/mouseEvent.js @@ -1,4 +1,5 @@ import { getLinkInfo } from '../utils/getLinkInfo' +import { collectFootnotes } from '../utils' class MouseEvent { constructor (muya) { @@ -12,30 +13,61 @@ class MouseEvent { const handler = event => { const target = event.target const parent = target.parentNode - const { hideLinkPopup } = this.muya.options - if (!hideLinkPopup && parent && parent.tagName === 'A' && parent.classList.contains('ag-inline-rule')) { - const rect = parent.getBoundingClientRect() - const reference = { - getBoundingClientRect () { - return rect - } + const preSibling = target.previousElementSibling + const { hideLinkPopup, footnote } = this.muya.options + const rect = parent.getBoundingClientRect() + const reference = { + getBoundingClientRect () { + return rect } + } + if (!hideLinkPopup && parent && parent.tagName === 'A' && parent.classList.contains('ag-inline-rule')) { eventCenter.dispatch('muya-link-tools', { reference, linkInfo: getLinkInfo(parent) }) } + + if ( + footnote && + parent && + parent.tagName === 'SUP' && + parent.classList.contains('ag-inline-footnote-identifier') && + preSibling && + preSibling.classList.contains('ag-hide') + ) { + const identifier = target.textContent + eventCenter.dispatch('muya-footnote-tool', { + reference, + identifier, + footnotes: collectFootnotes(this.muya.contentState.blocks) + }) + } } const leaveHandler = event => { const target = event.target const parent = target.parentNode - + const preSibling = target.previousElementSibling + const { footnote } = this.muya.options if (parent && parent.tagName === 'A' && parent.classList.contains('ag-inline-rule')) { eventCenter.dispatch('muya-link-tools', { reference: null }) } + + if ( + footnote && + parent && + parent.tagName === 'SUP' && + parent.classList.contains('ag-inline-footnote-identifier') && + preSibling && + preSibling.classList.contains('ag-hide') + ) { + eventCenter.dispatch('muya-footnote-tool', { + reference: null + }) + } } eventCenter.attachDOMEvent(container, 'mouseover', handler) diff --git a/src/muya/lib/parser/render/renderBlock/renderContainerBlock.js b/src/muya/lib/parser/render/renderBlock/renderContainerBlock.js index 5f16112d..376bd50d 100644 --- a/src/muya/lib/parser/render/renderBlock/renderContainerBlock.js +++ b/src/muya/lib/parser/render/renderBlock/renderContainerBlock.js @@ -1,5 +1,6 @@ import { CLASS_OR_ID } from '../../../config' import { renderTableTools } from './renderToolBar' +import { footnoteJumpIcon } from './renderFootnoteJump' import { renderEditIcon } from './renderContainerEditIcon' import { renderLeftBar, renderBottomBar } from './renderTableDargBar' import { h } from '../snabbdom' @@ -126,8 +127,10 @@ export default function renderContainerBlock (parent, block, activeBlocks, match Object.assign(data.dataset, { role: functionType.toUpperCase() }) if (functionType === 'table') { children.unshift(renderTableTools(activeBlocks)) - } else { + } else if (functionType !== 'footnote') { children.unshift(renderEditIcon()) + } else { + children.push(footnoteJumpIcon()) } } diff --git a/src/muya/lib/parser/render/renderBlock/renderFootnoteJump.js b/src/muya/lib/parser/render/renderBlock/renderFootnoteJump.js new file mode 100644 index 00000000..f3830b2e --- /dev/null +++ b/src/muya/lib/parser/render/renderBlock/renderFootnoteJump.js @@ -0,0 +1,5 @@ +import { h } from '../snabbdom' + +export const footnoteJumpIcon = () => { + return h('i.ag-footnote-backlink', '↩︎') +} diff --git a/src/muya/lib/parser/render/renderInlines/footnoteIdentifier.js b/src/muya/lib/parser/render/renderInlines/footnoteIdentifier.js index 4c35fc59..51f6ccab 100644 --- a/src/muya/lib/parser/render/renderInlines/footnoteIdentifier.js +++ b/src/muya/lib/parser/render/renderInlines/footnoteIdentifier.js @@ -10,7 +10,7 @@ export default function footnoteIdentifier (h, cursor, block, token, outerClass) const content = this.highlight(h, block, start + marker.length, end - 1, token) return [ - h(`sup.${CLASS_OR_ID.AG_INLINE_FOOTNOTE_IDENTIFIER}.${CLASS_OR_ID.AG_INLINE_RULE}`, [ + h(`sup#noteref-${token.content}.${CLASS_OR_ID.AG_INLINE_FOOTNOTE_IDENTIFIER}.${CLASS_OR_ID.AG_INLINE_RULE}`, [ h(`span.${className}.${CLASS_OR_ID.AG_REMOVE}`, startMarker), h('a', { attrs: { diff --git a/src/muya/lib/ui/footnoteTool/index.css b/src/muya/lib/ui/footnoteTool/index.css new file mode 100644 index 00000000..0619e4e2 --- /dev/null +++ b/src/muya/lib/ui/footnoteTool/index.css @@ -0,0 +1,53 @@ +.ag-footnote-tool-container { + width: 300px; + border-radius: 5px; +} + +.ag-footnote-tool-container .ag-footnote-tool > div { + display: flex; + height: 35px; + align-items: center; + color: var(--editorColor); + font-size: 12px; + padding: 0 10px; +} + +.ag-footnote-tool .text { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + flex: 1; +} + +.ag-footnote-tool .btn { + width: 40px; + display: inline-block; + cursor: pointer; +} + +.ag-footnote-tool .icon-wrapper { + width: 14px; + height: 14px; + margin-right: 5px; + position: relative; +} + +.ag-footnote-tool .icon-wrapper i.icon { + display: inline-block; + position: absolute; + top: 0; + height: 100%; + width: 100%; + overflow: hidden; + color: var(--iconColor); + transition: all .25s ease-in-out; +} + +.ag-footnote-tool .icon-wrapper i.icon > i[class^=icon-] { + display: inline-block; + width: 100%; + height: 100%; + filter: drop-shadow(14px 0 currentColor); + position: relative; + left: -14px; +} diff --git a/src/muya/lib/ui/footnoteTool/index.js b/src/muya/lib/ui/footnoteTool/index.js new file mode 100644 index 00000000..8f19a83f --- /dev/null +++ b/src/muya/lib/ui/footnoteTool/index.js @@ -0,0 +1,148 @@ +import BaseFloat from '../baseFloat' +import { patch, h } from '../../parser/render/snabbdom' +import WarningIcon from '../../assets/pngicon/warning/2.png' + +import './index.css' + +const getFootnoteText = block => { + let text = '' + const travel = block => { + if (block.children.length === 0 && block.text) { + text += block.text + } else if (block.children.length) { + for (const b of block.children) { + travel(b) + } + } + } + + const blocks = block.children.slice(1) + for (const b of blocks) { + travel(b) + } + + return text +} + +const defaultOptions = { + placement: 'bottom', + modifiers: { + offset: { + offset: '0, 5' + } + }, + showArrow: false +} + +class LinkTools extends BaseFloat { + static pluginName = 'footnoteTool' + + constructor (muya, options = {}) { + const name = 'ag-footnote-tool' + const opts = Object.assign({}, defaultOptions, options) + super(muya, name, opts) + this.oldVnode = null + this.identifier = null + this.footnotes = null + this.options = opts + this.hideTimer = null + const toolContainer = this.toolContainer = document.createElement('div') + this.container.appendChild(toolContainer) + this.floatBox.classList.add('ag-footnote-tool-container') + this.listen() + } + + listen () { + const { eventCenter } = this.muya + super.listen() + eventCenter.subscribe('muya-footnote-tool', ({ reference, identifier, footnotes }) => { + if (reference) { + this.footnotes = footnotes + this.identifier = identifier + setTimeout(() => { + this.show(reference) + this.render() + }, 0) + } else { + if (this.hideTimer) { + clearTimeout(this.hideTimer) + } + this.hideTimer = setTimeout(() => { + this.hide() + }, 500) + } + }) + + const mouseOverHandler = () => { + if (this.hideTimer) { + clearTimeout(this.hideTimer) + } + } + + const mouseOutHandler = () => { + this.hide() + } + + eventCenter.attachDOMEvent(this.container, 'mouseover', mouseOverHandler) + eventCenter.attachDOMEvent(this.container, 'mouseleave', mouseOutHandler) + } + + render () { + const { oldVnode, toolContainer, identifier, footnotes } = this + const hasFootnote = footnotes.has(identifier) + const iconWrapperSelector = 'div.icon-wrapper' + const icon = h('i.icon', h('i.icon-inner', { + style: { + background: `url(${WarningIcon}) no-repeat`, + 'background-size': '100%' + } + }, '')) + const iconWrapper = h(iconWrapperSelector, icon) + let text = 'Can\'t find footnote with syntax [^abc]:' + if (hasFootnote) { + const footnoteBlock = footnotes.get(identifier) + + text = getFootnoteText(footnoteBlock) + if (!text) { + text = 'Input the footnote definition...' + } + } + const textNode = h('span.text', text) + const button = h('a.btn', { + on: { + click: event => { + this.buttonClick(event, hasFootnote) + } + } + }, hasFootnote ? 'Go to' : 'Create') + const children = [textNode, button] + if (!hasFootnote) { + children.unshift(iconWrapper) + } + const vnode = h('div', children) + + if (oldVnode) { + patch(oldVnode, vnode) + } else { + patch(toolContainer, vnode) + } + this.oldVnode = vnode + } + + buttonClick (event, hasFootnote) { + event.preventDefault() + event.stopPropagation() + const { identifier, footnotes } = this + if (hasFootnote) { + const block = footnotes.get(identifier) + const key = block.key + const ele = document.querySelector(`#${key}`) + ele.scrollIntoView({ behavior: 'smooth' }) + } else { + this.muya.contentState.createFootnote(identifier) + } + return this.hide() + } +} + +export default LinkTools diff --git a/src/muya/lib/utils/index.js b/src/muya/lib/utils/index.js index c4cbb103..786a38fe 100644 --- a/src/muya/lib/utils/index.js +++ b/src/muya/lib/utils/index.js @@ -387,3 +387,15 @@ export const verticalPositionInRect = (event, rect) => { const { top, height } = rect return (clientY - top) > (height / 2) ? 'down' : 'up' } + +export const collectFootnotes = (blocks) => { + const map = new Map() + for (const block of blocks) { + if (block.type === 'figure' && block.functionType === 'footnote') { + const identifier = block.children[0].text + map.set(identifier, block) + } + } + + return map +} diff --git a/src/renderer/components/editorWithTabs/editor.vue b/src/renderer/components/editorWithTabs/editor.vue index fa432767..9939d6ec 100644 --- a/src/renderer/components/editorWithTabs/editor.vue +++ b/src/renderer/components/editorWithTabs/editor.vue @@ -88,6 +88,7 @@ import ImageToolbar from 'muya/lib/ui/imageToolbar' import Transformer from 'muya/lib/ui/transformer' import FormatPicker from 'muya/lib/ui/formatPicker' import LinkTools from 'muya/lib/ui/linkTools' +import FootnoteTool from 'muya/lib/ui/footnoteTool' import TableBarTools from 'muya/lib/ui/tableTools' import FrontMenu from 'muya/lib/ui/frontMenu' import Search from '../search' @@ -470,6 +471,7 @@ export default { Muya.use(LinkTools, { jumpClick: this.jumpClick }) + Muya.use(FootnoteTool) Muya.use(TableBarTools) const options = { From 3f74a8809ff030f477cc82a79538cfd8effc1071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E5=86=89?= Date: Mon, 28 Oct 2019 11:59:51 +0800 Subject: [PATCH 05/29] handle backspace in footnote --- docs/dev/code/BLOCK_ADDITION_PROPERTY.md | 4 ++++ src/muya/lib/assets/styles/index.css | 8 +++---- src/muya/lib/contentState/backspaceCtrl.js | 25 +++++++++++++++++++++- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/docs/dev/code/BLOCK_ADDITION_PROPERTY.md b/docs/dev/code/BLOCK_ADDITION_PROPERTY.md index 7fa03ee3..06671f2a 100644 --- a/docs/dev/code/BLOCK_ADDITION_PROPERTY.md +++ b/docs/dev/code/BLOCK_ADDITION_PROPERTY.md @@ -6,6 +6,8 @@ - languageInput + - footnoteInput + - codeContent (used in code block) - cellContent (used in table cell, it's parent must be th or td block) @@ -46,6 +48,8 @@ The container block of `table`, `html`, `block math`, `mermaid`,`flowchart`,`veg - table + - footnote + - html - multiplemath diff --git a/src/muya/lib/assets/styles/index.css b/src/muya/lib/assets/styles/index.css index 9c90c0c1..82ef43e2 100644 --- a/src/muya/lib/assets/styles/index.css +++ b/src/muya/lib/assets/styles/index.css @@ -183,12 +183,12 @@ figure[data-role="HTML"].ag-active .ag-html-preview { figure[data-role="FOOTNOTE"] { position: relative; background: var(--footnoteBgColor); - padding: 1em 1em .05em 1em; + padding: 1em 2em .05em 1em; font-size: .8em; opacity: .8; } -figure[data-role="FOOTNOTE"] .ag-paragraph-content:first-of-type:empty::after { +figure[data-role="FOOTNOTE"] > p:first-of-type .ag-paragraph-content:empty::after { content: 'Input the footnote definition...'; color: var(--editorColor30); } @@ -1176,8 +1176,8 @@ i.ag-footnote-backlink { line-height: 20px; display: block; position: absolute; - right: 1em; - bottom: .15em; + right: .5em; + bottom: .5em; font-family: sans-serif; cursor: pointer; z-index: 100; diff --git a/src/muya/lib/contentState/backspaceCtrl.js b/src/muya/lib/contentState/backspaceCtrl.js index 3840f135..b9312366 100644 --- a/src/muya/lib/contentState/backspaceCtrl.js +++ b/src/muya/lib/contentState/backspaceCtrl.js @@ -345,6 +345,29 @@ const backspaceCtrl = ContentState => { } if ( + block.type === 'span' && + block.functionType === 'paragraphContent' && + left === 0 && + preBlock && + preBlock.functionType === 'footnoteInput' + ) { + event.preventDefault() + event.stopPropagation() + if (!parent.nextSibling) { + const pBlock = this.createBlockP(block.text) + const figureBlock = this.closest(block, 'figure') + this.insertBefore(pBlock, figureBlock) + this.removeBlock(figureBlock) + const key = pBlock.children[0].key + const offset = 0 + this.cursor = { + start: { key, offset }, + end: { key, offset } + } + + this.partialRender() + } + } else if ( block.type === 'span' && block.functionType === 'codeContent' && left === 0 && @@ -492,7 +515,7 @@ const backspaceCtrl = ContentState => { // also need to remove the paragrah if (this.isOnlyChild(block) && block.type === 'span') { this.removeBlock(parent) - } else if (block.functionType !== 'languageInput') { + } else if (block.functionType !== 'languageInput' && block.functionType !== 'footnoteInput') { this.removeBlock(block) } From 7f48a55f1c1cdebf28d8226d15ce22a78860a02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E5=86=89?= Date: Mon, 28 Oct 2019 15:00:33 +0800 Subject: [PATCH 06/29] export markdown --- src/muya/lib/utils/exportMarkdown.js | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/muya/lib/utils/exportMarkdown.js b/src/muya/lib/utils/exportMarkdown.js index ca26e1e5..ca7749cd 100644 --- a/src/muya/lib/utils/exportMarkdown.js +++ b/src/muya/lib/utils/exportMarkdown.js @@ -4,8 +4,9 @@ * Before you edit or update codes in this file, * make sure you have read this bellow: * Commonmark Spec: https://spec.commonmark.org/0.29/ - * and GitHub Flavored Markdown Spec: https://github.github.com/gfm/ - * The output markdown needs to obey the standards of the two Spec. + * GitHub Flavored Markdown Spec: https://github.github.com/gfm/ + * Pandoc Markdown: https://pandoc.org/MANUAL.html#pandocs-markdown + * The output markdown needs to obey the standards of these Spec. */ class ExportMarkdown { @@ -74,6 +75,10 @@ class ExportMarkdown { result.push(this.normalizeHTML(block, indent)) break } + case 'footnote': { + result.push(this.normalizeFootnote(block, indent)) + break + } case 'multiplemath': { result.push(this.normalizeMultipleMath(block, indent)) break @@ -387,6 +392,24 @@ class ExportMarkdown { result.push(this.translateBlocks2Markdown(children, newIndent, listIndent).substring(newIndent.length)) return result.join('') } + + normalizeFootnote (block, indent) { + const result = [] + const identifier = block.children[0].text + result.push(`${indent}[^${identifier}]:`) + const hasMultipleBlocks = block.children.length > 2 || block.children[1].type !== 'p' + if (hasMultipleBlocks) { + result.push('\n') + const newIndent = indent + ' '.repeat(4) + result.push(this.translateBlocks2Markdown(block.children.slice(1), newIndent)) + } else { + result.push(' ') + const paragraphContent = block.children[1].children[0] + result.push(this.normalizeParagraphText(paragraphContent, indent)) + } + + return result.join('') + } } export default ExportMarkdown From 50615487840a652d497c800f05b8bb22f1274c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E5=86=89?= Date: Tue, 29 Oct 2019 17:32:53 +0800 Subject: [PATCH 07/29] add export html --- src/muya/lib/parser/marked/blockRules.js | 3 +- src/muya/lib/parser/marked/inlineLexer.js | 21 ++++++-- src/muya/lib/parser/marked/inlineRules.js | 7 +-- src/muya/lib/parser/marked/lexer.js | 63 +++++++++++++++++++++-- src/muya/lib/parser/marked/options.js | 3 +- src/muya/lib/parser/marked/parser.js | 28 +++++++++- src/muya/lib/parser/marked/renderer.js | 12 +++++ src/muya/lib/parser/marked/utils.js | 4 ++ src/muya/lib/utils/importMarkdown.js | 18 +++++++ 9 files changed, 144 insertions(+), 15 deletions(-) diff --git a/src/muya/lib/parser/marked/blockRules.js b/src/muya/lib/parser/marked/blockRules.js index 1b5d1033..99774b2b 100644 --- a/src/muya/lib/parser/marked/blockRules.js +++ b/src/muya/lib/parser/marked/blockRules.js @@ -35,7 +35,8 @@ export const block = { // extra frontmatter: /^(?:(?:---\n([\s\S]+?)---)|(?:\+\+\+\n([\s\S]+?)\+\+\+)|(?:;;;\n([\s\S]+?);;;)|(?:\{\n([\s\S]+?)\}))(?:\n{2,}|\n{1,2}$)/, - multiplemath: /^\$\$\n([\s\S]+?)\n\$\$(?:\n+|$)/ + multiplemath: /^\$\$\n([\s\S]+?)\n\$\$(?:\n+|$)/, + footnote: /^\[\^([^\^\[\]\s]+?)\]:[\s\S]+?(?=\n *\n {0,3}[^ ]+|$)/ } block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/ diff --git a/src/muya/lib/parser/marked/inlineLexer.js b/src/muya/lib/parser/marked/inlineLexer.js index 359461f3..e8271fc1 100644 --- a/src/muya/lib/parser/marked/inlineLexer.js +++ b/src/muya/lib/parser/marked/inlineLexer.js @@ -1,16 +1,17 @@ import Renderer from './renderer' import { normal, breaks, gfm, pedantic } from './inlineRules' import defaultOptions from './options' -import { escape, findClosingBracket } from './utils' +import { escape, findClosingBracket, getUniqueId } from './utils' import { validateEmphasize, lowerPriority } from '../utils' /** * Inline Lexer & Compiler */ -function InlineLexer (links, options) { +function InlineLexer (links, footnotes, options) { this.options = options || defaultOptions this.links = links + this.footnotes = footnotes this.rules = normal this.renderer = this.options.renderer || new Renderer() this.renderer.options = this.options @@ -49,7 +50,7 @@ function InlineLexer (links, options) { InlineLexer.prototype.output = function (src) { // src = src // .replace(/\u00a0/g, ' ') - const { disableInline, emoji, math, superSubScript } = this.options + const { disableInline, emoji, math, superSubScript, footnote } = this.options if (disableInline) { return escape(src) } @@ -64,6 +65,7 @@ InlineLexer.prototype.output = function (src) { let lastChar = '' while (src) { + console.log(src) // escape cap = this.rules.escape.exec(src) if (cap) { @@ -73,6 +75,19 @@ InlineLexer.prototype.output = function (src) { continue } + // footnote identifier + if (footnote) { + cap = this.rules.footnoteIdentifier.exec(src) + if (cap) { + src = src.substring(cap[0].length) + lastChar = cap[0].charAt(cap[0].length - 1) + const identifier = cap[1] + const footnoteInfo = this.footnotes[identifier] || {} + footnoteInfo.footnoteIdentifierId = getUniqueId() + out += this.renderer.footnoteIdentifier(identifier, footnoteInfo) + } + } + // tag cap = this.rules.tag.exec(src) if (cap) { diff --git a/src/muya/lib/parser/marked/inlineRules.js b/src/muya/lib/parser/marked/inlineRules.js index 8d0f6330..5eb11530 100644 --- a/src/muya/lib/parser/marked/inlineRules.js +++ b/src/muya/lib/parser/marked/inlineRules.js @@ -29,7 +29,7 @@ const inline = { // ------------------------ // patched - // allow inline math "$" and superscript ("?=[\\${content}` } +Renderer.prototype.footnoteIdentifier = function (identifier, { footnoteId, footnoteIdentifierId }) { + return `${identifier}` +} + +Renderer.prototype.footnote = function (footnote) { + return '

\n
\n
    \n' + footnote + '
\n
\n' +} + +Renderer.prototype.footnoteItem = function (content, { footnoteId, footnoteIdentifierId }) { + return `
  • ${content}↩︎
  • ` +} + Renderer.prototype.code = function (code, infostring, escaped, codeBlockStyle) { const lang = (infostring || '').match(/\S*/)[0] if (this.options.highlight) { diff --git a/src/muya/lib/parser/marked/utils.js b/src/muya/lib/parser/marked/utils.js index 74deac29..f5794128 100644 --- a/src/muya/lib/parser/marked/utils.js +++ b/src/muya/lib/parser/marked/utils.js @@ -2,6 +2,10 @@ * Helpers */ +let i = 0 + +export const getUniqueId = () => ++i + export const escape = function escape (html, encode) { if (encode) { if (escape.escapeTest.test(html)) { diff --git a/src/muya/lib/utils/importMarkdown.js b/src/muya/lib/utils/importMarkdown.js index b49539af..133db150 100644 --- a/src/muya/lib/utils/importMarkdown.js +++ b/src/muya/lib/utils/importMarkdown.js @@ -79,6 +79,7 @@ const importRegister = ContentState => { } const { trimUnnecessaryCodeBlockEmptyLines } = this.muya.options const tokens = new Lexer({ disableInline: true }).lex(markdown) + console.log(JSON.stringify(tokens, null, 2)) let token let block let value @@ -320,6 +321,23 @@ const importRegister = ContentState => { parentList.shift() break } + case 'footnote_start': { + block = this.createBlock('figure', { + functionType: 'footnote' + }) + const identifierInput = this.createBlock('span', { + text: token.identifier, + functionType: 'footnoteInput' + }) + this.appendChild(block, identifierInput) + this.appendChild(parentList[0], block) + parentList.unshift(block) + break + } + case 'footnote_end': { + parentList.shift() + break + } case 'list_start': { const { ordered, listType, start } = token block = this.createBlock(ordered === true ? 'ol' : 'ul') From e62f660e56ac8dffb7971719b1a42f69c3187f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E5=86=89?= Date: Tue, 29 Oct 2019 17:53:29 +0800 Subject: [PATCH 08/29] add export style --- .electron-vue/webpack.renderer.config.js | 4 ++-- src/muya/lib/assets/styles/exportStyle.css | 15 +++++++++++++++ src/muya/lib/utils/exportHtml.js | 2 ++ 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 src/muya/lib/assets/styles/exportStyle.css diff --git a/.electron-vue/webpack.renderer.config.js b/.electron-vue/webpack.renderer.config.js index 96ffeb75..300113e1 100644 --- a/.electron-vue/webpack.renderer.config.js +++ b/.electron-vue/webpack.renderer.config.js @@ -51,7 +51,7 @@ const rendererConfig = { } }, { - test: /(theme\-chalk(?:\/|\\)index|katex|github\-markdown|prism[\-a-z]*|\.theme|headerFooterStyle)\.css$/, + test: /(theme\-chalk(?:\/|\\)index|exportStyle|katex|github\-markdown|prism[\-a-z]*|\.theme|headerFooterStyle)\.css$/, use: [ 'to-string-loader', 'css-loader' @@ -59,7 +59,7 @@ const rendererConfig = { }, { test: /\.css$/, - exclude: /(theme\-chalk(?:\/|\\)index|katex|github\-markdown|prism[\-a-z]*|\.theme|headerFooterStyle)\.css$/, + exclude: /(theme\-chalk(?:\/|\\)index|exportStyle|katex|github\-markdown|prism[\-a-z]*|\.theme|headerFooterStyle)\.css$/, use: [ proMode ? MiniCssExtractPlugin.loader : 'style-loader', { loader: 'css-loader', options: { importLoaders: 1 } }, diff --git a/src/muya/lib/assets/styles/exportStyle.css b/src/muya/lib/assets/styles/exportStyle.css new file mode 100644 index 00000000..9f82365a --- /dev/null +++ b/src/muya/lib/assets/styles/exportStyle.css @@ -0,0 +1,15 @@ +.footnotes { + font-size: .9em; + opacity: .8; +} + +.footnotes li[role="doc-endnote"] { + position: relative; +} + +.footnotes .footnote-back { + position: absolute; + font-family: initial; + top: 0; + right: 1em; +} diff --git a/src/muya/lib/utils/exportHtml.js b/src/muya/lib/utils/exportHtml.js index 60e38915..b35cc060 100644 --- a/src/muya/lib/utils/exportHtml.js +++ b/src/muya/lib/utils/exportHtml.js @@ -3,6 +3,7 @@ import Prism from 'prismjs' import katex from 'katex' import loadRenderer from '../renderers' import githubMarkdownCss from 'github-markdown-css/github-markdown.css' +import footnoteCss from '../assets/styles/exportStyle.css' import highlightCss from 'prismjs/themes/prism.css' import katexCss from 'katex/dist/katex.css' import footerHeaderCss from '../assets/styles/headerFooterStyle.css' @@ -247,6 +248,7 @@ class ExportHtml { list-style-type: decimal; } + From b4788f559a5f14f1438f8c918b6f5263d9f9c97f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E5=86=89?= Date: Tue, 29 Oct 2019 18:00:01 +0800 Subject: [PATCH 09/29] modify style --- src/muya/lib/assets/styles/exportStyle.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/muya/lib/assets/styles/exportStyle.css b/src/muya/lib/assets/styles/exportStyle.css index 9f82365a..5aced124 100644 --- a/src/muya/lib/assets/styles/exportStyle.css +++ b/src/muya/lib/assets/styles/exportStyle.css @@ -10,6 +10,6 @@ .footnotes .footnote-back { position: absolute; font-family: initial; - top: 0; + top: .2em; right: 1em; } From 318bfc6aa207e39f70f8bfc77b5481598e8b1fb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E5=86=89?= Date: Tue, 29 Oct 2019 18:36:16 +0800 Subject: [PATCH 10/29] feat: add footnote setting option --- docs/PREFERENCES.md | 1 + src/main/preferences/schema.json | 5 +++++ src/muya/lib/config/index.js | 2 +- src/muya/lib/parser/marked/options.js | 2 +- src/muya/lib/utils/exportHtml.js | 1 + src/renderer/components/editorWithTabs/editor.vue | 9 +++++++++ src/renderer/prefComponents/markdown/index.vue | 9 ++++++++- src/renderer/store/preferences.js | 1 + static/preference.json | 1 + 9 files changed, 28 insertions(+), 3 deletions(-) diff --git a/docs/PREFERENCES.md b/docs/PREFERENCES.md index 48c5f252..441aa423 100644 --- a/docs/PREFERENCES.md +++ b/docs/PREFERENCES.md @@ -50,6 +50,7 @@ Preferences can be controlled and modified in the settings window or via the `pr | listIndentation | String | 1 | The list indentation of sub list items or paragraphs, optional value `dfm`, `tab` or number 1~4 | | frontmatterType | String | `-` | The frontmatter type: `-` (YAML), `+` (TOML), `;` (JSON) or `{` (JSON) | | superSubScript | Boolean | `false` | Enable pandoc's markdown extension superscript and subscript. | +| footnote | Boolean | `false` | Enable pandoc's markdown extension footnote | #### Theme diff --git a/src/main/preferences/schema.json b/src/main/preferences/schema.json index 03755007..110916fa 100644 --- a/src/main/preferences/schema.json +++ b/src/main/preferences/schema.json @@ -237,6 +237,11 @@ "type": "boolean", "default": false }, + "footnote": { + "description": "Markdown-Enable pandoc's markdown extension footnote.", + "type": "boolean", + "default": false + }, "theme": { "description": "Theme--Select the theme used in Mark Text", diff --git a/src/muya/lib/config/index.js b/src/muya/lib/config/index.js index 53d1e54f..a5a368bb 100644 --- a/src/muya/lib/config/index.js +++ b/src/muya/lib/config/index.js @@ -277,7 +277,7 @@ export const MUYA_DEFAULT_OPTION = { // Markdown extensions superSubScript: false, - footnote: true + footnote: false } // export const DIAGRAM_TEMPLATE = { diff --git a/src/muya/lib/parser/marked/options.js b/src/muya/lib/parser/marked/options.js index 8a8e3022..3d3a8158 100644 --- a/src/muya/lib/parser/marked/options.js +++ b/src/muya/lib/parser/marked/options.js @@ -29,5 +29,5 @@ export default { math: true, frontMatter: true, superSubScript: false, - footnote: true + footnote: false } diff --git a/src/muya/lib/utils/exportHtml.js b/src/muya/lib/utils/exportHtml.js index b35cc060..11c6be20 100644 --- a/src/muya/lib/utils/exportHtml.js +++ b/src/muya/lib/utils/exportHtml.js @@ -107,6 +107,7 @@ class ExportHtml { this.mathRendererCalled = false let html = marked(this.markdown, { superSubScript: this.muya ? this.muya.options.superSubScript : false, + footnote: this.muya ? this.muya.options.footnote : false, highlight (code, lang) { // Language may be undefined (GH#591) if (!lang) { diff --git a/src/renderer/components/editorWithTabs/editor.vue b/src/renderer/components/editorWithTabs/editor.vue index 9939d6ec..237e8725 100644 --- a/src/renderer/components/editorWithTabs/editor.vue +++ b/src/renderer/components/editorWithTabs/editor.vue @@ -137,6 +137,7 @@ export default { listIndentation: state => state.preferences.listIndentation, frontmatterType: state => state.preferences.frontmatterType, superSubScript: state => state.preferences.superSubScript, + footnote: state => state.preferences.footnote, lineHeight: state => state.preferences.lineHeight, fontSize: state => state.preferences.fontSize, codeFontSize: state => state.preferences.codeFontSize, @@ -251,6 +252,12 @@ export default { editor.setOptions({ superSubScript: value }, true) } }, + footnote: function (value, oldValue) { + const { editor } = this + if (value !== oldValue && editor) { + editor.setOptions({ footnote: value }, true) + } + }, hideQuickInsertHint: function (value, oldValue) { const { editor } = this if (value !== oldValue && editor) { @@ -447,6 +454,7 @@ export default { listIndentation, frontmatterType, superSubScript, + footnote, hideQuickInsertHint, editorLineWidth, theme, @@ -490,6 +498,7 @@ export default { listIndentation, frontmatterType, superSubScript, + footnote, hideQuickInsertHint, hideLinkPopup, spellcheckEnabled: spellcheckerEnabled, diff --git a/src/renderer/prefComponents/markdown/index.vue b/src/renderer/prefComponents/markdown/index.vue index a6e6d8ed..8a939cce 100644 --- a/src/renderer/prefComponents/markdown/index.vue +++ b/src/renderer/prefComponents/markdown/index.vue @@ -54,6 +54,12 @@ :onChange="value => onSelectChange('superSubScript', value)" more="https://pandoc.org/MANUAL.html#superscripts-and-subscripts" > + @@ -95,7 +101,8 @@ export default { tabSize: state => state.preferences.tabSize, listIndentation: state => state.preferences.listIndentation, frontmatterType: state => state.preferences.frontmatterType, - superSubScript: state => state.preferences.superSubScript + superSubScript: state => state.preferences.superSubScript, + footnote: state => state.preferences.footnote }) }, methods: { diff --git a/src/renderer/store/preferences.js b/src/renderer/store/preferences.js index c0bbef20..1635ac0e 100644 --- a/src/renderer/store/preferences.js +++ b/src/renderer/store/preferences.js @@ -41,6 +41,7 @@ const state = { listIndentation: 1, frontmatterType: '-', superSubScript: false, + footnote: false, theme: 'light', diff --git a/static/preference.json b/static/preference.json index 1364bf47..049c2dc5 100644 --- a/static/preference.json +++ b/static/preference.json @@ -38,6 +38,7 @@ "listIndentation": 1, "frontmatterType": "-", "superSubScript": false, + "footnote": false, "theme": "light", From 8886517c3a9608503443a2ac9e4221f6198a4bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E5=86=89?= Date: Tue, 29 Oct 2019 18:48:20 +0800 Subject: [PATCH 11/29] fix some bugs --- src/muya/lib/contentState/footnoteCtrl.js | 2 +- src/muya/lib/utils/importMarkdown.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/muya/lib/contentState/footnoteCtrl.js b/src/muya/lib/contentState/footnoteCtrl.js index f6088b3a..da9195ae 100644 --- a/src/muya/lib/contentState/footnoteCtrl.js +++ b/src/muya/lib/contentState/footnoteCtrl.js @@ -36,7 +36,7 @@ const footnoteCtrl = ContentState => { this.checkInlineUpdate(pBlock.children[0]) } - this.partialRender() + this.render() return sectionWrapper } diff --git a/src/muya/lib/utils/importMarkdown.js b/src/muya/lib/utils/importMarkdown.js index 133db150..6d75d846 100644 --- a/src/muya/lib/utils/importMarkdown.js +++ b/src/muya/lib/utils/importMarkdown.js @@ -77,8 +77,8 @@ const importRegister = ContentState => { nextSibling: null, children: [] } - const { trimUnnecessaryCodeBlockEmptyLines } = this.muya.options - const tokens = new Lexer({ disableInline: true }).lex(markdown) + const { trimUnnecessaryCodeBlockEmptyLines, footnote } = this.muya.options + const tokens = new Lexer({ disableInline: true, footnote }).lex(markdown) console.log(JSON.stringify(tokens, null, 2)) let token let block From efd38644cd67e061168154b9407f0bc58b8321bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E5=86=89?= Date: Wed, 30 Oct 2019 11:36:39 +0800 Subject: [PATCH 12/29] feat: fix [^longnote] is not auto numbered in exported document but in footnotes. --- docs/PREFERENCES.md | 2 +- src/muya/lib/parser/index.js | 1 + src/muya/lib/parser/marked/inlineLexer.js | 1 - src/muya/lib/parser/marked/lexer.js | 3 +++ src/muya/lib/parser/marked/renderer.js | 4 ++-- src/muya/lib/parser/marked/utils.js | 4 ++-- src/muya/lib/utils/importMarkdown.js | 2 -- 7 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/PREFERENCES.md b/docs/PREFERENCES.md index 441aa423..5d3f145c 100644 --- a/docs/PREFERENCES.md +++ b/docs/PREFERENCES.md @@ -50,7 +50,7 @@ Preferences can be controlled and modified in the settings window or via the `pr | listIndentation | String | 1 | The list indentation of sub list items or paragraphs, optional value `dfm`, `tab` or number 1~4 | | frontmatterType | String | `-` | The frontmatter type: `-` (YAML), `+` (TOML), `;` (JSON) or `{` (JSON) | | superSubScript | Boolean | `false` | Enable pandoc's markdown extension superscript and subscript. | -| footnote | Boolean | `false` | Enable pandoc's markdown extension footnote | +| footnote | Boolean | `false` | Enable pandoc's footnote markdown extension | #### Theme diff --git a/src/muya/lib/parser/index.js b/src/muya/lib/parser/index.js index 6fe34794..d1e48033 100644 --- a/src/muya/lib/parser/index.js +++ b/src/muya/lib/parser/index.js @@ -223,6 +223,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top, labels, option continue } } + // footnote identifier if (pos !== 0 && footnote) { const footnoteTo = inlineRules.footnote_identifier.exec(src) diff --git a/src/muya/lib/parser/marked/inlineLexer.js b/src/muya/lib/parser/marked/inlineLexer.js index e8271fc1..6349c8c7 100644 --- a/src/muya/lib/parser/marked/inlineLexer.js +++ b/src/muya/lib/parser/marked/inlineLexer.js @@ -65,7 +65,6 @@ InlineLexer.prototype.output = function (src) { let lastChar = '' while (src) { - console.log(src) // escape cap = this.rules.escape.exec(src) if (cap) { diff --git a/src/muya/lib/parser/marked/lexer.js b/src/muya/lib/parser/marked/lexer.js index 3603df3f..24649f59 100644 --- a/src/muya/lib/parser/marked/lexer.js +++ b/src/muya/lib/parser/marked/lexer.js @@ -10,6 +10,7 @@ function Lexer (opts) { this.tokens = [] this.tokens.links = Object.create(null) this.tokens.footnotes = Object.create(null) + this.footnoteOrder = 0 this.options = Object.assign({}, options, opts) this.rules = normal @@ -29,6 +30,7 @@ Lexer.prototype.lex = function (src) { .replace(/\r\n|\r/g, '\n') .replace(/\t/g, ' ') this.checkFrontmatter = true + this.footnoteOrder = 0 this.token(src, true) // Move footnote token to the end of tokens. const { tokens } = this @@ -163,6 +165,7 @@ Lexer.prototype.token = function (src, top) { identifier }) this.tokens.footnotes[identifier] = { + order: ++this.footnoteOrder, identifier, footnoteId: getUniqueId() } diff --git a/src/muya/lib/parser/marked/renderer.js b/src/muya/lib/parser/marked/renderer.js index 7a9139de..b25730dc 100644 --- a/src/muya/lib/parser/marked/renderer.js +++ b/src/muya/lib/parser/marked/renderer.js @@ -44,8 +44,8 @@ Renderer.prototype.script = function (content, marker) { return `<${tagName}>${content}` } -Renderer.prototype.footnoteIdentifier = function (identifier, { footnoteId, footnoteIdentifierId }) { - return `${identifier}` +Renderer.prototype.footnoteIdentifier = function (identifier, { footnoteId, footnoteIdentifierId, order }) { + return `${order || identifier}` } Renderer.prototype.footnote = function (footnote) { diff --git a/src/muya/lib/parser/marked/utils.js b/src/muya/lib/parser/marked/utils.js index f5794128..4a146bd3 100644 --- a/src/muya/lib/parser/marked/utils.js +++ b/src/muya/lib/parser/marked/utils.js @@ -2,9 +2,9 @@ * Helpers */ -let i = 0 +let uniqueIdCounter = 0 -export const getUniqueId = () => ++i +export const getUniqueId = () => ++uniqueIdCounter export const escape = function escape (html, encode) { if (encode) { diff --git a/src/muya/lib/utils/importMarkdown.js b/src/muya/lib/utils/importMarkdown.js index 6d75d846..fba3ffe1 100644 --- a/src/muya/lib/utils/importMarkdown.js +++ b/src/muya/lib/utils/importMarkdown.js @@ -79,7 +79,6 @@ const importRegister = ContentState => { } const { trimUnnecessaryCodeBlockEmptyLines, footnote } = this.muya.options const tokens = new Lexer({ disableInline: true, footnote }).lex(markdown) - console.log(JSON.stringify(tokens, null, 2)) let token let block let value @@ -573,7 +572,6 @@ const importRegister = ContentState => { results.add(attrs.src) } else { const rawSrc = label + backlash.second - console.log(render.labels) if (render.labels.has((rawSrc).toLowerCase())) { const { href } = render.labels.get(rawSrc.toLowerCase()) const { src } = getImageInfo(href) From 38d24056c40619662805c19b466aaa118e3528cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E5=86=89?= Date: Wed, 30 Oct 2019 12:42:39 +0800 Subject: [PATCH 13/29] Enter to create footnote --- src/muya/lib/contentState/enterCtrl.js | 24 +++++++++++++++++++ .../render/renderBlock/renderLeafBlock.js | 2 ++ 2 files changed, 26 insertions(+) diff --git a/src/muya/lib/contentState/enterCtrl.js b/src/muya/lib/contentState/enterCtrl.js index db084aa1..8393a966 100644 --- a/src/muya/lib/contentState/enterCtrl.js +++ b/src/muya/lib/contentState/enterCtrl.js @@ -1,6 +1,10 @@ import selection from '../selection' import { isOsx } from '../config' +/* eslint-disable no-useless-escape */ +const FOOTNOTE_REG = /^\[\^([^\^\[\]\s]+?)(? { const pairStr = text.substring(offset - 1, offset + 1) return /^(\{\}|\[\]|\(\)|><)$/.test(pairStr) @@ -226,6 +230,26 @@ const enterCtrl = ContentState => { return this.enterHandler(event) } + if ( + block.type === 'span' && + block.functionType === 'paragraphContent' && + !this.getParent(block).parent && + start.offset === text.length && + FOOTNOTE_REG.test(text) + ) { + event.preventDefault() + event.stopPropagation() + // Just to feet the `updateFootnote` API and add one white space. + block.text += ' ' + const key = block.key + const offset = block.text.length + this.cursor = { + start: { key, offset }, + end: { key, offset } + } + return this.updateFootnote(this.getParent(block), block) + } + // 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 diff --git a/src/muya/lib/parser/render/renderBlock/renderLeafBlock.js b/src/muya/lib/parser/render/renderBlock/renderLeafBlock.js index 54b493f8..a9f3ea64 100644 --- a/src/muya/lib/parser/render/renderBlock/renderLeafBlock.js +++ b/src/muya/lib/parser/render/renderBlock/renderLeafBlock.js @@ -247,6 +247,8 @@ export default function renderLeafBlock (parent, block, activeBlocks, matches, u } else if (type === 'span' && functionType === 'languageInput') { const html = getHighlightHtml(text, highlights) children = htmlToVNode(html) + } else if (type === 'span' && functionType === 'footnoteInput') { + Object.assign(data.attrs, { spellcheck: 'false' }) } if (!block.parent) { From 0d001649b1466ae257735d1c53c178fc72387380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E5=86=89?= Date: Wed, 30 Oct 2019 12:56:03 +0800 Subject: [PATCH 14/29] modify some style of footnote --- src/muya/lib/assets/styles/exportStyle.css | 2 +- src/muya/lib/assets/styles/index.css | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/muya/lib/assets/styles/exportStyle.css b/src/muya/lib/assets/styles/exportStyle.css index 5aced124..4d15756d 100644 --- a/src/muya/lib/assets/styles/exportStyle.css +++ b/src/muya/lib/assets/styles/exportStyle.css @@ -1,5 +1,5 @@ .footnotes { - font-size: .9em; + font-size: .85em; opacity: .8; } diff --git a/src/muya/lib/assets/styles/index.css b/src/muya/lib/assets/styles/index.css index 82ef43e2..91734a53 100644 --- a/src/muya/lib/assets/styles/index.css +++ b/src/muya/lib/assets/styles/index.css @@ -183,7 +183,7 @@ figure[data-role="HTML"].ag-active .ag-html-preview { figure[data-role="FOOTNOTE"] { position: relative; background: var(--footnoteBgColor); - padding: 1em 2em .05em 1em; + padding: 1.2em 2em .05em 1em; font-size: .8em; opacity: .8; } @@ -197,7 +197,7 @@ figure[data-role="FOOTNOTE"].ag-active::before { content: attr(data-role); text-transform: lowercase; position: absolute; - top: .15em; + top: .2em; right: 1em; color: var(--editorColor30); font-size: 12px; @@ -211,7 +211,7 @@ figure[data-role="FOOTNOTE"] .ag-footnote-input { padding: 0 1em; min-width: 80px; position: absolute; - top: 0px; + top: 0.2em; left: 0; font-size: 14px; font-family: monospace; From 09c7244dfe594c5366a0a9dac0b4d41e60bdbbd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E5=86=89?= Date: Wed, 30 Oct 2019 16:58:11 +0800 Subject: [PATCH 15/29] update footnote icon and add highlight format icon button --- src/muya/lib/assets/pngicon/footnote/1.png | Bin 0 -> 500 bytes src/muya/lib/assets/pngicon/footnote/2.png | Bin 1675 -> 857 bytes src/muya/lib/assets/pngicon/footnote/3.png | Bin 0 -> 1238 bytes src/muya/lib/assets/pngicon/highlight/1.png | Bin 0 -> 795 bytes src/muya/lib/assets/pngicon/highlight/2.png | Bin 0 -> 1491 bytes src/muya/lib/assets/pngicon/highlight/3.png | Bin 0 -> 2185 bytes src/muya/lib/ui/formatPicker/config.js | 6 ++++++ 7 files changed, 6 insertions(+) create mode 100755 src/muya/lib/assets/pngicon/footnote/1.png mode change 100644 => 100755 src/muya/lib/assets/pngicon/footnote/2.png create mode 100755 src/muya/lib/assets/pngicon/footnote/3.png create mode 100755 src/muya/lib/assets/pngicon/highlight/1.png create mode 100755 src/muya/lib/assets/pngicon/highlight/2.png create mode 100755 src/muya/lib/assets/pngicon/highlight/3.png diff --git a/src/muya/lib/assets/pngicon/footnote/1.png b/src/muya/lib/assets/pngicon/footnote/1.png new file mode 100755 index 0000000000000000000000000000000000000000..379ca9a6641bde4e36942e97cadd09143821c738 GIT binary patch literal 500 zcmeAS@N?(olHy`uVBq!ia0vp^B0wz4!3HGVuAW-~q!^2X+?^QKos)S9tDWDQNthH(0^`ymT3Bqug;B)KjnWPId{!jVXK4pf(QTO zWuuMvRvwXJyJWg??Jrez?(4c@V&~cok{_`B`~UsEf{xA`eV?WXsV5Ec_WSG`R<=uX zl)TQJp|JAHm3v_o7ZiE^9ADtI%F*EK&7)WUC+4&?F4k*l#R*B1Qj*f9&b@Q>1B+T;KijkVnf3Mc_G~X(S2`CdK4zTo z;D7!8^hS|GA`cchW;tXjeiGQLV0NhWj7};~{r?@z-G=A?J481ocAeJQU~wTeVb;XP zkFv@h`XUC0C6@Qe{Yc4sy`|^j|9Z~_;w}x3_83^^n4FmHaCjR7FSnXm$A+2C^@a?0 W`Pv+xvoP-lg}A4ypUXO@geCxXzrwu$ literal 0 HcmV?d00001 diff --git a/src/muya/lib/assets/pngicon/footnote/2.png b/src/muya/lib/assets/pngicon/footnote/2.png old mode 100644 new mode 100755 index f5be4b5e3f9c7cfada36dc825a84256e161fe2e3..bff14d4d6447312880da5fe7d6eb1421d0e80802 GIT binary patch literal 857 zcmV-f1E&0mP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91D4+uX1ONa40RR91DgXcg001U6%m4ra!%0LzR9Fe^SWRdXQ4pTV?)npx zOKc8W=}AE-f}nU+JQVaI9tBYk9)u>LZ5nHvq-;!DNNTWlQ@!-ysYouKym*j+cu-FY zJy-=nL{BPesM_uBb6#*=2TTR zP_0%aU0+7iWEV_k>~)J#LSiaZKsm56P#AFl_vL4srJ7E2x;Z~V`oe& z>>r8Db1WRr+$6rU10z{psb_Y4p~R6$#Hf@DC!=AdS5wtb_Tcq;Mud3;95SWgdnbob z;#e~C8W0w`Lh>={MbkN<-L_C-lDZohbhkUX{$zUfE1oS+G>9f7xJgm=v|FW(#Ya*< z=lB1zDxt)b*zQgw=l5XSRQG$(Lu$VVJ%nk!+wVaSP5mD95N6}M8}?p&xmGW9YU$4X zps?ldRAOGdeu7@g{Hfn%KZtbz7P0`8&B2}1lj*mX-O6!bY+{H0ZRyP~OwQeS2_>cz zh*$OyLti>#w_Ew_#CCFXE-^uXyY?jL0H8Fesu8=d70(=#W81Olm(sw^-w7p_rNEqS zumqhhtR_$6F1^0;B%VrNl$2m?t>xxr<;oFeiDO#{(n*6UOE_oQ)+qc3`HOfmQ!#<{ zTS4s@1Awzyxe#-52_@zM+r#n@t>~UJc)e`S#W~&ZB{yfSRx^(G9liB+nMDBcAjP;U z?Kbyse7vlr+;uudJ3}WK%WiX<5)Y1KdshteJe^DQQB(KWe?e}5&3#zsUn~D0c)wpQ j<`fi1|>#WAEJ?(N-+d6xqOTrYn3 zFVDN`h_u3;oql_-M%q+gGX7ohe3z?N-JZDh^Vg^U`&;qsc-`#UuiseBFC6#f`Xb&? zw>PeQzv7Fx+*Qga#2xA-Vtb<^G1UrkHofIX^9nk6Lf$Fk$tQe|2~7sz1DH z(*Na|;Oi{g=hyGnCfgPr-t$mpkJq<}wVXv;=Y4(+v~Gez`X4=id!}E!@AuqK?O%P= zcGG&{Cm;=$j^`v`mRmdOeSf?@;$K&3&E}dt2g(KiYW28(``S?oGQ}oot|81B-#SWn z%J)aeZS2bz>yOy3@aKPb#gSk3i+8X4&{694Y2xJh3h7I0(`G5mU-M~^O{mfD}$q!?}>>j4ii#zokBST zmJAW8eQZ{d-2v<@dH;VkN5_BoT4gzv@0Mwm&x32fZ*mshX&3JRhRhm)!YCGwORt<~ zNia4Y;SyZ#+Mux3Zb|20mU>X;81>q-*`cGsKZ$>;qE|nIW7S*_5haHS0g{Wp03+yn zr;8#J3+K>~I?%4Ly)LFE-?dX0R>ymUJzqqxp`@etvk>Mhte{*8Jlu4Z_QI?Vv$TV-U7^A+e~uL3o>R= z)SwVOYuod|YuyQMqg$s0m|U6O|CsmX(L5tXgMsz_4_3uT5Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91JfH&r1ONa40RR91KL7v#0L*`ri~s-xKuJVFRA>e5T1{vZK@^_ZB&|wn z@vmwT4_ZMf=ur?+(Su5*UPKhcix$Bqg|=x$T488Y)F!21(@U%ce?UC8D0=atco9!R zMX?q9LGY(oNZM+X+3~&ThLX-CE45}5XD&1QX5P&Az4zwrusek4qAj2;pe>*+;9LvP zKY`|FY^gCt_HZ+Kywv0F?77HZpuR8AJ}QLRh;755Reho0ST=cmbK7g^ufj1U9?z!U zOKcR|e6DqY9{%x zCzgo&18qLL5ONYe%a|9exdbO%;dKpCO2)A*2wj79z6uw77p_ey<-}z?wu>WcNCQ## z*U98_VE3M`uc+;7X!udz(tZf6G~-^o{Bdtn)3n`%{`R=Wa~G;-xHty{-bBJdhV%QH z+2)zp+OcT#Oah-}#Vr$Bvhg^-4B@*4p107qpCVA#^@oCYt^R!0y_Y*jVEvO8hK9g} zqg*7fm6p8=fvm%lXVzIm+Au_v5_$)e5-{*)rm|C>{jF@BNyKf~XkL>LhRIchEo(?y zN`U~Y2#48c>)DAlyeS3&*9IVTyL5H=t#C`rqztE$FoZ~xX^7E9X8FT#SgtIa(CIQ| zNW9oB~Bs;DFwnab|=i|I5@Zf++U@?r%M{WO2Y7h zdtfOSQ>`T29}XTYCZhi>7ipZMH5yvl4_H>Y5(*Ygcq>Dio8rDj7;2?qW{oL`rjl?$ zpw9rQvvh_*@KNbuouwt#DnyYo8R;xti0+hNR_0zCvCDtlLk)iQZL zWGXBw?1)iG7=Es#E|+Bz;Y6Iul;!JLiA8}OaViN5O7F>tz|DZuaGzqYtnXjS5CdY7 zt*OF}W0iyr>K=lAE}0M_M%>CL>D1bQ0kFwViu^Zr33RzadZ53#?J8UYl3Ec4fg$OD zi_mM!ysnXjPGp#%aL-~G|MskmEP8ODucxanO(LTqRkFtTT3Mwq6EESSN0}vabWKT& z8lKIOZYE8*h+P@d*olhNpeMkS;Am$T#E4k4kwpfsTr{3OdGu=gj@=WmTlT})tPzoG zu7&DPktMV-5(?hPwT5j1zSj0yVM3yCT6m|z7(E*t&P5jcb9&gCB<1Oy`>q?pEQiZb z+d!UmJ6L)PSaSd{NL-#Hk?xLXcGt1(;n@?WE^~_HVJbuoczh$=ovxG}Luc#EzfI$* z6BHaO@I8yKp1yAOVgB>#8MFno1+)dU1)O7npFo^_ErB9i{{R3007*qoM6N<$f^tVK A$p8QV literal 0 HcmV?d00001 diff --git a/src/muya/lib/assets/pngicon/highlight/1.png b/src/muya/lib/assets/pngicon/highlight/1.png new file mode 100755 index 0000000000000000000000000000000000000000..107c43f1376bdf4777b53057f9ee7866e97ccbb2 GIT binary patch literal 795 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VV{wqX6T`Z5GB1G~m(&Q)G+$o^ zEg+kNfw4W4fd!-lh^2s-fq{7eBLg##W(0{XV1mnvEMP{kK?*nB{qUE8fvMHg#WBP} zaBawWZ;?Wg`uDqoy&^sAJv4u@iz$D(;5uzm$06quPX4D07c98uduJ-&E`tYBTTLU^ zR%?{#%bu|n5iHd_)ig!u@}w7&Jj9h(I-I#ymbP#GcUj4sk!f!?*xq>m`)lR=+V{2T zY7E&PDpL6zre+skyg0GmG+~}d)gC2vnYZ(pS`-9q1T~DJR~<=?y}njy#&KQGihaTB z*)#n_7D@i$4Y+faB{fe)cs^J0y4XnuGOxXa4_dON?Ks`HmfY%3zZZR-+OR56xcb-nFFvdPv_{m-_np+av9)-1D39Q@2^&mPRo-dG zuUH&Wr!(`ojL`wk={|{hajz>c=YN@Zcbn&G*2YB9$C_pWhn8%b`lC;#uhd?3QFDie z`&@rlxxq$Q{*ht(%hP{k z?*Cf*&98R7mYRy(Tz8%W3ofn8SoiQ^#`7ItnOPq3u_icrWTic-I<{rzrJUqPncrSl z>|vO-?7BAv(u9cOwobL0|Vd3Lj zkCWHWGjDTxpTnsu6I{LS>HRvEld%t(-zlAby}@CP@x6U=GmBqO`SvzI)kj+T%CZ}C z9zM@i7YY@6P_^$`nD>H-`(|0JDLDG5TJvU3SZUoYM(&*S3wt&NUgF%>=Di}5+3(!* z4{tv_{n4&`=cFFH{!79BrLS1e<7ad@-(U37f&C|Je*AxGk-m(T(@%S#G{*()@gJHe6T+JsQUC%f%PR33!f^c*Xp%*$iJMw>Yskl W)(6{J9ZwtuB}`9OKbLh*2~7Y_vso(u literal 0 HcmV?d00001 diff --git a/src/muya/lib/assets/pngicon/highlight/2.png b/src/muya/lib/assets/pngicon/highlight/2.png new file mode 100755 index 0000000000000000000000000000000000000000..eb36a37cbac861be17fcf84409154e741fdbcc5f GIT binary patch literal 1491 zcmV;^1uXiBP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91D4+uX1ONa40RR91C;$Ke0D9(TtN;K7JxN4CR9Fe^S6fURRTw^JW(UGe zi6!cTG`5L}sZ@xh0hG_*isj24JZ8WyW8U|TMk$Pywf(85+9l^_-LKHx*X@xTsiA>7#z=X zN>5Nxh~#Am<0t{r)OLzbFR$Q#}sMknY@!FxPs;anA#}hz+TLo~x!NG!^ zB+@kpdV)V1of`KoEpO)BWU(sn>R`LUC^PTr_lNp|1!f*0J}YApx(4zEa7g8sPT(|O zJw30-uncR{zfXRYD{3v}{6!Sw1DaePaH{Df4A(V?HlgXhWb(l2h>2c=2zSdr9SQ|o zL?78R0S0Y-1A&mcuB&5l_JX@3v%X~-5LYt~7mm}>r^$iyc@-TSX3Y7+E( zmxD7>YgI*>G63ua%+CPw*M>lATSJTdkX6q}H#PNUF-oe9JhZ8Zkv8sK10;g*0YWs6 zUu*XwtcIYIGxY)*`6yaQCCVbrc$l`IrPnJ7CpkJF{-#Td3r^~^b#ME0OcYT*?jzFh!H_(qDI(I7ur1Y%0I(ac%YL1(xQ1JV)8YSd&ke*GVqMdd=wU4yU6 zaxM<2FdmLnt;B25F{iR~g!1i(WPh3{6`@lOYUwrVCfmb4Y40=Yx&|1(9bbha{L5%; z*rWIIa?aUtC9aO61SBd*Tk$Se0rW{kD)C}b63d{u9~yil-Fmttnuk4s*3C>1cQF=! z9;CB9B~t(w5hUL!KC5g4${N_L*8Ctc?+*2Ky{zq>hU*CGP7d~UO%l#dgLVd|8F2DA zXZ!%0Q@>%9K0ySj5`ed0ju26LNB8Npdc7p`zEJS^f5iPj`n7(zgK`!L_XS6>pKxpK zIfAbQ+SlRpsZc)v(ZIt%8xha%bl$%!?v!a<++0#HX5zRYJOe~evqX=NA}Ry8wO<*} zL;lBbLH1(){10C^YW>zT>f7xXuOovcp04aGF%|z-@@$J>~feb)f%o8>~gXUyuDO1_Admn!W##R6BW7@=1eI|!HM zTkXqvF}-E^-HL#z&G7Sm`9xGU-L1pz&Y^4ezOT#7HJCmpkvO?W7cPa>^)3Ftz%B#4 t)5tGJ@J=IWzQxyPXGm_2_CdL1#^0RhzFBz7a(Vy&002ovPDHLkV1gcwx(EOO literal 0 HcmV?d00001 diff --git a/src/muya/lib/assets/pngicon/highlight/3.png b/src/muya/lib/assets/pngicon/highlight/3.png new file mode 100755 index 0000000000000000000000000000000000000000..4d1602263a88cfee0bb6ddeab5be95cb4ca0348c GIT binary patch literal 2185 zcmV;42zK|0P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91JfH&r1ONa40RR91JOBUy0E^%0TmS$E^GQTORA>e5T77I(RTjVJ&YRj# z*9e&S0e+y-M2ml*_^|qFB%v zzunDdSy?|&<0dOH2pSbd@Po~6nO2y2@8130(lOJS`#SH(bkFO%4-V(} zao*@usI6GhJV7M-4?%V~B_E3ROQP;@K~c}T=IG%R{fEAv62MEA)>TU;*E)c)iP6g} z#Jb8SPkeFFqD4suzEeUzHGm`0_oZZ4b6!bjm}8# z59&Ek4_B{UTa@_n=wT+=F9I^~BhJ|tqo8Q(!%cN62>Nb;0Q>esZeEl~CO?9XESa1_w{*l-y_l}Q zs@9xjS;_gJEx>gFF7J)#9g@^tVw60&X3g^dxe<_MoSQ*9p2aow4NQo-%szPX%Nf(A zOqxA=HdcFflTuPu?H_(6gjk5c^&5DyFq>;+3P5~^CI2+%ynFqcKkkA~h|;$}0OOnD z-^Q)aVElQRYKZZNI@=mnWYTr-_nYP!cE5F}6zpDn>r-_+xo8M-(T>)pW+y(wslyBX zKmFE?ICea!rKXIta_<4eOT(9On@eI<9GqknE#ZSHrHsp!5vym&?PIF=l^jbZc6 zY%tKZcQePP-iNlJ9eCe?Q`~t>*i}-BNUA^m&N+3Zzw4fTdzh>NEIC<_DI8!Ep_0(E znRGwz9WCY;2s)qZWl;PydAgdI5u0zo0IOZjY7(q?a(_jnzOA~p{_+v6@X+a`4}1Mn zu~XUsbmh|esWubu=29})PqatvnN$|qo^Nt6lD?3^e%ZdJ>5W{r`0}tIq4lm- zsg&19CN`<$U~#0br=q5QCcNco!kzR%$UZkk z=XA+IMNRZhA=nd{Hejq(M8?F{R`W<^pYGk4hf@NlPc7fe3XF~4CkI4-ISC@gG4dTt zo8xeClVEa(N)AvWTDPROzC3*tz^Vz&Zj!y^7OWZ^jE5sDX9;P)0FZA&W>+yU>$Spt zXCzM0e7r9JpbK&L+z@%+h0N{{Gu}*!3n%T6SX4GUgl-2Zx4%P|2!!CzW$u4 z9N;cN_On{r$%Fm&v28-~BjuM&x}FR^g%eu`$9}xU*m9Q?oT#h4Wz}ny%T~@9{MH$}9rB8mo>T+a{$hUm*)1)# z$5J%6_do!rD?{Qw*OJL?=`^p;Lmu8j+}O&BS)V*+&yj>A1;^7NS&wgMYg*t{uHyH# zq;(1{X5FQud!fOzlW{dlfwECNxt+ND8=~>GoP{sv#Mc_cAN3+Hoi|VqtKWoi*!UF2 z1~-vJ$HS$@*+b=@bldZK4$d&BPbT>99Kd>rzQb|ABhLf{R!I&FaBpufwjF&jfH<{> zC|r)rs$)1DS8rXexLh&?26*?|dv6BdtTOGO9ft8(d(7O666z~Xb|Fx$MD!h2OaSc@ z98Lf3G$tQFV?o7$)#>jL)tP>J+)81roGW z0*~K>KZVXvre5r;%L=6IO;WhbmV+5j)-e>n7~BI-_N0$RPOJ)p1-Qr4>3*MmmuZy& z*gjOQngv$%!K*FD8Eauu|IY?|*L~wkLRrQ0p&~%uzY;3ly5@UH!!VV+E5La+xg9dp zDeDJmz5ySNi<{u%iTPR^!f(F$j?HJBZ@^B$%8fCI${(zcHF*9%h#T2XJiY)|M;p&Y z(QcNL@LY6+-$3`+T>4bMub^cmj^Bzb=Lv2J~jhmGvH|kv?ne3 zVe@4;N8lRm&E7|sJ~*uIr4L$yN)S`d8trgV!TLNCgRgBVQ`l&D9|JrZyyVBmF%SEh zz0ojs%)_I>OMYzp*?HLE;2~_rjT3%m@+UD?Sb5jOe-icwVZ{Fd&=c0-=%RV*00000 LNkvXXu0mjfq|+*t literal 0 HcmV?d00001 diff --git a/src/muya/lib/ui/formatPicker/config.js b/src/muya/lib/ui/formatPicker/config.js index 34db3463..73aa302d 100644 --- a/src/muya/lib/ui/formatPicker/config.js +++ b/src/muya/lib/ui/formatPicker/config.js @@ -7,6 +7,7 @@ import imageIcon from '../../assets/pngicon/format_image/2.png' import linkIcon from '../../assets/pngicon/format_link/2.png' import strikeIcon from '../../assets/pngicon/format_strike/2.png' import mathIcon from '../../assets/pngicon/format_math/2.png' +import highlightIcon from '../../assets/pngicon/highlight/2.png' import clearIcon from '../../assets/pngicon/format_clear/2.png' const COMMAND_KEY = isOsx ? '⌘' : '⌃' @@ -32,6 +33,11 @@ const icons = [ tooltip: 'Strikethrough', shortcut: `${COMMAND_KEY}+D`, icon: strikeIcon + }, { + type: 'mark', + tooltip: 'Highlight', + shortcut: `⇧+${COMMAND_KEY}+H`, + icon: highlightIcon }, { type: 'inline_code', tooltip: 'Inline Code', From a006781fad5797082d48455e7eca56991c570d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E5=86=89?= Date: Thu, 31 Oct 2019 15:56:42 +0800 Subject: [PATCH 16/29] fix #1568 --- src/muya/lib/eventHandler/mouseEvent.js | 10 +++++++++- src/muya/lib/ui/formatPicker/index.css | 8 ++++---- src/muya/lib/ui/frontMenu/index.css | 12 ++++++------ 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/muya/lib/eventHandler/mouseEvent.js b/src/muya/lib/eventHandler/mouseEvent.js index 4611bb45..ca7fff02 100644 --- a/src/muya/lib/eventHandler/mouseEvent.js +++ b/src/muya/lib/eventHandler/mouseEvent.js @@ -14,6 +14,7 @@ class MouseEvent { const target = event.target const parent = target.parentNode const preSibling = target.previousElementSibling + const parentPreSibling = parent ? parent.previousElementSibling : null const { hideLinkPopup, footnote } = this.muya.options const rect = parent.getBoundingClientRect() const reference = { @@ -22,7 +23,14 @@ class MouseEvent { } } - if (!hideLinkPopup && parent && parent.tagName === 'A' && parent.classList.contains('ag-inline-rule')) { + if ( + !hideLinkPopup && + parent && + parent.tagName === 'A' && + parent.classList.contains('ag-inline-rule') && + parentPreSibling && + parentPreSibling.classList.contains('ag-hide') + ) { eventCenter.dispatch('muya-link-tools', { reference, linkInfo: getLinkInfo(parent) diff --git a/src/muya/lib/ui/formatPicker/index.css b/src/muya/lib/ui/formatPicker/index.css index cbbf1590..62660956 100644 --- a/src/muya/lib/ui/formatPicker/index.css +++ b/src/muya/lib/ui/formatPicker/index.css @@ -49,8 +49,8 @@ .ag-format-picker li.item .icon-wrapper { display: flex; - width: 14px; - height: 14px; + width: 16px; + height: 16px; } .ag-format-picker li.item .icon-wrapper i.icon { @@ -67,9 +67,9 @@ display: inline-block; width: 100%; height: 100%; - filter: drop-shadow(14px 0 currentColor); + filter: drop-shadow(16px 0 currentColor); position: relative; - left: -14px; + left: -16px; } .ag-format-picker li.item.active .icon-wrapper i.icon { diff --git a/src/muya/lib/ui/frontMenu/index.css b/src/muya/lib/ui/frontMenu/index.css index c8d8c8a1..28b07ae0 100644 --- a/src/muya/lib/ui/frontMenu/index.css +++ b/src/muya/lib/ui/frontMenu/index.css @@ -33,16 +33,16 @@ margin-left: 10px; margin-right: 8px; display: flex; - width: 14px; - height: 14px; + width: 16px; + height: 16px; color: var(--iconColor); } .ag-front-menu li.item .icon-wrapper i.icon { display: flex; position: relative; - height: 14px; - width: 14px; + height: 16px; + width: 16px; overflow: hidden; color: var(--iconColor); transition: all .25s ease-in-out; @@ -52,9 +52,9 @@ display: inline-block; width: 100%; height: 100%; - filter: drop-shadow(14px 0 currentColor); + filter: drop-shadow(16px 0 currentColor); position: relative; - left: -14px; + left: -16px; } .ag-front-menu > ul li > span { From 154b22058dfee4f8e605c430d773fef39b748b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E5=86=89?= Date: Thu, 31 Oct 2019 16:41:09 +0800 Subject: [PATCH 17/29] fix #1569 --- src/muya/lib/contentState/imageCtrl.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/muya/lib/contentState/imageCtrl.js b/src/muya/lib/contentState/imageCtrl.js index 7279bec0..2eab7982 100644 --- a/src/muya/lib/contentState/imageCtrl.js +++ b/src/muya/lib/contentState/imageCtrl.js @@ -177,11 +177,19 @@ const imageCtrl = ContentState => { this.selectedImage = imageInfo const { key } = imageInfo const block = this.getBlock(key) + const outMostBlock = this.findOutMostBlock(block) this.cursor = { start: { key, offset: imageInfo.token.range.end }, end: { key, offset: imageInfo.token.range.end } } - return this.singleRender(block, true) + // Fix #1568 + const { start } = this.prevCursor + const oldBlock = this.findOutMostBlock(this.getBlock(start.key)) + if (oldBlock.key !== outMostBlock.key) { + this.singleRender(oldBlock, false) + } + + return this.singleRender(outMostBlock, true) } } From ba3e6f6110c63b0be9893341fffca42d436d8196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4usler?= Date: Fri, 1 Nov 2019 18:37:31 +0100 Subject: [PATCH 18/29] Improve spellchecker UX (#1575) --- src/main/menu/templates/paragraph.js | 2 +- src/renderer/contextMenu/editor/spellcheck.js | 3 ++- .../prefComponents/spellchecker/index.vue | 13 +++++++++---- .../spellchecker/dictionaryDownloader.js | 19 +++++++++++++++++-- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/main/menu/templates/paragraph.js b/src/main/menu/templates/paragraph.js index d927de53..b6400a05 100644 --- a/src/main/menu/templates/paragraph.js +++ b/src/main/menu/templates/paragraph.js @@ -166,7 +166,7 @@ export default function (keybindings) { } }, { id: 'frontMatterMenuItem', - label: 'YAML Front Matter', + label: 'Front Matter', type: 'checkbox', accelerator: keybindings.getAccelerator('paragraphYAMLFrontMatter'), click (menuItem, browserWindow) { diff --git a/src/renderer/contextMenu/editor/spellcheck.js b/src/renderer/contextMenu/editor/spellcheck.js index d49becad..7be59cd0 100644 --- a/src/renderer/contextMenu/editor/spellcheck.js +++ b/src/renderer/contextMenu/editor/spellcheck.js @@ -1,6 +1,7 @@ import { remote } from 'electron' import log from 'electron-log' import bus from '@/bus' +import { getLanguageName } from '@/spellchecker/languageMap' import { SEPARATOR } from './menuItems' const { MenuItem } = remote @@ -24,7 +25,7 @@ export default (spellchecker, selectedWord, wordSuggestions, replaceCallback) => const availableDictionariesSubmenu = [] for (const dict of availableDictionaries) { availableDictionariesSubmenu.push(new MenuItem({ - label: dict, + label: getLanguageName(dict), enabled: dict !== currentLanguage, click () { bus.$emit('switch-spellchecker-language', dict) diff --git a/src/renderer/prefComponents/spellchecker/index.vue b/src/renderer/prefComponents/spellchecker/index.vue index 5e7e67b5..5de8b4c3 100644 --- a/src/renderer/prefComponents/spellchecker/index.vue +++ b/src/renderer/prefComponents/spellchecker/index.vue @@ -8,7 +8,7 @@ >
    -
    Available Hunspell dictionaries. Please add additional language dictionaries via button below.
    +
    List of available Hunspell dictionaries. Please add additional language dictionaries via drop-down menu below.
    @@ -65,7 +65,7 @@ -
    Add new dictionaries to Hunspell.
    +
    Download new dictionaries for Hunspell.
    { responseType: 'stream' }) + const dstFile = path.join(dictionaryPath, `${lang}.bdic`) + const tmpFile = `${dstFile}.tmp` return new Promise((resolve, reject) => { - const outStream = fs.createWriteStream(path.join(dictionaryPath, `${lang}.bdic`)) + const outStream = fs.createWriteStream(tmpFile) response.data.pipe(outStream) + + let totalLength = 0 + response.data.on('data', chunk => { + totalLength += chunk.length + }) + outStream.once('error', reject) - outStream.once('finish', () => resolve()) + outStream.once('finish', async () => { + if (totalLength < 8 * 1024) { + throw new Error('Dictionary is most likely bogus.') + } + + await fs.move(tmpFile, dstFile, { overwrite: true }) + resolve() + }) }) } From 30997f0d26ac52ce672a2b3549fe7e168c571520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4usler?= Date: Sat, 2 Nov 2019 02:09:20 +0100 Subject: [PATCH 19/29] Fix no underline spell checker mode (#1584) --- src/renderer/spellchecker/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/renderer/spellchecker/index.js b/src/renderer/spellchecker/index.js index 29aea457..7e9eba2a 100644 --- a/src/renderer/spellchecker/index.js +++ b/src/renderer/spellchecker/index.js @@ -320,21 +320,21 @@ export class SpellChecker { /** * Returns true if not misspelled words should be highlighted. */ - get spellcheckerNoUnderline () { + get isPassiveMode () { if (!this.isEnabled) { return false } - return this.provider.spellcheckerNoUnderline + return this.provider.isPassiveMode } /** * Should we highlight misspelled words. */ - set spellcheckerNoUnderline (value) { + set isPassiveMode (value) { if (!this.isEnabled) { return } - this.provider.spellcheckerNoUnderline = !!value + this.provider.isPassiveMode = !!value } /** From d7b655ac6c87f990d6ddfd8f379323012fae205a Mon Sep 17 00:00:00 2001 From: jocs Date: Sat, 2 Nov 2019 10:46:39 +0800 Subject: [PATCH 20/29] fix #1578 --- src/muya/lib/parser/marked/lexer.js | 4 ++-- src/muya/lib/utils/importMarkdown.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/muya/lib/parser/marked/lexer.js b/src/muya/lib/parser/marked/lexer.js index e5d19033..4dc000c0 100644 --- a/src/muya/lib/parser/marked/lexer.js +++ b/src/muya/lib/parser/marked/lexer.js @@ -48,7 +48,6 @@ Lexer.prototype.token = function (src, top) { let i let tag let l - let checked // Only check front matter at the begining of a markdown file. // Please see note in "blockquote" why we need "checkFrontmatter" and "top". @@ -233,6 +232,7 @@ Lexer.prototype.token = function (src, top) { // list cap = this.rules.list.exec(src) if (cap) { + let checked src = src.substring(cap[0].length) bull = cap[2] let isOrdered = bull.length > 1 @@ -367,7 +367,7 @@ Lexer.prototype.token = function (src, top) { const isOrderedListItem = /\d/.test(bull) this.tokens.push({ - checked: checked, + checked, listItemType: bull.length > 1 ? 'order' : (isTaskList ? 'task' : 'bullet'), bulletMarkerOrDelimiter: isOrderedListItem ? bull.slice(-1) : bull.charAt(0), type: loose ? 'loose_item_start' : 'list_item_start' diff --git a/src/muya/lib/utils/importMarkdown.js b/src/muya/lib/utils/importMarkdown.js index b49539af..8e30f624 100644 --- a/src/muya/lib/utils/importMarkdown.js +++ b/src/muya/lib/utils/importMarkdown.js @@ -79,6 +79,7 @@ const importRegister = ContentState => { } const { trimUnnecessaryCodeBlockEmptyLines } = this.muya.options const tokens = new Lexer({ disableInline: true }).lex(markdown) + let token let block let value From 60bb2a0d5cad0f5921e1c8e780118471b69e0ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4usler?= Date: Fri, 1 Nov 2019 17:32:15 +0100 Subject: [PATCH 21/29] Improve image picker UX --- src/muya/lib/ui/imageSelector/index.js | 81 +++++++++++++------ .../components/editorWithTabs/editor.vue | 2 +- 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/src/muya/lib/ui/imageSelector/index.js b/src/muya/lib/ui/imageSelector/index.js index 5e1e0229..789b9dc3 100644 --- a/src/muya/lib/ui/imageSelector/index.js +++ b/src/muya/lib/ui/imageSelector/index.js @@ -12,7 +12,7 @@ class ImageSelector extends BaseFloat { constructor (muya, options) { const name = 'ag-image-selector' - const { accessKey } = options + const { unsplashAccessKey } = options options = Object.assign(options, { placement: 'bottom-center', modifiers: { @@ -26,9 +26,13 @@ class ImageSelector extends BaseFloat { this.renderArray = [] this.oldVnode = null this.imageInfo = null - this.unsplash = new Unsplash({ - accessKey - }) + if (!unsplashAccessKey) { + this.unsplash = null + } else { + this.unsplash = new Unsplash({ + accessKey: unsplashAccessKey + }) + } this.photoList = [] this.loading = false this.tab = 'link' // select or link @@ -56,22 +60,27 @@ class ImageSelector extends BaseFloat { } Object.assign(this.state, imageInfo.token.attrs) - // load latest unsplash photos. - this.loading = true - this.unsplash.photos.listPhotos(1, 40, 'latest') - .then(toJson) - .then(json => { - this.loading = false - if (Array.isArray(json)) { - this.photoList = json - if (this.tab === 'unsplash') { - this.render() + + if (this.unsplash) { + // Load latest unsplash photos. + this.loading = true + this.unsplash.photos.listPhotos(1, 40, 'latest') + .then(toJson) + .then(json => { + this.loading = false + if (Array.isArray(json)) { + this.photoList = json + if (this.tab === 'unsplash') { + this.render() + } } - } - }) + }) + } + this.imageInfo = imageInfo this.show(reference, cb) this.render() + // Auto focus and select all content of the `src.input` element. const input = this.imageSelectorContainer.querySelector('input.src') if (input) { @@ -85,6 +94,10 @@ class ImageSelector extends BaseFloat { } searchPhotos = (keyword) => { + if (!this.unsplash) { + return + } + this.loading = true this.photoList = [] this.unsplash.search.photos(keyword, 1, 40) @@ -253,11 +266,15 @@ class ImageSelector extends BaseFloat { }, { label: 'Embed link', value: 'link' - }, { - label: 'Unsplash', - value: 'unsplash' }] + if (this.unsplash) { + tabs.push({ + label: 'Unsplash', + value: 'unsplash' + }) + } + const children = tabs.map(tab => { const itemSelector = this.tab === tab.value ? 'li.active' : 'li' return h(itemSelector, h('span', { @@ -285,7 +302,11 @@ class ImageSelector extends BaseFloat { } } }, 'Choose an Image'), - h('span.description', 'Choose image from you computer.') + h('span.description', { + props: { + style: 'user-select: none;' + } + }, 'Choose image from your computer.') ] } else if (tab === 'link') { const altInput = h('input.alt', { @@ -355,14 +376,18 @@ class ImageSelector extends BaseFloat { } }, 'Embed Image') const bottomDes = h('span.description', [ - h('span', 'Paste web image or local image path, '), + h('span', { + props: { + style: 'user-select: none;' + } + }, 'Paste web image or local image path. Use '), h('a', { on: { click: event => { this.toggleMode() } } - }, `${isFullMode ? 'simple mode' : 'full mode'}`) + }, `${isFullMode ? 'simple mode' : 'full mode'}.`) ]) bodyContent = [inputWrapper, embedButton, bottomDes] } else { @@ -386,7 +411,11 @@ class ImageSelector extends BaseFloat { const loadingCom = h('div.ag-plugin-loading') bodyContent.push(loadingCom) } else if (this.photoList.length === 0) { - const noDataCom = h('div.no-data', 'No result...') + const noDataCom = h('div.no-data', { + props: { + style: 'user-select: none;' + } + }, 'No result...') bodyContent.push(noDataCom) } else { const photos = this.photoList.map(photo => { @@ -429,7 +458,11 @@ class ImageSelector extends BaseFloat { return h('div.photo', [imageWrapper, desCom]) }) const photoWrapper = h('div.photos-wrapper', photos) - const moreCom = h('div.more', 'Search for more photos...') + const moreCom = h('div.more', { + props: { + style: 'user-select: none;' + } + }, 'Search for more photos...') bodyContent.push(photoWrapper, moreCom) } } diff --git a/src/renderer/components/editorWithTabs/editor.vue b/src/renderer/components/editorWithTabs/editor.vue index fa432767..328e0105 100644 --- a/src/renderer/components/editorWithTabs/editor.vue +++ b/src/renderer/components/editorWithTabs/editor.vue @@ -460,7 +460,7 @@ export default { Muya.use(EmojiPicker) Muya.use(ImagePathPicker) Muya.use(ImageSelector, { - accessKey: process.env.UNSPLASH_ACCESS_KEY, + unsplashAccessKey: process.env.UNSPLASH_ACCESS_KEY, photoCreatorClick: this.photoCreatorClick }) Muya.use(Transformer) From be64ab6d4bcf0304612dd9a31001adba39319f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4usler?= Date: Sat, 2 Nov 2019 15:01:28 +0100 Subject: [PATCH 22/29] Adjust style --- src/muya/lib/ui/imageSelector/index.css | 3 +++ src/muya/lib/ui/imageSelector/index.js | 24 ++++-------------------- src/muya/themes/default.css | 1 - 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/muya/lib/ui/imageSelector/index.css b/src/muya/lib/ui/imageSelector/index.css index f73df562..d3afbaf9 100644 --- a/src/muya/lib/ui/imageSelector/index.css +++ b/src/muya/lib/ui/imageSelector/index.css @@ -108,6 +108,7 @@ text-align: center; display: block; color: var(--editorColor30); + user-select: none; } .ag-image-selector span.description a { @@ -154,6 +155,7 @@ text-align: center; font-size: 14px; color: var(--editorColor); + user-select: none; } .ag-image-selector .more { @@ -161,6 +163,7 @@ color: var(--editorColor); text-align: center; margin-bottom: 20px; + user-select: none; } .ag-image-selector .photo { diff --git a/src/muya/lib/ui/imageSelector/index.js b/src/muya/lib/ui/imageSelector/index.js index 789b9dc3..e11f9592 100644 --- a/src/muya/lib/ui/imageSelector/index.js +++ b/src/muya/lib/ui/imageSelector/index.js @@ -302,11 +302,7 @@ class ImageSelector extends BaseFloat { } } }, 'Choose an Image'), - h('span.description', { - props: { - style: 'user-select: none;' - } - }, 'Choose image from your computer.') + h('span.description', 'Choose image from your computer.') ] } else if (tab === 'link') { const altInput = h('input.alt', { @@ -376,11 +372,7 @@ class ImageSelector extends BaseFloat { } }, 'Embed Image') const bottomDes = h('span.description', [ - h('span', { - props: { - style: 'user-select: none;' - } - }, 'Paste web image or local image path. Use '), + h('span', 'Paste web image or local image path. Use '), h('a', { on: { click: event => { @@ -411,11 +403,7 @@ class ImageSelector extends BaseFloat { const loadingCom = h('div.ag-plugin-loading') bodyContent.push(loadingCom) } else if (this.photoList.length === 0) { - const noDataCom = h('div.no-data', { - props: { - style: 'user-select: none;' - } - }, 'No result...') + const noDataCom = h('div.no-data', 'No result...') bodyContent.push(noDataCom) } else { const photos = this.photoList.map(photo => { @@ -458,11 +446,7 @@ class ImageSelector extends BaseFloat { return h('div.photo', [imageWrapper, desCom]) }) const photoWrapper = h('div.photos-wrapper', photos) - const moreCom = h('div.more', { - props: { - style: 'user-select: none;' - } - }, 'Search for more photos...') + const moreCom = h('div.more', 'Search for more photos...') bodyContent.push(photoWrapper, moreCom) } } diff --git a/src/muya/themes/default.css b/src/muya/themes/default.css index 18945cdd..60a25652 100644 --- a/src/muya/themes/default.css +++ b/src/muya/themes/default.css @@ -640,7 +640,6 @@ kbd { border-left-color: transparent; border-right-color: transparent; } - } /* end not print */ @media print { From 07ef9cba0ff7896eab7ebf50de1ba25272179f83 Mon Sep 17 00:00:00 2001 From: Ran Luo Date: Sat, 2 Nov 2019 22:18:58 +0800 Subject: [PATCH 23/29] Fix 1551, and also fix no need heading rules in table cell (#1558) * Fix 1551, and also fix no need heading rules in table cell * no need to hide heading # in table cell --- .../lib/parser/render/renderBlock/renderContainerBlock.js | 4 ++-- src/muya/lib/parser/render/renderBlock/renderLeafBlock.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/muya/lib/parser/render/renderBlock/renderContainerBlock.js b/src/muya/lib/parser/render/renderBlock/renderContainerBlock.js index 5f16112d..9b560b83 100644 --- a/src/muya/lib/parser/render/renderBlock/renderContainerBlock.js +++ b/src/muya/lib/parser/render/renderBlock/renderContainerBlock.js @@ -124,9 +124,9 @@ export default function renderContainerBlock (parent, block, activeBlocks, match } else if (type === 'figure') { if (functionType) { Object.assign(data.dataset, { role: functionType.toUpperCase() }) - if (functionType === 'table') { + if (functionType === 'table' && activeBlocks[0] && activeBlocks[0].functionType === 'cellContent') { children.unshift(renderTableTools(activeBlocks)) - } else { + } else if (functionType !== 'footnote') { children.unshift(renderEditIcon()) } } diff --git a/src/muya/lib/parser/render/renderBlock/renderLeafBlock.js b/src/muya/lib/parser/render/renderBlock/renderLeafBlock.js index 54b493f8..7cc20592 100644 --- a/src/muya/lib/parser/render/renderBlock/renderLeafBlock.js +++ b/src/muya/lib/parser/render/renderBlock/renderLeafBlock.js @@ -101,7 +101,8 @@ export default function renderLeafBlock (parent, block, activeBlocks, matches, u functionType !== 'codeContent' && functionType !== 'languageInput' ) { - const hasBeginRules = type === 'span' + const hasBeginRules = /paragraphContent|atxLine/.test(functionType) + tokens = tokenizer(text, { highlights, hasBeginRules, From 3105228774cfd12a31cc295e5f0feb0c93183db4 Mon Sep 17 00:00:00 2001 From: Ran Luo Date: Sat, 2 Nov 2019 22:20:21 +0800 Subject: [PATCH 24/29] fix #1547 (#1560) --- src/muya/lib/contentState/imageCtrl.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/muya/lib/contentState/imageCtrl.js b/src/muya/lib/contentState/imageCtrl.js index 7279bec0..98e6163e 100644 --- a/src/muya/lib/contentState/imageCtrl.js +++ b/src/muya/lib/contentState/imageCtrl.js @@ -29,7 +29,7 @@ const imageCtrl = ContentState => { // Only encode URLs but not local paths or data URLs let imgUrl if (!/data:image/.test(src)) { - imgUrl = encodeURI(src) + imgUrl = encodeURI(src).replace(/#/g, encodeURIComponent('#')) } else { imgUrl = src } @@ -132,7 +132,7 @@ const imageCtrl = ContentState => { } imageText += '](' if (src) { - imageText += encodeURI(src) + imageText += encodeURI(src).replace(/#/g, encodeURIComponent('#')) } if (title) { imageText += ` "${title}"` From 070ef96b7a93393bf35c546d606a38839eac4fb5 Mon Sep 17 00:00:00 2001 From: Ran Luo Date: Sat, 2 Nov 2019 22:28:44 +0800 Subject: [PATCH 25/29] fix: #1466 (#1562) --- src/muya/lib/contentState/copyCutCtrl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/muya/lib/contentState/copyCutCtrl.js b/src/muya/lib/contentState/copyCutCtrl.js index 2f18e482..f0782a57 100644 --- a/src/muya/lib/contentState/copyCutCtrl.js +++ b/src/muya/lib/contentState/copyCutCtrl.js @@ -188,7 +188,7 @@ const copyCutCtrl = ContentState => { } let htmlData = wrapper.innerHTML - const textData = this.htmlToMarkdown(htmlData) + const textData = escapeHtml(this.htmlToMarkdown(htmlData)) htmlData = marked(textData) return { html: htmlData, text: textData } From 68e4a9ce7e48a7ecec949785e406d4f15a9d33c7 Mon Sep 17 00:00:00 2001 From: Ran Luo Date: Sat, 2 Nov 2019 22:50:38 +0800 Subject: [PATCH 26/29] fix #1583 (#1585) --- src/muya/lib/contentState/backspaceCtrl.js | 8 ++++++-- src/muya/lib/contentState/enterCtrl.js | 10 +++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/muya/lib/contentState/backspaceCtrl.js b/src/muya/lib/contentState/backspaceCtrl.js index 3840f135..e714213f 100644 --- a/src/muya/lib/contentState/backspaceCtrl.js +++ b/src/muya/lib/contentState/backspaceCtrl.js @@ -500,10 +500,14 @@ const backspaceCtrl = ContentState => { start: { key, offset }, end: { key, offset } } - if (this.isCollapse()) { + let needRenderAll = false + + if (this.isCollapse() && preBlock.type === 'span' && preBlock.functionType === 'paragraphContent') { this.checkInlineUpdate(preBlock) + needRenderAll = true } - this.partialRender() + + needRenderAll ? this.render() : this.partialRender() } } } diff --git a/src/muya/lib/contentState/enterCtrl.js b/src/muya/lib/contentState/enterCtrl.js index db084aa1..3fe3d62c 100644 --- a/src/muya/lib/contentState/enterCtrl.js +++ b/src/muya/lib/contentState/enterCtrl.js @@ -418,6 +418,7 @@ const enterCtrl = ContentState => { } this.insertAfter(newBlock, block) + break } case left === 0 && right === 0: { @@ -511,7 +512,14 @@ const enterCtrl = ContentState => { end: { key, offset } } - this.partialRender() + let needRenderAll = false + + if (this.isCollapse() && cursorBlock.type === 'p') { + this.checkInlineUpdate(cursorBlock.children[0]) + needRenderAll = true + } + + needRenderAll ? this.render() : this.partialRender() } } From fd18637d64c5dd0ae135b136e046a3c882f154d4 Mon Sep 17 00:00:00 2001 From: Ran Luo Date: Sat, 2 Nov 2019 22:55:43 +0800 Subject: [PATCH 27/29] fix #1579 (#1587) --- src/muya/lib/contentState/formatCtrl.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/muya/lib/contentState/formatCtrl.js b/src/muya/lib/contentState/formatCtrl.js index 5027138d..1ab8e536 100644 --- a/src/muya/lib/contentState/formatCtrl.js +++ b/src/muya/lib/contentState/formatCtrl.js @@ -86,7 +86,12 @@ const clearFormat = (token, { start, end }) => { } const addFormat = (type, block, { start, end }) => { - if (block.type === 'pre') return false + if ( + block.type !== 'span' || + (block.type === 'span' && !/paragraphContent|cellConntent|atxLine/.test(block.functionType)) + ) { + return false + } switch (type) { case 'em': case 'del': From 09f920eade0f26daee6a62f6a8060ad23b96da0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4usler?= Date: Sat, 2 Nov 2019 16:41:51 +0100 Subject: [PATCH 28/29] Upgrade to Electron 7 (#1543) * Upgrade to Electron 7 * Fix E2E test issue with Electron 7 * Fix native theme event * Remove runtime native theme detection * Update Electron to v7.0.1 * Fix keytar exception --- electron-builder.yml | 2 + package.json | 11 +- src/main/app/index.js | 57 ++++++---- src/main/config.js | 2 +- src/main/dataCenter/index.js | 4 +- src/main/filesystem/watcher.js | 2 +- src/main/index.js | 4 +- src/main/menu/actions/file.js | 41 ++----- src/main/menu/actions/window.js | 4 +- src/main/menu/index.js | 2 +- src/main/menu/templates/window.js | 6 - src/main/platform/win32/registry.js | 34 ------ src/main/preferences/index.js | 20 +--- src/main/preferences/schema.json | 9 ++ src/main/utils/imagePathAutoComplement.js | 5 +- src/main/windows/setting.js | 10 +- src/renderer/pages/app.vue | 1 + src/renderer/prefComponents/theme/config.js | 11 ++ src/renderer/prefComponents/theme/index.vue | 24 ++-- src/renderer/store/preferences.js | 1 + static/preference.json | 1 + yarn.lock | 115 ++++++++++++-------- 22 files changed, 180 insertions(+), 186 deletions(-) delete mode 100755 src/main/platform/win32/registry.js diff --git a/electron-builder.yml b/electron-builder.yml index 12b264c6..8d1a53bb 100755 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -19,6 +19,7 @@ files: - "!node_modules/vega-lite/build/vega-lite*.js.map" # Don't bundle build files - "!node_modules/@felixrieseberg/spellchecker/bin" +- "!node_modules/@hfelix/spellchecker/bin" - "!node_modules/ced/bin" - "!node_modules/ced/vendor" - "!node_modules/cld/bin" @@ -34,6 +35,7 @@ files: - "!node_modules/ced/build/vendor" # Don't bundle LGPL source files - "!node_modules/@felixrieseberg/spellchecker/vendor" +- "!node_modules/@hfelix/spellchecker/vendor" extraFiles: - "LICENSE" - from: "resources/THIRD-PARTY-LICENSES.txt" diff --git a/package.json b/package.json index ceabf11d..aad09e28 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "dependencies": { "@hfelix/electron-localshortcut": "^3.1.1", - "@hfelix/electron-spellchecker": "^1.0.0-rc.1", + "@hfelix/electron-spellchecker": "^1.0.0-rc.3", "@octokit/rest": "^16.33.1", "arg": "^4.1.1", "axios": "^0.19.0", @@ -65,7 +65,7 @@ "joplin-turndown-plugin-gfm": "^1.0.11", "katex": "^0.11.1", "keyboard-layout": "^2.0.16", - "keytar": "^5.0.0-beta.3", + "keytar": "5.0.0-beta.4", "mermaid": "^8.4.0", "plist": "^3.0.1", "popper.js": "^1.16.0", @@ -113,7 +113,7 @@ "del": "^5.1.0", "devtron": "^1.4.0", "dotenv": "^8.2.0", - "electron": "^6.1.0", + "electron": "^7.0.1", "electron-builder": "^21.2.0", "electron-devtools-installer": "^2.2.4", "electron-rebuild": "^1.8.6", @@ -153,7 +153,7 @@ "postcss-preset-env": "^6.6.0", "raw-loader": "^3.1.0", "require-dir": "^1.2.0", - "spectron": "^8.0.0", + "spectron": "^9.0.0", "style-loader": "^1.0.0", "svg-sprite-loader": "^4.1.6", "svgo": "^1.3.0", @@ -171,9 +171,6 @@ "webpack-hot-middleware": "^2.25.0", "webpack-merge": "^4.2.1" }, - "optionalDependencies": { - "vscode-windows-registry": "^1.0.2" - }, "repository": { "type": "git", "url": "git@github.com:marktext/marktext.git" diff --git a/src/main/app/index.js b/src/main/app/index.js index 2954984d..981faa95 100644 --- a/src/main/app/index.js +++ b/src/main/app/index.js @@ -3,7 +3,7 @@ import fse from 'fs-extra' import { exec } from 'child_process' import dayjs from 'dayjs' import log from 'electron-log' -import { app, BrowserWindow, clipboard, dialog, ipcMain, systemPreferences } from 'electron' +import { app, BrowserWindow, clipboard, dialog, ipcMain, nativeTheme } from 'electron' import { isChildOfDirectory } from 'common/filesystem/paths' import { isLinux, isOsx, isWindows } from '../config' import parseArgs from '../cli/parser' @@ -115,7 +115,7 @@ class App { const { paths } = this._accessor ensureDefaultDict(paths.userDataPath) .catch(error => { - log.error(error) + log.error('Error copying Hunspell dictionary: ', error) }) } @@ -143,7 +143,13 @@ class App { } } - const { startUpAction, defaultDirectoryToOpen } = preferences.getAll() + const { + startUpAction, + defaultDirectoryToOpen, + autoSwitchTheme, + theme + } = preferences.getAll() + if (startUpAction === 'folder' && defaultDirectoryToOpen) { const info = normalizeMarkdownPath(defaultDirectoryToOpen) if (info) { @@ -151,29 +157,32 @@ class App { } } + // Set initial native theme for theme in preferences. + const isDarkTheme = /dark/i.test(theme) + if (autoSwitchTheme === 0 && isDarkTheme !== nativeTheme.shouldUseDarkColors) { + selectTheme(nativeTheme.shouldUseDarkColors ? 'dark' : 'light') + nativeTheme.themeSource = nativeTheme.shouldUseDarkColors ? 'dark' : 'light' + } else { + nativeTheme.themeSource = isDarkTheme ? 'dark' : 'light' + } + + let isDarkMode = nativeTheme.shouldUseDarkColors + ipcMain.on('broadcast-preferences-changed', change => { + // Set Chromium's color for native elements after theme change. + if (change.theme) { + const isDarkTheme = /dark/i.test(change.theme) + if (isDarkMode !== isDarkTheme) { + isDarkMode = isDarkTheme + nativeTheme.themeSource = isDarkTheme ? 'dark' : 'light' + } else if (nativeTheme.themeSource === 'system') { + // Need to set dark or light theme because we set `system` to get the current system theme. + nativeTheme.themeSource = isDarkMode ? 'dark' : 'light' + } + } + }) + if (isOsx) { app.dock.setMenu(dockMenu) - - // Listen for system theme change and change Mark Text own `dark` and `light`. - // In macOS 10.14 Mojave, Apple introduced a new system-wide dark mode for - // all macOS computers. - systemPreferences.subscribeNotification( - 'AppleInterfaceThemeChangedNotification', - () => { - const preferences = this._accessor.preferences - const { theme } = preferences.getAll() - - // Application menu is automatically updated via preference manager. - if (systemPreferences.isDarkMode() && theme !== 'dark' && - theme !== 'material-dark' && theme !== 'one-dark') { - selectTheme('dark') - } - if (!systemPreferences.isDarkMode() && theme !== 'light' && - theme !== 'ulysses' && theme !== 'graphite') { - selectTheme('light') - } - } - ) } else if (isWindows) { app.setJumpList([{ type: 'recent' diff --git a/src/main/config.js b/src/main/config.js index f1f28771..90523f28 100644 --- a/src/main/config.js +++ b/src/main/config.js @@ -16,7 +16,7 @@ export const editorWinOptions = { zoomFactor: 1.0 } -export const defaultPreferenceWinOptions = { +export const preferencesWinOptions = { width: 950, height: 650, webPreferences: { diff --git a/src/main/dataCenter/index.js b/src/main/dataCenter/index.js index 9742fe38..4b59b7d9 100644 --- a/src/main/dataCenter/index.js +++ b/src/main/dataCenter/index.js @@ -71,7 +71,7 @@ class DataCenter extends EventEmitter { return Object.assign(data, encryptObj) } catch (err) { - log.error(err) + log.error('Failed to decrypt secure keys:', err) return data } } @@ -133,7 +133,7 @@ class DataCenter extends EventEmitter { try { return await keytar.setPassword(serviceName, key, value) } catch (err) { - log.error(err) + log.error('dataCenter::setItem:', err) } } else { return this.store.set(key, value) diff --git a/src/main/filesystem/watcher.js b/src/main/filesystem/watcher.js index fa5a3696..5d242a50 100644 --- a/src/main/filesystem/watcher.js +++ b/src/main/filesystem/watcher.js @@ -235,7 +235,7 @@ class Watcher { }) } } else { - log.error(error) + log.error('Error while watching files:', error) } }) diff --git a/src/main/index.js b/src/main/index.js index 19a588dd..60d39444 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -60,9 +60,7 @@ try { // Catch errors that may come from invalid configuration files like settings. const msgHint = err.message.includes('Config schema violation') ? 'This seems to be an issue with your configuration file(s). ' : '' - - log.error(`Loading Mark Text failed during initialization! ${msgHint}`) - log.error(err) + log.error(`Loading Mark Text failed during initialization! ${msgHint}`, err) const EXIT_ON_ERROR = !!process.env.MARKTEXT_EXIT_ON_ERROR const SHOW_ERROR_DIALOG = !process.env.MARKTEXT_ERROR_INTERACTION diff --git a/src/main/menu/actions/file.js b/src/main/menu/actions/file.js index 4eba8183..1dccfa3d 100644 --- a/src/main/menu/actions/file.js +++ b/src/main/menu/actions/file.js @@ -62,7 +62,7 @@ const handleResponseForExport = async (e, { type, content, pathname, title, page } win.webContents.send('AGANI::export-success', { type, filePath }) } catch (err) { - log.error(err) + log.error('Error while exporting:', err) const ERROR_MSG = err.message || `Error happened when export ${filePath}` win.webContents.send('AGANI::show-notification', { title: 'Export failure', @@ -80,19 +80,9 @@ const handleResponseForExport = async (e, { type, content, pathname, title, page const handleResponseForPrint = e => { const win = BrowserWindow.fromWebContents(e.sender) - - // See GH#749, Electron#16085 and Electron#17523. - dialog.showMessageBox(win, { - type: 'info', - buttons: ['OK'], - defaultId: 0, - noLink: true, - message: 'Printing doesn\'t work', - detail: 'Printing is disabled due to an Electron upstream issue. Please export the document as PDF and print the PDF file. We apologize for the inconvenience!' + win.webContents.print({ printBackground: true }, () => { + removePrintServiceFromWindow(win) }) - // win.webContents.print({ printBackground: true }, () => { - // removePrintServiceFromWindow(win) - // }) } const handleResponseForSave = async (e, { id, filename, markdown, pathname, options, defaultPath }) => { @@ -140,7 +130,7 @@ const handleResponseForSave = async (e, { id, filename, markdown, pathname, opti return id }) .catch(err => { - log.error(err) + log.error('Error while saving:', err) win.webContents.send('mt::tab-save-failure', id, err.message) }) } @@ -185,7 +175,7 @@ const openPandocFile = async (windowId, pathname) => { const data = await converter() ipcMain.emit('app-open-markdown-by-id', windowId, data) } catch (err) { - log.error(err) + log.error('Error while converting file:', err) } } @@ -216,7 +206,7 @@ ipcMain.on('mt::save-and-close-tabs', async (e, unsavedFiles) => { win.send('mt::force-close-tabs-by-id', tabIds) }) .catch(err => { - log.error(err.error) + log.error('Error while save all:', err.error) }) } else { const tabIds = unsavedFiles.map(f => f.id) @@ -262,7 +252,7 @@ ipcMain.on('AGANI::response-file-save-as', async (e, { id, filename, markdown, p } }) .catch(err => { - log.error(err) + log.error('Error while save as:', err) win.webContents.send('mt::tab-save-failure', id, err.message) }) } @@ -282,8 +272,7 @@ ipcMain.on('mt::close-window-confirm', async (e, unsavedFiles) => { ipcMain.emit('window-close-by-id', win.id) }) .catch(err => { - console.log(err) - log.error(err) + log.error('Error while saving before quit:', err) // Notify user about the problem. dialog.showMessageBox(win, { @@ -446,19 +435,9 @@ export const importFile = async win => { } export const print = win => { - if (!win) { - return + if (win) { + win.webContents.send('mt::show-export-dialog', 'print') } - // See GH#749, Electron#16085 and Electron#17523. - dialog.showMessageBox(win, { - type: 'info', - buttons: ['OK'], - defaultId: 0, - noLink: true, - message: 'Printing doesn\'t work', - detail: 'Printing is disabled due to an Electron upstream issue. Please export the document as PDF and print the PDF file. We apologize for the inconvenience!' - }) - // win.webContents.send('mt::show-export-dialog', 'print') } export const openFile = async win => { diff --git a/src/main/menu/actions/window.js b/src/main/menu/actions/window.js index 4874f605..01a0a077 100644 --- a/src/main/menu/actions/window.js +++ b/src/main/menu/actions/window.js @@ -9,13 +9,13 @@ export const toggleAlwaysOnTop = win => { export const zoomIn = win => { const { webContents } = win const zoom = webContents.getZoomFactor() - // WORKAROUND: Electron#16018 + // WORKAROUND: We need to set zoom on the browser window due to Electron#16018. webContents.send('mt::window-zoom', Math.min(2.0, zoom + 0.125)) } export const zoomOut = win => { const { webContents } = win const zoom = webContents.getZoomFactor() - // WORKAROUND: Electron#16018 + // WORKAROUND: We need to set zoom on the browser window due to Electron#16018. webContents.send('mt::window-zoom', Math.max(1.0, zoom - 0.125)) } diff --git a/src/main/menu/index.js b/src/main/menu/index.js index 0ac669fa..6f811c53 100644 --- a/src/main/menu/index.js +++ b/src/main/menu/index.js @@ -92,7 +92,7 @@ class AppMenu { } return recentDocuments } catch (err) { - log.error(err) + log.error('Error while read recently used documents:', err) return [] } } diff --git a/src/main/menu/templates/window.js b/src/main/menu/templates/window.js index cc0b5e38..4e69c566 100755 --- a/src/main/menu/templates/window.js +++ b/src/main/menu/templates/window.js @@ -17,19 +17,13 @@ export default function (keybindings) { toggleAlwaysOnTop(browserWindow) } }, { - // TODO: Disable due GH#1225. - visible: false, type: 'separator' }, { - // TODO: Disable due GH#1225. - visible: false, label: 'Zoom In', click (menuItem, browserWindow) { zoomIn(browserWindow) } }, { - // TODO: Disable due GH#1225. - visible: false, label: 'Zoom Out', click (menuItem, browserWindow) { zoomOut(browserWindow) diff --git a/src/main/platform/win32/registry.js b/src/main/platform/win32/registry.js deleted file mode 100755 index d027d751..00000000 --- a/src/main/platform/win32/registry.js +++ /dev/null @@ -1,34 +0,0 @@ -import { isWindows } from '../../config' - -let GetStringRegKey = null -if (isWindows) { - try { - GetStringRegKey = require('vscode-windows-registry').GetStringRegKey - } catch (e) { - // Ignore webpack build error on macOS and Linux. - } -} - -export const winHKEY = { - HKCU: 'HKEY_CURRENT_USER', - HKLM: 'HKEY_LOCAL_MACHINE', - HKCR: 'HKEY_CLASSES_ROOT', - HKU: 'HKEY_USERS', - HKCC: 'HKEY_CURRENT_CONFIG' -} - -/** - * Returns the registry key value. - * - * @param {winHKEY} hive The registry key - * @param {string} path The registry subkey - * @param {string} name The registry name - * @returns {string|null|undefined} The registry key value or null/undefined. - */ -export const getStringRegKey = (hive, path, name) => { - try { - return GetStringRegKey(hive, path, name) - } catch (e) { - return null - } -} diff --git a/src/main/preferences/index.js b/src/main/preferences/index.js index 48e4fa81..69f69687 100644 --- a/src/main/preferences/index.js +++ b/src/main/preferences/index.js @@ -3,24 +3,12 @@ import fs from 'fs' import path from 'path' import EventEmitter from 'events' import Store from 'electron-store' -import { BrowserWindow, ipcMain, systemPreferences } from 'electron' +import { BrowserWindow, ipcMain, nativeTheme } from 'electron' import log from 'electron-log' -import { isOsx, isWindows } from '../config' +import { isWindows } from '../config' import { hasSameKeys } from '../utils' -import { getStringRegKey, winHKEY } from '../platform/win32/registry.js' import schema from './schema' -const isDarkSystemMode = () => { - if (isOsx) { - return systemPreferences.isDarkMode() - } else if (isWindows) { - // NOTE: This key is a 32-Bit DWORD but converted to JS string! - const buf = getStringRegKey(winHKEY.HKCU, 'Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize', 'AppsUseLightTheme') - return buf === '' // zero (0) - } - return false -} - const PREFERENCES_FILE_NAME = 'preferences' class Preference extends EventEmitter { @@ -50,7 +38,9 @@ class Preference extends EventEmitter { let defaultSettings = null try { defaultSettings = fse.readJsonSync(this.staticPath) - if (isDarkSystemMode()) { + + // Set best theme on first application start. + if (nativeTheme.shouldUseDarkColors) { defaultSettings.theme = 'dark' } } catch (err) { diff --git a/src/main/preferences/schema.json b/src/main/preferences/schema.json index 110916fa..f14c9976 100644 --- a/src/main/preferences/schema.json +++ b/src/main/preferences/schema.json @@ -247,6 +247,15 @@ "description": "Theme--Select the theme used in Mark Text", "type": "string" }, + "autoSwitchTheme": { + "description": "Theme--Automatically adjust application theme according system.", + "default": 2, + "enum": [ + 0, + 1, + 2 + ] + }, "spellcheckerEnabled": { "description": "Spelling--Whether spell checking is enabled.", diff --git a/src/main/utils/imagePathAutoComplement.js b/src/main/utils/imagePathAutoComplement.js index e9a3d69b..235c86c6 100644 --- a/src/main/utils/imagePathAutoComplement.js +++ b/src/main/utils/imagePathAutoComplement.js @@ -46,8 +46,9 @@ const filesHandler = (files, directory, key) => { const rebuild = (directory) => { fs.readdir(directory, (err, files) => { - if (err) log.error(err) - else { + if (err) { + log.error('imagePathAutoComplement::rebuild:', err) + } else { filesHandler(files, directory) } }) diff --git a/src/main/windows/setting.js b/src/main/windows/setting.js index f0a27751..b9eb5554 100644 --- a/src/main/windows/setting.js +++ b/src/main/windows/setting.js @@ -3,7 +3,7 @@ import { BrowserWindow, ipcMain } from 'electron' import electronLocalshortcut from '@hfelix/electron-localshortcut' import BaseWindow, { WindowLifecycle, WindowType } from './base' import { centerWindowOptions } from './utils' -import { TITLE_BAR_HEIGHT, defaultPreferenceWinOptions, isLinux, isOsx } from '../config' +import { TITLE_BAR_HEIGHT, preferencesWinOptions, isLinux, isOsx, isWindows } from '../config' class SettingWindow extends BaseWindow { /** @@ -21,12 +21,18 @@ class SettingWindow extends BaseWindow { */ createWindow (options = {}) { const { menu: appMenu, env, keybindings, preferences } = this._accessor - const winOptions = Object.assign({}, defaultPreferenceWinOptions, options) + const winOptions = Object.assign({}, preferencesWinOptions, options) centerWindowOptions(winOptions) if (isLinux) { winOptions.icon = path.join(__static, 'logo-96px.png') } + // WORKAROUND: Electron has issues with different DPI per monitor when + // setting a fixed window size. + if (isWindows) { + winOptions.resizable = true + } + // Enable native or custom/frameless window and titlebar const { titleBarStyle, theme } = preferences.getAll() if (!isOsx) { diff --git a/src/renderer/pages/app.vue b/src/renderer/pages/app.vue index 50dad342..2b42f9ed 100644 --- a/src/renderer/pages/app.vue +++ b/src/renderer/pages/app.vue @@ -146,6 +146,7 @@ export default { dispatch('LINTEN_FOR_PRINT_SERVICE_CLEARUP') dispatch('LINTEN_FOR_EXPORT_SUCCESS') dispatch('LISTEN_FOR_FILE_CHANGE') + dispatch('LISTEN_WINDOW_ZOOM') // module: notification dispatch('LISTEN_FOR_NOTIFICATION') diff --git a/src/renderer/prefComponents/theme/config.js b/src/renderer/prefComponents/theme/config.js index f124330a..20bb426b 100644 --- a/src/renderer/prefComponents/theme/config.js +++ b/src/renderer/prefComponents/theme/config.js @@ -18,3 +18,14 @@ export const themes = [ name: 'one-dark' } ] + +export const autoSwitchThemeOptions = [{ + label: 'Adjust theme at startup', // Always + value: 0 +}, /* { + label: 'Only at runtime', + value: 1 +}, */ { + label: 'Never', + value: 2 +}] diff --git a/src/renderer/prefComponents/theme/index.vue b/src/renderer/prefComponents/theme/index.vue index 8bb3372a..a7685d33 100644 --- a/src/renderer/prefComponents/theme/index.vue +++ b/src/renderer/prefComponents/theme/index.vue @@ -4,12 +4,19 @@
    + +
    Open the themes folder @@ -27,21 +34,25 @@