From dac23e38d95bb8a1850812524c179c9e9eb12ec9 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 10 Aug 2018 13:27:17 +0300 Subject: [PATCH 01/59] Add support for converting to MD when paste HTML --- browser/components/CodeEditor.js | 17 ++++++ package.json | 4 +- yarn.lock | 93 ++++++++++++++++++++++++++++++-- 3 files changed, 110 insertions(+), 4 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 1b546ed1..eb4299e4 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -14,6 +14,8 @@ import consts from 'browser/lib/consts' import fs from 'fs' const { ipcRenderer } = require('electron') import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' +import TurndownService from 'turndown' +import { gfm } from 'turndown-plugin-gfm' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' @@ -52,6 +54,9 @@ export default class CodeEditor extends React.Component { this.searchState = null this.formatTable = () => this.handleFormatTable() + + this.turndownService = new TurndownService() + this.turndownService.use(gfm) } handleSearch (msg) { @@ -406,6 +411,12 @@ export default class CodeEditor extends React.Component { ) return prevChar === '](' && nextChar === ')' } + + const pastedHtml = clipboardData.getData('text/html') + if (pastedHtml !== '') { + this.handlePasteHtml(e, editor, pastedHtml) + } + if (dataTransferItem.type.match('image')) { attachmentManagement.handlePastImageEvent(this, storageKey, noteKey, dataTransferItem) } else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) { @@ -459,6 +470,12 @@ export default class CodeEditor extends React.Component { }) } + handlePasteHtml (e, editor, pastedHtml) { + e.preventDefault() + const markdown = this.turndownService.turndown(pastedHtml) + editor.replaceSelection(markdown) + } + mapNormalResponse (response, pastedTxt) { return this.decodeResponse(response).then((body) => { return new Promise((resolve, reject) => { diff --git a/package.json b/package.json index fbbb025f..d7d49b80 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,9 @@ "sanitize-html": "^1.18.2", "striptags": "^2.2.1", "unique-slug": "2.0.0", - "uuid": "^3.2.1" + "uuid": "^3.2.1", + "turndown":"^4.0.2", + "turndown-plugin-gfm":"^1.0.2" }, "devDependencies": { "ava": "^0.25.0", diff --git a/yarn.lock b/yarn.lock index f77252ca..0f4f6b48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -93,6 +93,10 @@ abab@^1.0.3, abab@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" +abab@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f" + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -138,6 +142,10 @@ acorn@^5.0.0, acorn@^5.3.0, acorn@^5.5.0: version "5.5.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9" +acorn@^5.5.3: + version "5.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8" + ajv-keywords@^1.0.0: version "1.5.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" @@ -2162,6 +2170,12 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": dependencies: cssom "0.3.x" +cssstyle@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.0.0.tgz#79b16d51ec5591faec60e688891f15d2a5705129" + dependencies: + cssom "0.3.x" + ctype@0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/ctype/-/ctype-0.5.3.tgz#82c18c2461f74114ef16c135224ad0b9144ca12f" @@ -2673,7 +2687,7 @@ domelementtype@~1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" -domexception@^1.0.0: +domexception@^1.0.0, domexception@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" dependencies: @@ -3010,6 +3024,17 @@ escodegen@^1.6.1, escodegen@^1.9.0: optionalDependencies: source-map "~0.6.1" +escodegen@^1.9.1: + version "1.11.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589" + dependencies: + esprima "^3.1.3" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + escope@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" @@ -5257,6 +5282,37 @@ jsdom@^11.5.1: ws "^4.0.0" xml-name-validator "^3.0.0" +jsdom@^11.9.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8" + dependencies: + abab "^2.0.0" + acorn "^5.5.3" + acorn-globals "^4.1.0" + array-equal "^1.0.0" + cssom ">= 0.3.2 < 0.4.0" + cssstyle "^1.0.0" + data-urls "^1.0.0" + domexception "^1.0.1" + escodegen "^1.9.1" + html-encoding-sniffer "^1.0.2" + left-pad "^1.3.0" + nwsapi "^2.0.7" + parse5 "4.0.0" + pn "^1.1.0" + request "^2.87.0" + request-promise-native "^1.0.5" + sax "^1.2.4" + symbol-tree "^3.2.2" + tough-cookie "^2.3.4" + w3c-hr-time "^1.0.1" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.3" + whatwg-mimetype "^2.1.0" + whatwg-url "^6.4.1" + ws "^5.2.0" + xml-name-validator "^3.0.0" + jsdom@^9.4.2: version "9.12.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4" @@ -5416,7 +5472,7 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" -left-pad@^1.2.0: +left-pad@^1.2.0, left-pad@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" @@ -6288,6 +6344,10 @@ nwsapi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.0.tgz#7c8faf4ad501e1d17a651ebc5547f966b547c5c7" +nwsapi@^2.0.7: + version "2.0.8" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.8.tgz#e3603579b7e162b3dbedae4fb24e46f771d8fa24" + oauth-sign@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.6.0.tgz#7dbeae44f6ca454e1f168451d630746735813ce3" @@ -7028,6 +7088,10 @@ pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" +psl@^1.1.24: + version "1.1.29" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" + punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" @@ -7510,7 +7574,7 @@ request@2.55.0: tough-cookie ">=0.12.0" tunnel-agent "~0.4.0" -request@^2.45.0, request@^2.79.0, request@^2.83.0: +request@^2.45.0, request@^2.79.0, request@^2.83.0, request@^2.87.0: version "2.87.0" resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" dependencies: @@ -8515,6 +8579,13 @@ tough-cookie@>=0.12.0, tough-cookie@>=2.3.3, tough-cookie@^2.3.2, tough-cookie@^ dependencies: punycode "^1.4.1" +tough-cookie@^2.3.4: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -8565,6 +8636,16 @@ tunnel-agent@~0.4.0: version "0.4.3" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" +turndown-plugin-gfm@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/turndown-plugin-gfm/-/turndown-plugin-gfm-1.0.2.tgz#6f8678a361f35220b2bdf5619e6049add75bf1c7" + +turndown@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/turndown/-/turndown-4.0.2.tgz#c3ddb8ba32a3665723599be2f4e7860adb6042ae" + dependencies: + jsdom "^11.9.0" + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -9098,6 +9179,12 @@ ws@^4.0.0: async-limiter "~1.0.0" safe-buffer "~5.1.0" +ws@^5.2.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + dependencies: + async-limiter "~1.0.0" + xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" From 5bf3824f28d41d89180df0520bb4175c5a52eee8 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 10 Aug 2018 15:15:19 +0300 Subject: [PATCH 02/59] Fix --- browser/components/CodeEditor.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index eb4299e4..43b16bc9 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -415,9 +415,7 @@ export default class CodeEditor extends React.Component { const pastedHtml = clipboardData.getData('text/html') if (pastedHtml !== '') { this.handlePasteHtml(e, editor, pastedHtml) - } - - if (dataTransferItem.type.match('image')) { + } else if (dataTransferItem.type.match('image')) { attachmentManagement.handlePastImageEvent(this, storageKey, noteKey, dataTransferItem) } else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) { this.handlePasteUrl(e, editor, pastedTxt) From 73fbf49ba4cb3603b7f381ec9e3626b5efa0537d Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Tue, 21 Aug 2018 00:19:26 +0200 Subject: [PATCH 03/59] - show tags of note in alphabetical order - enable live count of notes --- browser/components/NoteItem.js | 16 ++++-- browser/components/TagListItem.js | 2 +- browser/main/Detail/MarkdownNoteDetail.js | 3 +- browser/main/Detail/SnippetNoteDetail.js | 1 + browser/main/Detail/TagSelect.js | 4 +- browser/main/NoteList/index.js | 1 + browser/main/SideNav/index.js | 18 +++++- browser/main/modals/PreferencesModal/UiTab.js | 55 +++++++++++++++---- locales/en.json | 4 +- locales/fr.json | 4 +- 10 files changed, 81 insertions(+), 27 deletions(-) diff --git a/browser/components/NoteItem.js b/browser/components/NoteItem.js index 5073dc73..b0731eca 100644 --- a/browser/components/NoteItem.js +++ b/browser/components/NoteItem.js @@ -24,16 +24,19 @@ const TagElement = ({ tagName }) => ( /** * @description Tag element list component. * @param {Array|null} tags + * @param {boolean} showTagsAlphabetically * @return {React.Component} */ -const TagElementList = tags => { +const TagElementList = (tags, showTagsAlphabetically) => { if (!isArray(tags)) { return [] } - const tagElements = tags.map(tag => TagElement({ tagName: tag })) - - return tagElements + if (showTagsAlphabetically) { + return _.sortBy(tags).map(tag => TagElement({ tagName: tag })) + } else { + return tags.map(tag => TagElement({ tagName: tag })) + } } /** @@ -55,7 +58,8 @@ const NoteItem = ({ pathname, storageName, folderName, - viewType + viewType, + showTagsAlphabetically }) => (
{note.tags.length > 0 - ? TagElementList(note.tags) + ? TagElementList(note.tags, showTagsAlphabetically) : handleClickTagListItem(name)}> {`# ${name}`} - {count} + {count !== 0 ? count : ''}
diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 82073162..705736e9 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -312,7 +312,7 @@ class MarkdownNoteDetail extends React.Component { } render () { - const { data, location } = this.props + const { data, location, config } = this.props const { note, editorType } = this.state const storageKey = note.storage const folderKey = note.folder @@ -363,6 +363,7 @@ class MarkdownNoteDetail extends React.Component { diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 652d1f53..3633ec85 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -744,6 +744,7 @@ class SnippetNoteDetail extends React.Component { this.handleChange(e)} />
diff --git a/browser/main/Detail/TagSelect.js b/browser/main/Detail/TagSelect.js index e251dd42..e51d5673 100644 --- a/browser/main/Detail/TagSelect.js +++ b/browser/main/Detail/TagSelect.js @@ -119,10 +119,10 @@ class TagSelect extends React.Component { } render () { - const { value, className } = this.props + const { value, className, showTagsAlphabetically } = this.props const tagList = _.isArray(value) - ? value.map((tag) => { + ? (showTagsAlphabetically ? _.sortBy(value) : value).map((tag) => { return ( ) } diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index c4fa417b..d1292f68 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -19,6 +19,10 @@ import {SortableContainer} from 'react-sortable-hoc' import i18n from 'browser/lib/i18n' import context from 'browser/lib/context' +function findOne(haystack, arr) { + return arr.some(v => haystack.indexOf(v) >= 0) +} + class SideNav extends React.Component { // TODO: should not use electron stuff v0.7 @@ -144,12 +148,20 @@ class SideNav extends React.Component { tagListComponent () { const { data, location, config } = this.props - const relatedTags = this.getRelatedTags(this.getActiveTags(location.pathname), data.noteMap) + const activeTags = this.getActiveTags(location.pathname) + const relatedTags = this.getRelatedTags(activeTags, data.noteMap) let tagList = _.sortBy(data.tagNoteMap.map( (tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) }) - ), ['name']).filter( + ).filter( tag => tag.size > 0 - ) + ), ['name']) + if (config.ui.enableLiveNoteCounts && activeTags.length !== 0) { + const notesTags = data.noteMap.map(note => note.tags) + tagList = tagList.map(tag => { + tag.size = notesTags.filter(tags => tags.includes(tag.name) && findOne(tags, activeTags)).length + return tag + }) + } if (config.sortTagsBy === 'COUNTER') { tagList = _.sortBy(tagList, item => (0 - item.size)) } diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index aa3568e7..40269190 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -70,6 +70,8 @@ class UiTab extends React.Component { showCopyNotification: this.refs.showCopyNotification.checked, confirmDeletion: this.refs.confirmDeletion.checked, showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked, + showTagsAlphabetically: this.refs.showTagsAlphabetically.checked, + enableLiveNoteCounts: this.refs.enableLiveNoteCounts.checked, disableDirectWrite: this.refs.uiD2w != null ? this.refs.uiD2w.checked : false @@ -172,7 +174,9 @@ class UiTab extends React.Component {
{i18n.__('Interface')}
- {i18n.__('Interface Theme')} +
+ {i18n.__('Interface Theme')} +
this.handleUIChange(e)} @@ -221,16 +227,6 @@ class UiTab extends React.Component { {i18n.__('Show a confirmation dialog when deleting notes')}
-
- -
{ global.process.platform === 'win32' ?
@@ -246,6 +242,41 @@ class UiTab extends React.Component {
: null } +
Tags
+ +
+ +
+ +
+ +
+ +
+ +
+
Editor
diff --git a/locales/en.json b/locales/en.json index a9767492..d01a6175 100644 --- a/locales/en.json +++ b/locales/en.json @@ -175,5 +175,7 @@ "Allow styles": "Allow styles", "Allow dangerous html tags": "Allow dangerous html tags", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.", - "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠" + "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠", + "Show tags of a note in alphabetical order": "Show tags of a note in alphabetical order", + "Enable live count of notes": "Enable live count of notes" } diff --git a/locales/fr.json b/locales/fr.json index 8b880aa6..33a7c503 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -152,5 +152,7 @@ "Allow styles": "Accepter les styles", "Allow dangerous html tags": "Accepter les tags html dangereux", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convertir des flèches textuelles en jolis signes. ⚠ Cela va interferérer avec les éventuels commentaires HTML dans votre Markdown.", - "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Vous avez collé un lien qui référence une pièce-jointe qui n'a pas pu être récupéré dans le dossier de stockage de la note. Coller des liens qui font référence à des pièces-jointes ne fonctionne que si la source et la destination et la même. Veuillez plutôt utiliser du Drag & Drop ! ⚠" + "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Vous avez collé un lien qui référence une pièce-jointe qui n'a pas pu être récupéré dans le dossier de stockage de la note. Coller des liens qui font référence à des pièces-jointes ne fonctionne que si la source et la destination et la même. Veuillez plutôt utiliser du Drag & Drop ! ⚠", + "Show tags of a note in alphabetical order": "Afficher les tags d'une note par ordre alphabétique", + "Enable live count of notes": "Activer le comptage live des notes" } From 7cde30d3522a4c23d6995ccb68393a12ab6c85e1 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Tue, 21 Aug 2018 00:24:03 +0200 Subject: [PATCH 04/59] fix lint errors --- browser/main/SideNav/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index d1292f68..0dd34989 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -19,8 +19,8 @@ import {SortableContainer} from 'react-sortable-hoc' import i18n from 'browser/lib/i18n' import context from 'browser/lib/context' -function findOne(haystack, arr) { - return arr.some(v => haystack.indexOf(v) >= 0) +function findOne (haystack, arr) { + return arr.some(v => haystack.indexOf(v) >= 0) } class SideNav extends React.Component { From 3bdc88cecb6ed117312e2860e3c84afc5b803d69 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sat, 25 Aug 2018 23:14:05 +0200 Subject: [PATCH 05/59] fixing sanitization of inline html like () #1992 --- browser/lib/markdown-it-sanitize-html.js | 86 +++++++++++++++++++++++- browser/lib/markdown.js | 6 +- 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/browser/lib/markdown-it-sanitize-html.js b/browser/lib/markdown-it-sanitize-html.js index 05e5e7be..ea27bfa0 100644 --- a/browser/lib/markdown-it-sanitize-html.js +++ b/browser/lib/markdown-it-sanitize-html.js @@ -2,6 +2,7 @@ import sanitizeHtml from 'sanitize-html' import { escapeHtmlCharacters } from './utils' +import url from 'url' module.exports = function sanitizePlugin (md, options) { options = options || {} @@ -25,7 +26,7 @@ module.exports = function sanitizePlugin (md, options) { const inlineTokens = state.tokens[tokenIdx].children for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) { if (inlineTokens[childIdx].type === 'html_inline') { - inlineTokens[childIdx].content = sanitizeHtml( + inlineTokens[childIdx].content = sanitizeInline( inlineTokens[childIdx].content, options ) @@ -35,3 +36,86 @@ module.exports = function sanitizePlugin (md, options) { } }) } + +const tag_regex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:="(?:[^\"]+)\")?)*)\s*>|<\/([A-Z][A-Z0-9]*)\s*>/i +const attributes_regex = /([A-Z][A-Z0-9]*)(="[^\"]+\")?/ig + +function sanitizeInline(html, options) { + let match = tag_regex.exec(html) + if (!match) { + return '' + } + + const { allowedTags, allowedAttributes, allowedIframeHostnames, selfClosing, allowedSchemesAppliedToAttributes } = options + + if (match[1] !== null) { + // opening tag + const tag = match[1].toLowerCase() + if (allowedTags.indexOf(tag) === -1) { + return '' + } + + const attributes = match[2] + + let attrs = '' + let name + let value + + while ((match = attributes_regex.exec(attributes))) { + name = match[1].toLowerCase() + value = match[2] + + if (allowedAttributes['*'].indexOf(name) !== -1 || (allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)) { + if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) { + if (naughtyHRef(value) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value))) { + continue + } + } + + attrs += ` ${name}${value}` + } + } + + if (selfClosing.indexOf(tag)) { + return '<' + tag + attrs + ' />' + } else { + return '<' + tag + attrs + '>' + } + } else { + // closing tag + if (allowedTags.indexOf(match[3].toLowerCase()) !== -1) { + return html + } else { + return '' + } + } +} + +function naughtyHRef(name, href, options) { + href = href.replace(/[\x00-\x20]+/g, '') + href = href.replace(/<\!\-\-.*?\-\-\>/g, '') + + const matches = href.match(/^([a-zA-Z]+)\:/) + if (!matches) { + if (href.match(/^[\/\\]{2}/)) { + return !options.allowProtocolRelative + } + + // No scheme + return false + } + + const scheme = matches[1].toLowerCase() + + return options.allowedSchemes.indexOf(scheme) === -1 +} + +function naughtyIFrame(src) { + try { + const parsed = url.parse(src, false, true) + + return allowedIframeHostnames.index(parsed.hostname) === -1 + } catch (e) { + return true + } +} \ No newline at end of file diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 49fd2f86..90b1ccea 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -105,7 +105,11 @@ class Markdown { 'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'], 'input': ['type', 'id', 'checked'] }, - allowedIframeHostnames: ['www.youtube.com'] + allowedIframeHostnames: ['www.youtube.com'], + selfClosing: [ 'img', 'br', 'hr', 'input' ], + allowedSchemes: [ 'http', 'https', 'ftp', 'mailto' ], + allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ], + allowProtocolRelative: true }) } From fabc975b2025c12402312862c26abfb12bcc00b4 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sat, 25 Aug 2018 23:36:43 +0200 Subject: [PATCH 06/59] - fix lint errors - correctly parse self-closed tag - fix naughty functions --- browser/lib/markdown-it-sanitize-html.js | 46 ++++++++++++------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/browser/lib/markdown-it-sanitize-html.js b/browser/lib/markdown-it-sanitize-html.js index ea27bfa0..9bdd3034 100644 --- a/browser/lib/markdown-it-sanitize-html.js +++ b/browser/lib/markdown-it-sanitize-html.js @@ -37,45 +37,45 @@ module.exports = function sanitizePlugin (md, options) { }) } -const tag_regex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:="(?:[^\"]+)\")?)*)\s*>|<\/([A-Z][A-Z0-9]*)\s*>/i -const attributes_regex = /([A-Z][A-Z0-9]*)(="[^\"]+\")?/ig +const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:="(?:[^\"]+)\")?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i +const attributesRegex = /([A-Z][A-Z0-9]*)(="[^\"]+\")?/ig -function sanitizeInline(html, options) { - let match = tag_regex.exec(html) +function sanitizeInline (html, options) { + let match = tagRegex.exec(html) if (!match) { return '' } - - const { allowedTags, allowedAttributes, allowedIframeHostnames, selfClosing, allowedSchemesAppliedToAttributes } = options - - if (match[1] !== null) { + + const { allowedTags, allowedAttributes, selfClosing, allowedSchemesAppliedToAttributes } = options + + if (match[1] !== undefined) { // opening tag const tag = match[1].toLowerCase() if (allowedTags.indexOf(tag) === -1) { return '' } - + const attributes = match[2] - + let attrs = '' let name let value - - while ((match = attributes_regex.exec(attributes))) { + + while ((match = attributesRegex.exec(attributes))) { name = match[1].toLowerCase() value = match[2] - + if (allowedAttributes['*'].indexOf(name) !== -1 || (allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)) { if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) { - if (naughtyHRef(value) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value))) { + if (naughtyHRef(value, options) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value, options))) { continue } } - + attrs += ` ${name}${value}` } } - + if (selfClosing.indexOf(tag)) { return '<' + tag + attrs + ' />' } else { @@ -91,10 +91,10 @@ function sanitizeInline(html, options) { } } -function naughtyHRef(name, href, options) { - href = href.replace(/[\x00-\x20]+/g, '') +function naughtyHRef (href, options) { + // href = href.replace(/[\x00-\x20]+/g, '') href = href.replace(/<\!\-\-.*?\-\-\>/g, '') - + const matches = href.match(/^([a-zA-Z]+)\:/) if (!matches) { if (href.match(/^[\/\\]{2}/)) { @@ -110,12 +110,12 @@ function naughtyHRef(name, href, options) { return options.allowedSchemes.indexOf(scheme) === -1 } -function naughtyIFrame(src) { +function naughtyIFrame (src, options) { try { const parsed = url.parse(src, false, true) - - return allowedIframeHostnames.index(parsed.hostname) === -1 + + return options.allowedIframeHostnames.index(parsed.hostname) === -1 } catch (e) { return true } -} \ No newline at end of file +} From 2a838ebb0b46bad08f54f46256396fac1c52c02a Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sun, 26 Aug 2018 00:14:29 +0200 Subject: [PATCH 07/59] fixing single quoted attributes --- browser/lib/markdown-it-sanitize-html.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/browser/lib/markdown-it-sanitize-html.js b/browser/lib/markdown-it-sanitize-html.js index 9bdd3034..ce6c5e29 100644 --- a/browser/lib/markdown-it-sanitize-html.js +++ b/browser/lib/markdown-it-sanitize-html.js @@ -37,8 +37,8 @@ module.exports = function sanitizePlugin (md, options) { }) } -const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:="(?:[^\"]+)\")?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i -const attributesRegex = /([A-Z][A-Z0-9]*)(="[^\"]+\")?/ig +const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:=("|')(?:[^\3]+?)\3)?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i +const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/ig function sanitizeInline (html, options) { let match = tagRegex.exec(html) @@ -63,7 +63,7 @@ function sanitizeInline (html, options) { while ((match = attributesRegex.exec(attributes))) { name = match[1].toLowerCase() - value = match[2] + value = match[3] if (allowedAttributes['*'].indexOf(name) !== -1 || (allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)) { if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) { @@ -72,7 +72,10 @@ function sanitizeInline (html, options) { } } - attrs += ` ${name}${value}` + attrs += ` ${name}` + if (match[2]) { + attrs += `="${value}"` + } } } @@ -83,7 +86,7 @@ function sanitizeInline (html, options) { } } else { // closing tag - if (allowedTags.indexOf(match[3].toLowerCase()) !== -1) { + if (allowedTags.indexOf(match[4].toLowerCase()) !== -1) { return html } else { return '' From 5006aaae38d409aab09d0b9b1bd73e19be09959b Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Tue, 28 Aug 2018 01:44:33 +0200 Subject: [PATCH 08/59] fix live note counts when multiple tags are selected --- browser/main/SideNav/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index 0dd34989..65f76691 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -19,8 +19,8 @@ import {SortableContainer} from 'react-sortable-hoc' import i18n from 'browser/lib/i18n' import context from 'browser/lib/context' -function findOne (haystack, arr) { - return arr.some(v => haystack.indexOf(v) >= 0) +function matchActiveTags (tags, activeTags) { + return _.every(activeTags, v => tags.indexOf(v) >= 0) } class SideNav extends React.Component { @@ -158,7 +158,7 @@ class SideNav extends React.Component { if (config.ui.enableLiveNoteCounts && activeTags.length !== 0) { const notesTags = data.noteMap.map(note => note.tags) tagList = tagList.map(tag => { - tag.size = notesTags.filter(tags => tags.includes(tag.name) && findOne(tags, activeTags)).length + tag.size = notesTags.filter(tags => tags.includes(tag.name) && matchActiveTags(tags, activeTags)).length return tag }) } From 8b4a9dd3259b6935e9fc2c6b346ca62d0ee02cdb Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Wed, 29 Aug 2018 19:28:09 +0200 Subject: [PATCH 09/59] add `saveTagsAlphabetically` option --- browser/main/Detail/MarkdownNoteDetail.js | 1 + browser/main/Detail/SnippetNoteDetail.js | 1 + browser/main/Detail/TagSelect.js | 10 ++++++++-- browser/main/modals/PreferencesModal/UiTab.js | 12 ++++++++++++ locales/en.json | 1 + locales/fr.json | 1 + 6 files changed, 24 insertions(+), 2 deletions(-) diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 705736e9..1e30d28b 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -363,6 +363,7 @@ class MarkdownNoteDetail extends React.Component { diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 3633ec85..4a54e7c4 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -744,6 +744,7 @@ class SnippetNoteDetail extends React.Component { this.handleChange(e)} /> diff --git a/browser/main/Detail/TagSelect.js b/browser/main/Detail/TagSelect.js index e51d5673..9bfbabbe 100644 --- a/browser/main/Detail/TagSelect.js +++ b/browser/main/Detail/TagSelect.js @@ -82,8 +82,14 @@ class TagSelect extends React.Component { value = _.isArray(value) ? value.slice() : [] - value.push(newTag) - value = _.uniq(value) + + if (!_.includes(value, newTag)) { + value.push(newTag) + } + + if (this.props.saveTagsAlphabetically) { + value = _.sortBy(value) + } this.setState({ newTag: '' diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index 40269190..a53281bc 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -71,6 +71,7 @@ class UiTab extends React.Component { confirmDeletion: this.refs.confirmDeletion.checked, showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked, showTagsAlphabetically: this.refs.showTagsAlphabetically.checked, + saveTagsAlphabetically: this.refs.saveTagsAlphabetically.checked, enableLiveNoteCounts: this.refs.enableLiveNoteCounts.checked, disableDirectWrite: this.refs.uiD2w != null ? this.refs.uiD2w.checked @@ -244,6 +245,17 @@ class UiTab extends React.Component { }
Tags
+
+ +
+
- +
+
+
{i18n.__('Delete Note')}
+
+ this.handleHotkeyChange(e)} + ref='deleteNote' + value={config.hotkey.deleteNote} + type='text' + /> +
+
) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index e69f312e..47f30ecc 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -484,10 +484,6 @@ export default class MarkdownPreview extends React.Component { eventEmitter.on('export:save-md', this.saveAsMdHandler) eventEmitter.on('export:save-html', this.saveAsHtmlHandler) eventEmitter.on('print', this.printHandler) - eventEmitter.on('config-renew', () => { - this.markdown.updateConfig() - this.rewriteIframe() - }) } componentWillUnmount () { @@ -531,7 +527,8 @@ export default class MarkdownPreview extends React.Component { prevProps.smartQuotes !== this.props.smartQuotes || prevProps.sanitize !== this.props.sanitize || prevProps.smartArrows !== this.props.smartArrows || - prevProps.breaks !== this.props.breaks + prevProps.breaks !== this.props.breaks || + prevProps.lineThroughCheckbox !== this.props.lineThroughCheckbox ) { this.initMarkdown() this.rewriteIframe() diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index d714125a..208a29b4 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -192,6 +192,7 @@ class MarkdownSplitEditor extends React.Component { noteKey={noteKey} customCSS={config.preview.customCSS} allowCustomCSS={config.preview.allowCustomCSS} + lineThroughCheckbox={config.preview.lineThroughCheckbox} />
) From 3c404f36784548675f4569e0cb86c4a3c8163f27 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Thu, 11 Oct 2018 15:09:26 +0200 Subject: [PATCH 36/59] remove unused method --- browser/lib/markdown.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 248dbb4b..49260740 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -21,7 +21,7 @@ function createGutter (str, firstLineNumber) { class Markdown { constructor (options = {}) { - let config = ConfigManager.get() + const config = ConfigManager.get() const defaultOptions = { typographer: config.preview.smartQuotes, linkify: true, @@ -265,9 +265,6 @@ class Markdown { } // FIXME We should not depend on global variable. window.md = this.md - this.updateConfig = () => { - config = ConfigManager.get() - } } render (content) { From ca038937e9cfab366a1af89f31b5ace8a77a6b81 Mon Sep 17 00:00:00 2001 From: jacob Date: Fri, 12 Oct 2018 22:10:35 -0500 Subject: [PATCH 37/59] Update ConfigManager.js --- browser/main/lib/ConfigManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index 42bf35c9..7f8ae6fb 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -25,7 +25,7 @@ export const DEFAULT_CONFIG = { hotkey: { toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E', toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M', - deleteNote: OSX ? 'Command + Alt + Backspace' : 'Ctrl + Alt + Backspace' + deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace' }, ui: { language: 'en', From e536d203d22401f0ae7214b4efe75796aa4fe56d Mon Sep 17 00:00:00 2001 From: antogyn Date: Sat, 13 Oct 2018 14:21:46 +0200 Subject: [PATCH 38/59] feat(editor): add ability to jump to line --- browser/components/CodeEditor.js | 10 +++++++++- browser/components/MarkdownPreview.js | 9 +++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index c36a50c1..9953f9bf 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -57,6 +57,7 @@ export default class CodeEditor extends React.Component { } this.searchHandler = (e, msg) => this.handleSearch(msg) this.searchState = null + this.scrollToLineHandeler = this.scrollToLine.bind(this) this.formatTable = () => this.handleFormatTable() this.editorActivityHandler = () => this.handleEditorActivity() @@ -125,6 +126,7 @@ export default class CodeEditor extends React.Component { componentDidMount () { const { rulers, enableRulers } = this.props const expandSnippet = this.expandSnippet.bind(this) + eventEmitter.on('line:jump', this.scrollToLineHandeler) const defaultSnippet = [ { @@ -475,7 +477,13 @@ export default class CodeEditor extends React.Component { moveCursorTo (row, col) {} - scrollToLine (num) {} + scrollToLine (event, num) { + const cursor = { + line: num, + ch: 1 + } + this.editor.setCursor(cursor) + } focus () { this.editor.focus() diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index e69f312e..dbb2acd7 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -860,6 +860,15 @@ export default class MarkdownPreview extends React.Component { return } + const regexIsLine = /^:line:[0-9]/ + if (regexIsLine.test(linkHash)) { + const numberPattern = /\d+/g + + const lineNumber = parseInt(linkHash.match(numberPattern)[0]) + eventEmitter.emit('line:jump', lineNumber) + return + } + // this will match the old link format storage.key-note.key // e.g. // 877f99c3268608328037-1c211eb7dcb463de6490 From 1da477d1d1992b5741dc4686e9a501ca857a90c5 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Mon, 15 Oct 2018 08:09:50 +0200 Subject: [PATCH 39/59] add context menu to delete tag --- browser/components/TagListItem.js | 4 +- browser/main/Detail/MarkdownNoteDetail.js | 2 +- browser/main/Main.js | 2 +- browser/main/SideNav/index.js | 59 +++++++++++++++++++ .../TagListItem.snapshot.test.js.snap | 1 + 5 files changed, 64 insertions(+), 4 deletions(-) diff --git a/browser/components/TagListItem.js b/browser/components/TagListItem.js index 6cd50c9c..19f11791 100644 --- a/browser/components/TagListItem.js +++ b/browser/components/TagListItem.js @@ -14,8 +14,8 @@ import CSSModules from 'browser/lib/CSSModules' * @param {bool} isRelated */ -const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isActive, isRelated, count}) => ( -
+const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count}) => ( +
handleContextMenu(e, name)}> {isRelated ?
this.handleSwitchMode(e)} editorType={editorType} /> From ff3026686ff7a8c8954d94527dbfb4538c9addcd Mon Sep 17 00:00:00 2001 From: Junyoung Choi Date: Fri, 19 Oct 2018 19:04:53 +0900 Subject: [PATCH 46/59] v0.11.10 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index be70464b..d39d6f37 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "boost", "productName": "Boostnote", - "version": "0.11.9", + "version": "0.11.10", "main": "index.js", "description": "Boostnote", "license": "GPL-3.0", From 5aaecfc0fe8a4ac76b62c669c27a159205b05271 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Fri, 19 Oct 2018 14:06:21 +0200 Subject: [PATCH 47/59] fix color for even rows in table --- extra_scripts/codemirror/mode/bfm/bfm.css | 5 +-- extra_scripts/codemirror/mode/bfm/bfm.js | 3 ++ gruntfile.js | 44 ++++++++++++++++++++--- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/extra_scripts/codemirror/mode/bfm/bfm.css b/extra_scripts/codemirror/mode/bfm/bfm.css index cb6fd638..f14d6f7c 100644 --- a/extra_scripts/codemirror/mode/bfm/bfm.css +++ b/extra_scripts/codemirror/mode/bfm/bfm.css @@ -34,11 +34,12 @@ .cm-s-rubyblue.CodeMirror .cm-table-row-even { background-color: rgb(41, 58, 73); } .cm-s-seti.CodeMirror .cm-table-row-even { background-color: rgb(40, 42, 43); } .cm-s-shadowfox.CodeMirror .cm-table-row-even { background-color: rgb(56, 56, 59); } -.cm-s-solarized.CodeMirror .cm-table-row-even { background-color: rgb(242, 242, 242); } .cm-s-the-matrix.CodeMirror .cm-table-row-even { background-color: rgb(0, 26, 0); } .cm-s-tomorrow-night-bright.CodeMirror .cm-table-row-even { background-color: rgb(23, 23, 23); } .cm-s-tomorrow-night-eighties.CodeMirror .cm-table-row-even { background-color: rgb(20, 20, 20); } .cm-s-twilight.CodeMirror .cm-table-row-even { background-color: rgb(43, 43, 43); } .cm-s-vibrant-ink.CodeMirror .cm-table-row-even { background-color: rgb(26, 26, 26); } .cm-s-xq-dark.CodeMirror .cm-table-row-even { background-color: rgb(34, 25, 53); } -.cm-s-yeti.CodeMirror .cm-table-row-even { background-color: rgb(235, 232, 230); } \ No newline at end of file +.cm-s-yeti.CodeMirror .cm-table-row-even { background-color: rgb(235, 232, 230); } +.cm-s-solarized.cm-s-dark .cm-table-row-even { background-color: rgb(13, 54, 64); } +.cm-s-solarized.cm-s-light .cm-table-row-even { background-color: rgb(245, 240, 222); } \ No newline at end of file diff --git a/extra_scripts/codemirror/mode/bfm/bfm.js b/extra_scripts/codemirror/mode/bfm/bfm.js index 1cd9e87c..baf65d18 100644 --- a/extra_scripts/codemirror/mode/bfm/bfm.js +++ b/extra_scripts/codemirror/mode/bfm/bfm.js @@ -54,6 +54,9 @@ stream.skipToEnd() return null + }, + blankLine(state) { + state.inTable = false } } diff --git a/gruntfile.js b/gruntfile.js index 651640fc..883e536c 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -289,9 +289,24 @@ module.exports = function (grunt) { const Color = require('color') const parseCSS = require('css').parse + function generateRule(selector, bgColor, fgColor) { + if (bgColor.isLight()) { + bgColor = bgColor.mix(fgColor, 0.05) + } else { + bgColor = bgColor.mix(fgColor, 0.1) + } + + if (selector && selector.length > 0) { + return `${selector} .cm-table-row-even { background-color: ${bgColor.rgb().string()}; }` + } + else { + return `.cm-table-row-even { background-color: ${bgColor.rgb().string()}; }` + } + } + const root = path.join(__dirname, 'node_modules/codemirror/theme/') - const colors = fs.readdirSync(root).map(file => { + const colors = fs.readdirSync(root).filter(file => file !== 'solarized.css').map(file => { const css = parseCSS(fs.readFileSync(path.join(root, file), 'utf8')) const rules = css.stylesheet.rules.filter(rule => rule.selectors && /\b\.CodeMirror$/.test(rule.selectors[0])) @@ -313,19 +328,38 @@ module.exports = function (grunt) { } }) - if (bgColor.isLight()) { + /* if (bgColor.isLight()) { bgColor = bgColor.mix(fgColor, 0.05) } else { bgColor = bgColor.mix(fgColor, 0.1) } - return `${rules[0].selectors[0]} .cm-table-row-even { background-color: ${bgColor.rgb().string()}; }` + return `${rules[0].selectors[0]} .cm-table-row-even { background-color: ${bgColor.rgb().string()}; }` */ + return generateRule(rules[0].selectors[0], bgColor, fgColor) } }).filter(value => !!value) - const defaultBgColor = Color('white').mix(Color('black'), 0.05) + //const defaultBgColor = Color('white').mix(Color('black'), 0.05) - fs.writeFileSync(path.join(__dirname, 'extra_scripts/codemirror/mode/bfm/bfm.css'), [`.cm-table-row-even { background-color: ${defaultBgColor.rgb().string()}; }`, ...colors].join('\n'), 'utf8') + // default + colors.unshift(generateRule(null, Color('white'), Color('black'))) + // solarized dark + colors.push(generateRule('.cm-s-solarized.cm-s-dark', Color('#002b36'), Color('#839496'))) + // solarized light + colors.push(generateRule('.cm-s-solarized.cm-s-light', Color('#fdf6e3'), Color('#657b83'))) + /* .cm-s-solarized.cm-s-dark { + color: #839496; + background-color: #002b36; + text-shadow: #002b36 0 1px; +} +.cm-s-solarized.cm-s-light { + background-color: #fdf6e3; + color: #657b83; + text-shadow: #eee8d5 0 1px; +} */ + + /* fs.writeFileSync(path.join(__dirname, 'extra_scripts/codemirror/mode/bfm/bfm.css'), [`.cm-table-row-even { background-color: ${defaultBgColor.rgb().string()}; }`, ...colors].join('\n'), 'utf8') */ + fs.writeFileSync(path.join(__dirname, 'extra_scripts/codemirror/mode/bfm/bfm.css'), colors.join('\n'), 'utf8') }) grunt.registerTask('default', ['build']) From 9d84fe77195a8ccb41b8043a66997af7eca6c01d Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Fri, 19 Oct 2018 14:09:13 +0200 Subject: [PATCH 48/59] fix lint errors --- gruntfile.js | 43 +++++++++++-------------------------------- 1 file changed, 11 insertions(+), 32 deletions(-) diff --git a/gruntfile.js b/gruntfile.js index 883e536c..ec3bbf79 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -289,19 +289,18 @@ module.exports = function (grunt) { const Color = require('color') const parseCSS = require('css').parse - function generateRule(selector, bgColor, fgColor) { + function generateRule (selector, bgColor, fgColor) { if (bgColor.isLight()) { - bgColor = bgColor.mix(fgColor, 0.05) - } else { - bgColor = bgColor.mix(fgColor, 0.1) - } + bgColor = bgColor.mix(fgColor, 0.05) + } else { + bgColor = bgColor.mix(fgColor, 0.1) + } - if (selector && selector.length > 0) { - return `${selector} .cm-table-row-even { background-color: ${bgColor.rgb().string()}; }` - } - else { - return `.cm-table-row-even { background-color: ${bgColor.rgb().string()}; }` - } + if (selector && selector.length > 0) { + return `${selector} .cm-table-row-even { background-color: ${bgColor.rgb().string()}; }` + } else { + return `.cm-table-row-even { background-color: ${bgColor.rgb().string()}; }` + } } const root = path.join(__dirname, 'node_modules/codemirror/theme/') @@ -319,7 +318,7 @@ module.exports = function (grunt) { bgColor = Color(declaration.value.split(' ')[0]) } else if (declaration.property === 'color') { const value = /^(.*?)(?:\s*!important)?$/.exec(declaration.value)[1] - let match = /^rgba\((.*?),\s*1\)$/.exec(value) + const match = /^rgba\((.*?),\s*1\)$/.exec(value) if (match) { fgColor = Color(`rgb(${match[1]})`) } else { @@ -328,37 +327,17 @@ module.exports = function (grunt) { } }) - /* if (bgColor.isLight()) { - bgColor = bgColor.mix(fgColor, 0.05) - } else { - bgColor = bgColor.mix(fgColor, 0.1) - } - - return `${rules[0].selectors[0]} .cm-table-row-even { background-color: ${bgColor.rgb().string()}; }` */ return generateRule(rules[0].selectors[0], bgColor, fgColor) } }).filter(value => !!value) - //const defaultBgColor = Color('white').mix(Color('black'), 0.05) - // default colors.unshift(generateRule(null, Color('white'), Color('black'))) // solarized dark colors.push(generateRule('.cm-s-solarized.cm-s-dark', Color('#002b36'), Color('#839496'))) // solarized light colors.push(generateRule('.cm-s-solarized.cm-s-light', Color('#fdf6e3'), Color('#657b83'))) - /* .cm-s-solarized.cm-s-dark { - color: #839496; - background-color: #002b36; - text-shadow: #002b36 0 1px; -} -.cm-s-solarized.cm-s-light { - background-color: #fdf6e3; - color: #657b83; - text-shadow: #eee8d5 0 1px; -} */ - /* fs.writeFileSync(path.join(__dirname, 'extra_scripts/codemirror/mode/bfm/bfm.css'), [`.cm-table-row-even { background-color: ${defaultBgColor.rgb().string()}; }`, ...colors].join('\n'), 'utf8') */ fs.writeFileSync(path.join(__dirname, 'extra_scripts/codemirror/mode/bfm/bfm.css'), colors.join('\n'), 'utf8') }) From 0e38f61c85031949aadcc45f4e83fbb50531806f Mon Sep 17 00:00:00 2001 From: David Nagle Date: Fri, 19 Oct 2018 10:59:37 -0700 Subject: [PATCH 49/59] Title will now be extracted from front matter if title: is present. --- browser/lib/findNoteTitle.js | 29 ++++++++++++++++++----------- tests/lib/find-title-test.js | 6 ++++-- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/browser/lib/findNoteTitle.js b/browser/lib/findNoteTitle.js index b954f172..f28e44de 100644 --- a/browser/lib/findNoteTitle.js +++ b/browser/lib/findNoteTitle.js @@ -6,6 +6,11 @@ export function findNoteTitle (value) { if (splitted[0] === '---') { let line = 0 while (++line < splitted.length) { + if (splitted[line].startsWith('title:')) { + title = splitted[line].substring(6).trim() + + break + } if (splitted[line] === '---') { splitted.splice(0, line + 1) @@ -14,17 +19,19 @@ export function findNoteTitle (value) { } } - splitted.some((line, index) => { - const trimmedLine = line.trim() - const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim() - if (trimmedLine.match('```')) { - isInsideCodeBlock = !isInsideCodeBlock - } - if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) { - title = trimmedLine - return true - } - }) + if (title === null) { + splitted.some((line, index) => { + const trimmedLine = line.trim() + const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim() + if (trimmedLine.match('```')) { + isInsideCodeBlock = !isInsideCodeBlock + } + if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) { + title = trimmedLine + return true + } + }) + } if (title === null) { title = '' diff --git a/tests/lib/find-title-test.js b/tests/lib/find-title-test.js index f587804c..1e9daa0f 100644 --- a/tests/lib/find-title-test.js +++ b/tests/lib/find-title-test.js @@ -15,7 +15,10 @@ test('findNoteTitle#find should return a correct title (string)', t => { ['====', '===='], ['```\n# hoge\n```', '```'], ['hoge', 'hoge'], - ['---\nlayout: test\n---\n # hoge', '# hoge'] + ['---\nlayout: test\n---\n # hoge', '# hoge'], + ['---\nlayout: test\ntitle: hoge hoge hoge \n---\n# fuga', 'hoge hoge hoge'], + ['---\ntitle:hoge\n---\n# fuga', 'hoge'], + ['title: fuga\n# hoge', '# hoge'] ] testCases.forEach(testCase => { @@ -23,4 +26,3 @@ test('findNoteTitle#find should return a correct title (string)', t => { t.is(findNoteTitle(input), expected, `Test for find() input: ${input} expected: ${expected}`) }) }) - From 73d1bdf84bf06e0f161a9dc8745fd557b0fa691f Mon Sep 17 00:00:00 2001 From: David Nagle Date: Fri, 19 Oct 2018 14:29:48 -0700 Subject: [PATCH 50/59] Prevent empty printed page with scroll-past-end --- browser/components/MarkdownPreview.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index e69f312e..749bd43f 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -88,6 +88,11 @@ body { font-size: ${fontSize}px; ${scrollPastEnd && 'padding-bottom: 90vh;'} } +@media print { + body { + padding-bottom: initial; + } +} code { font-family: '${codeBlockFontFamily.join("','")}'; background-color: rgba(0,0,0,0.04); From 3272033c632a4e7386d5901606b915aee6f81fd5 Mon Sep 17 00:00:00 2001 From: David Nagle Date: Fri, 19 Oct 2018 13:41:32 -0700 Subject: [PATCH 51/59] Added preferences to support toggling front matter title extraction on/off and to customize what field is used for the title. --- browser/lib/findNoteTitle.js | 6 +-- browser/main/Detail/MarkdownNoteDetail.js | 2 +- browser/main/Detail/SnippetNoteDetail.js | 2 +- browser/main/lib/ConfigManager.js | 4 +- browser/main/modals/PreferencesModal/UiTab.js | 29 ++++++++++++- tests/lib/find-title-test.js | 42 ++++++++++++++++++- 6 files changed, 76 insertions(+), 9 deletions(-) diff --git a/browser/lib/findNoteTitle.js b/browser/lib/findNoteTitle.js index f28e44de..912c3bdd 100644 --- a/browser/lib/findNoteTitle.js +++ b/browser/lib/findNoteTitle.js @@ -1,4 +1,4 @@ -export function findNoteTitle (value) { +export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleField = 'title') { const splitted = value.split('\n') let title = null let isInsideCodeBlock = false @@ -6,8 +6,8 @@ export function findNoteTitle (value) { if (splitted[0] === '---') { let line = 0 while (++line < splitted.length) { - if (splitted[line].startsWith('title:')) { - title = splitted[line].substring(6).trim() + if (enableFrontMatterTitle && splitted[line].startsWith(frontMatterTitleField + ':')) { + title = splitted[line].substring(frontMatterTitleField.length + 1).trim() break } diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index e4493a80..fe98c8bf 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -91,7 +91,7 @@ class MarkdownNoteDetail extends React.Component { handleUpdateContent () { const { note } = this.state note.content = this.refs.content.value - note.title = markdown.strip(striptags(findNoteTitle(note.content))) + note.title = markdown.strip(striptags(findNoteTitle(note.content, this.props.config.editor.enableFrontMatterTitle, this.props.config.editor.frontMatterTitleField))) this.updateNote(note) } diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 9356a02c..65d5dfd3 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -112,7 +112,7 @@ class SnippetNoteDetail extends React.Component { if (this.refs.tags) note.tags = this.refs.tags.value note.description = this.refs.description.value note.updatedAt = new Date() - note.title = findNoteTitle(note.description) + note.title = findNoteTitle(note.description, false) this.setState({ note diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index 1727deb8..33cab7aa 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -47,7 +47,9 @@ export const DEFAULT_CONFIG = { scrollPastEnd: false, type: 'SPLIT', fetchUrlTitle: true, - enableTableEditor: false + enableTableEditor: false, + enableFrontMatterTitle: true, + frontMatterTitleField: 'title' }, preview: { fontSize: '14', diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index 97f3877c..638fab90 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -89,7 +89,9 @@ class UiTab extends React.Component { snippetDefaultLanguage: this.refs.editorSnippetDefaultLanguage.value, scrollPastEnd: this.refs.scrollPastEnd.checked, fetchUrlTitle: this.refs.editorFetchUrlTitle.checked, - enableTableEditor: this.refs.enableTableEditor.checked + enableTableEditor: this.refs.enableTableEditor.checked, + enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked, + frontMatterTitleField: this.refs.frontMatterTitleField.value }, preview: { fontSize: this.refs.previewFontSize.value, @@ -428,6 +430,31 @@ class UiTab extends React.Component {
+
+
+ {i18n.__('Front matter title field')} +
+
+ this.handleUIChange(e)} + type='text' + /> +
+
+ +
+ +
+
+
+ +