From 52f694a714ea80ec980b5a612cdb1cd1bd113175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Sat, 19 May 2018 18:58:44 +0700 Subject: [PATCH 01/74] fixed null attachment --- browser/main/lib/dataApi/attachmentManagement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index efacd47c..7c4b46be 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -172,7 +172,7 @@ function getAttachmentsInContent (markdownContent) { * @returns {String[]} Absolute paths of the referenced attachments */ function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) { - const temp = getAttachmentsInContent(markdownContent) + const temp = getAttachmentsInContent(markdownContent) || [] const result = [] for (const relativePath of temp) { result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER))) From d6c28da3a8d61c149a2c5f9c6af46a1b57407f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Sat, 19 May 2018 19:39:08 +0700 Subject: [PATCH 02/74] added export escape html config --- browser/components/MarkdownPreview.js | 3 +- browser/main/lib/ConfigManager.js | 3 + .../main/modals/PreferencesModal/Export.js | 120 ++++++++++++++++++ browser/main/modals/PreferencesModal/index.js | 11 +- 4 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 browser/main/modals/PreferencesModal/Export.js diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 6646f749..ef11a0cc 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -15,6 +15,7 @@ import copy from 'copy-to-clipboard' import mdurl from 'mdurl' import exportNote from 'browser/main/lib/dataApi/exportNote' import { escapeHtmlCharacters } from 'browser/lib/utils' +import ConfigManager from 'browser/main/lib/ConfigManager' const { remote } = require('electron') const attachmentManagement = require('../main/lib/dataApi/attachmentManagement') @@ -218,7 +219,7 @@ export default class MarkdownPreview extends React.Component { const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = this.getStyleParams() const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme) - let body = this.markdown.render(escapeHtmlCharacters(noteContent)) + let body = this.markdown.render(ConfigManager.get().exports.escapeHtml ? escapeHtmlCharacters(noteContent) : noteContent) const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath) diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index 79fe0f5f..5b263191 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -65,6 +65,9 @@ export const DEFAULT_CONFIG = { token: '', username: '', password: '' + }, + exports: { + escapeHtml: true } } diff --git a/browser/main/modals/PreferencesModal/Export.js b/browser/main/modals/PreferencesModal/Export.js new file mode 100644 index 00000000..ae57cc14 --- /dev/null +++ b/browser/main/modals/PreferencesModal/Export.js @@ -0,0 +1,120 @@ +import React from 'react' +import ConfigManager from 'browser/main/lib/ConfigManager' +import i18n from 'browser/lib/i18n' +import styles from './ConfigTab.styl' +import CSSModules from 'browser/lib/CSSModules' +import store from 'browser/main/store' + +const electron = require('electron') +const ipc = electron.ipcRenderer + +class Export extends React.Component { + constructor(props) { + super(props) + this.state = { + config: props.config + } + } + + componentDidMount () { + this.handleSettingDone = () => { + this.setState({ExportAlert: { + type: 'success', + message: i18n.__('Successfully applied!') + }}) + } + this.handleSettingError = (err) => { + this.setState({ExportAlert: { + type: 'error', + message: err.message != null ? err.message : i18n.__('Error occurs!') + }}) + } + ipc.addListener('APP_SETTING_DONE', this.handleSettingDone) + ipc.addListener('APP_SETTING_ERROR', this.handleSettingError) + } + + componentWillUnmount () { + ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone) + ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError) + } + + handleUIChange (e) { + const newConfig = { + exports: { + escapeHtml: this.refs.escapeHtmlExport.checked + } + } + + this.setState({ config: newConfig }, () => { + const { exports } = this.props.config + this.currentConfig = { exports } + if (_.isEqual(this.currentConfig, this.state.config)) { + this.props.haveToSave() + } else { + this.props.haveToSave({ + tab: 'EXPORT', + type: 'warning', + message: i18n.__('You have to save!') + }) + } + }) + } + + handleSaveUIClick (e) { + const newConfig = { + exports: this.state.config.exports + } + + ConfigManager.set(newConfig) + + store.dispatch({ + type: 'SET_UI', + config: newConfig + }) + this.clearMessage() + this.props.haveToSave() + } + + clearMessage () { + _.debounce(() => { + this.setState({ + UiAlert: null + }) + }, 2000)() + } + + render () { + const ExportAlert = this.state.ExportAlert + const ExportAlertElement = ExportAlert != null + ?

+ {ExportAlert.message} +

+ : null + return ( +
+
+
{i18n.__('Export')}
+ +
+ +
+
+ + {ExportAlertElement} +
+
+
+ ) + } +} + +export default CSSModules(Export, styles) diff --git a/browser/main/modals/PreferencesModal/index.js b/browser/main/modals/PreferencesModal/index.js index 70e25a88..a68cd588 100644 --- a/browser/main/modals/PreferencesModal/index.js +++ b/browser/main/modals/PreferencesModal/index.js @@ -7,6 +7,7 @@ import InfoTab from './InfoTab' import Crowdfunding from './Crowdfunding' import StoragesTab from './StoragesTab' import Blog from './Blog' +import Export from './Export' import ModalEscButton from 'browser/components/ModalEscButton' import CSSModules from 'browser/lib/CSSModules' import styles from './PreferencesModal.styl' @@ -86,6 +87,13 @@ class Preferences extends React.Component { haveToSave={alert => this.setState({BlogAlert: alert})} /> ) + case 'EXPORT': + return ( + this.setState({ExportAlert: alert})} + /> + ) case 'STORAGES': default: return ( @@ -123,7 +131,8 @@ class Preferences extends React.Component { {target: 'UI', label: i18n.__('Interface'), UI: this.state.UIAlert}, {target: 'INFO', label: i18n.__('About')}, {target: 'CROWDFUNDING', label: i18n.__('Crowdfunding')}, - {target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert} + {target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert}, + {target: 'EXPORT', label: i18n.__('Export')} ] const navButtons = tabs.map((tab) => { From 9893fd9ae55169498d1ee55fbf152b11322303ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Sat, 19 May 2018 19:52:47 +0700 Subject: [PATCH 03/74] fixed eslint --- browser/main/modals/PreferencesModal/Export.js | 2 +- browser/main/modals/PreferencesModal/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/main/modals/PreferencesModal/Export.js b/browser/main/modals/PreferencesModal/Export.js index ae57cc14..c7d99c8c 100644 --- a/browser/main/modals/PreferencesModal/Export.js +++ b/browser/main/modals/PreferencesModal/Export.js @@ -9,7 +9,7 @@ const electron = require('electron') const ipc = electron.ipcRenderer class Export extends React.Component { - constructor(props) { + constructor (props) { super(props) this.state = { config: props.config diff --git a/browser/main/modals/PreferencesModal/index.js b/browser/main/modals/PreferencesModal/index.js index a68cd588..02710289 100644 --- a/browser/main/modals/PreferencesModal/index.js +++ b/browser/main/modals/PreferencesModal/index.js @@ -89,7 +89,7 @@ class Preferences extends React.Component { ) case 'EXPORT': return ( - this.setState({ExportAlert: alert})} /> From 1516807ed548d95e031f4ff64506764fd9c50e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Sun, 20 May 2018 19:24:36 +0700 Subject: [PATCH 04/74] fixed double escape html --- browser/lib/utils.js | 43 ++++++------------------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/browser/lib/utils.js b/browser/lib/utils.js index f67ca377..d27449e0 100644 --- a/browser/lib/utils.js +++ b/browser/lib/utils.js @@ -15,43 +15,12 @@ export function escapeHtmlCharacters (text) { return str } - let escape - let html = '' - let index = 0 - let lastIndex = 0 - - for (index = match.index; index < str.length; index++) { - switch (str.charCodeAt(index)) { - case 34: // " - escape = '"' - break - case 38: // & - escape = '&' - break - case 39: // ' - escape = ''' - break - case 60: // < - escape = '<' - break - case 62: // > - escape = '>' - break - default: - continue - } - - if (lastIndex !== index) { - html += str.substring(lastIndex, index) - } - - lastIndex = index + 1 - html += escape - } - - return lastIndex !== index - ? html + str.substring(lastIndex, index) - : html + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') } export default { From 707356bffe54a4d00c944b50330134930a90ceed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Tue, 29 May 2018 18:15:57 +0700 Subject: [PATCH 05/74] revert to the original function for better performance --- browser/lib/utils.js | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/browser/lib/utils.js b/browser/lib/utils.js index d27449e0..ee4d4ad0 100644 --- a/browser/lib/utils.js +++ b/browser/lib/utils.js @@ -15,12 +15,43 @@ export function escapeHtmlCharacters (text) { return str } - return str - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, ''') + let escape + let html = '' + let index = 0 + let lastIndex = 0 + + for (index = match.index; index < str.length; index++) { + switch (str.charCodeAt(index)) { + case 34: // " + escape = '"' + break + case 38: // & + escape = '&ssssss;' + break + case 39: // ' + escape = ''' + break + case 60: // < + escape = '<' + break + case 62: // > + escape = '>' + break + default: + continue + } + + if (lastIndex !== index) { + html += str.substring(lastIndex, index) + } + + lastIndex = index + 1 + html += escape + } + + return lastIndex !== index + ? html + str.substring(lastIndex, index) + : html } export default { From c6a9c9c57d73a5e84476d473a3b75f3efbdc1392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Tue, 5 Jun 2018 21:24:37 +0700 Subject: [PATCH 06/74] fixed disappear scrollbar --- browser/components/MarkdownPreview.js | 18 ++++++++++++++++++ browser/main/global.styl | 13 +++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 89f71a9b..7b5facf2 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -126,6 +126,21 @@ body p { ` } +const scrollBarStyle = ` +::-webkit-scrollbar-track { + border-radius: 10px; +} + +::-webkit-scrollbar { + width: 12px; +} + +::-webkit-scrollbar-thumb { + box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); + background-color: rgba(0, 0, 0, 0.3); +} +` + const { shell } = require('electron') const OSX = global.process.platform === 'darwin' @@ -298,6 +313,9 @@ export default class MarkdownPreview extends React.Component { + ` CSS_FILES.forEach((file) => { diff --git a/browser/main/global.styl b/browser/main/global.styl index 7025163f..c68df2b8 100644 --- a/browser/main/global.styl +++ b/browser/main/global.styl @@ -15,6 +15,16 @@ body font-weight 200 -webkit-font-smoothing antialiased +::-webkit-scrollbar-track + border-radius 10px + +::-webkit-scrollbar + width 12px + +::-webkit-scrollbar-thumb + box-shadow inset 0 0 6px rgba(0, 0, 0, 0.3) + background-color rgba(0, 0, 0, 0.3) + button, input, select, textarea font-family DEFAULT_FONTS @@ -85,7 +95,7 @@ modalBackColor = white absolute top left bottom right background-color modalBackColor z-index modalZIndex + 1 - + body[data-theme="dark"] .ModalBase @@ -140,4 +150,3 @@ body[data-theme="monokai"] background-color $ui-monokai-backgroundColor .sortableItemHelper color: $ui-monokai-text-color - From 31f1ebe801e9ffa5180934aff46fc68e654b9a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Tue, 5 Jun 2018 21:31:50 +0700 Subject: [PATCH 07/74] removed unnecessary style --- browser/components/MarkdownPreview.js | 4 ---- browser/main/global.styl | 3 --- 2 files changed, 7 deletions(-) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 7b5facf2..0348996d 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -127,10 +127,6 @@ body p { } const scrollBarStyle = ` -::-webkit-scrollbar-track { - border-radius: 10px; -} - ::-webkit-scrollbar { width: 12px; } diff --git a/browser/main/global.styl b/browser/main/global.styl index c68df2b8..9a967085 100644 --- a/browser/main/global.styl +++ b/browser/main/global.styl @@ -15,9 +15,6 @@ body font-weight 200 -webkit-font-smoothing antialiased -::-webkit-scrollbar-track - border-radius 10px - ::-webkit-scrollbar width 12px From 0ae1263d9deff16c25f849e9a438abd0e250e671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Wed, 6 Jun 2018 00:25:49 +0700 Subject: [PATCH 08/74] abort --- browser/components/MarkdownPreview.js | 2 +- browser/lib/markdown.js | 1 - browser/main/lib/ConfigManager.js | 3 - .../main/modals/PreferencesModal/Export.js | 120 ------------------ browser/main/modals/PreferencesModal/index.js | 11 +- 5 files changed, 2 insertions(+), 135 deletions(-) delete mode 100644 browser/main/modals/PreferencesModal/Export.js diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index ef11a0cc..04fc29fd 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -219,7 +219,7 @@ export default class MarkdownPreview extends React.Component { const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = this.getStyleParams() const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme) - let body = this.markdown.render(ConfigManager.get().exports.escapeHtml ? escapeHtmlCharacters(noteContent) : noteContent) + let body = this.markdown.render(escapeHtmlCharacters(noteContent)) const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath) diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 5848aeea..2d81cd31 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -239,4 +239,3 @@ class Markdown { } export default Markdown - diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index 5b263191..79fe0f5f 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -65,9 +65,6 @@ export const DEFAULT_CONFIG = { token: '', username: '', password: '' - }, - exports: { - escapeHtml: true } } diff --git a/browser/main/modals/PreferencesModal/Export.js b/browser/main/modals/PreferencesModal/Export.js deleted file mode 100644 index c7d99c8c..00000000 --- a/browser/main/modals/PreferencesModal/Export.js +++ /dev/null @@ -1,120 +0,0 @@ -import React from 'react' -import ConfigManager from 'browser/main/lib/ConfigManager' -import i18n from 'browser/lib/i18n' -import styles from './ConfigTab.styl' -import CSSModules from 'browser/lib/CSSModules' -import store from 'browser/main/store' - -const electron = require('electron') -const ipc = electron.ipcRenderer - -class Export extends React.Component { - constructor (props) { - super(props) - this.state = { - config: props.config - } - } - - componentDidMount () { - this.handleSettingDone = () => { - this.setState({ExportAlert: { - type: 'success', - message: i18n.__('Successfully applied!') - }}) - } - this.handleSettingError = (err) => { - this.setState({ExportAlert: { - type: 'error', - message: err.message != null ? err.message : i18n.__('Error occurs!') - }}) - } - ipc.addListener('APP_SETTING_DONE', this.handleSettingDone) - ipc.addListener('APP_SETTING_ERROR', this.handleSettingError) - } - - componentWillUnmount () { - ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone) - ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError) - } - - handleUIChange (e) { - const newConfig = { - exports: { - escapeHtml: this.refs.escapeHtmlExport.checked - } - } - - this.setState({ config: newConfig }, () => { - const { exports } = this.props.config - this.currentConfig = { exports } - if (_.isEqual(this.currentConfig, this.state.config)) { - this.props.haveToSave() - } else { - this.props.haveToSave({ - tab: 'EXPORT', - type: 'warning', - message: i18n.__('You have to save!') - }) - } - }) - } - - handleSaveUIClick (e) { - const newConfig = { - exports: this.state.config.exports - } - - ConfigManager.set(newConfig) - - store.dispatch({ - type: 'SET_UI', - config: newConfig - }) - this.clearMessage() - this.props.haveToSave() - } - - clearMessage () { - _.debounce(() => { - this.setState({ - UiAlert: null - }) - }, 2000)() - } - - render () { - const ExportAlert = this.state.ExportAlert - const ExportAlertElement = ExportAlert != null - ?

- {ExportAlert.message} -

- : null - return ( -
-
-
{i18n.__('Export')}
- -
- -
-
- - {ExportAlertElement} -
-
-
- ) - } -} - -export default CSSModules(Export, styles) diff --git a/browser/main/modals/PreferencesModal/index.js b/browser/main/modals/PreferencesModal/index.js index 02710289..70e25a88 100644 --- a/browser/main/modals/PreferencesModal/index.js +++ b/browser/main/modals/PreferencesModal/index.js @@ -7,7 +7,6 @@ import InfoTab from './InfoTab' import Crowdfunding from './Crowdfunding' import StoragesTab from './StoragesTab' import Blog from './Blog' -import Export from './Export' import ModalEscButton from 'browser/components/ModalEscButton' import CSSModules from 'browser/lib/CSSModules' import styles from './PreferencesModal.styl' @@ -87,13 +86,6 @@ class Preferences extends React.Component { haveToSave={alert => this.setState({BlogAlert: alert})} /> ) - case 'EXPORT': - return ( - this.setState({ExportAlert: alert})} - /> - ) case 'STORAGES': default: return ( @@ -131,8 +123,7 @@ class Preferences extends React.Component { {target: 'UI', label: i18n.__('Interface'), UI: this.state.UIAlert}, {target: 'INFO', label: i18n.__('About')}, {target: 'CROWDFUNDING', label: i18n.__('Crowdfunding')}, - {target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert}, - {target: 'EXPORT', label: i18n.__('Export')} + {target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert} ] const navButtons = tabs.map((tab) => { From 5c60da0f8f8e50c455f04101d1140ace6ac591d6 Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Wed, 27 Jun 2018 15:59:14 +0900 Subject: [PATCH 09/74] apply style to each themes button --- browser/main/modals/PreferencesModal/SnippetTab.styl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/browser/main/modals/PreferencesModal/SnippetTab.styl b/browser/main/modals/PreferencesModal/SnippetTab.styl index 118c56ed..fcd73b3e 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.styl +++ b/browser/main/modals/PreferencesModal/SnippetTab.styl @@ -154,6 +154,8 @@ body[data-theme="dark"] background darken($ui-dark-backgroundColor, 5) .snippet-detail color white + .group-control-button + colorDarkPrimaryButton() body[data-theme="solarized-dark"] .snippets @@ -166,6 +168,8 @@ body[data-theme="solarized-dark"] background darken($ui-solarized-dark-backgroundColor, 5) .snippet-detail color white + .group-control-button + colorSolarizedDarkPrimaryButton() body[data-theme="monokai"] .snippets @@ -178,3 +182,5 @@ body[data-theme="monokai"] background darken($ui-monokai-backgroundColor, 5) .snippet-detail color white + .group-control-button + colorMonokaiPrimaryButton() From c42b5c88066c76abc8646b5b40dae56931514c30 Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Wed, 27 Jun 2018 16:13:42 +0900 Subject: [PATCH 10/74] First snippet is selected when open Snippets tab --- browser/main/modals/PreferencesModal/SnippetList.js | 7 +++++-- browser/main/modals/PreferencesModal/SnippetTab.js | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/browser/main/modals/PreferencesModal/SnippetList.js b/browser/main/modals/PreferencesModal/SnippetList.js index 3cf28cf6..22e800f2 100644 --- a/browser/main/modals/PreferencesModal/SnippetList.js +++ b/browser/main/modals/PreferencesModal/SnippetList.js @@ -21,7 +21,10 @@ class SnippetList extends React.Component { } reloadSnippetList () { - dataApi.fetchSnippet().then(snippets => this.setState({snippets})) + dataApi.fetchSnippet().then(snippets => { + this.setState({snippets}) + this.props.onSnippetSelect(snippets[0]) + }) } handleSnippetContextMenu (snippet) { @@ -43,7 +46,7 @@ class SnippetList extends React.Component { } handleSnippetClick (snippet) { - this.props.onSnippetClick(snippet) + this.props.onSnippetSelect(snippet) } createSnippet () { diff --git a/browser/main/modals/PreferencesModal/SnippetTab.js b/browser/main/modals/PreferencesModal/SnippetTab.js index 67e9ace6..ec2a872e 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.js +++ b/browser/main/modals/PreferencesModal/SnippetTab.js @@ -25,7 +25,7 @@ class SnippetTab extends React.Component { }, 500) } - handleSnippetClick (snippet) { + handleSnippetSelect (snippet) { const { currentSnippet } = this.state if (currentSnippet === null || currentSnippet.id !== snippet.id) { dataApi.fetchSnippet(snippet.id).then(changedSnippet => { @@ -66,7 +66,7 @@ class SnippetTab extends React.Component {
{i18n.__('Snippets')}
From 7414d52dc26be3a19a1fd41f2a8b6ca62f176d8b Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Wed, 27 Jun 2018 16:27:55 +0900 Subject: [PATCH 11/74] selected snippet item background-color is darken --- browser/main/modals/PreferencesModal/SnippetList.js | 12 +++++++++++- browser/main/modals/PreferencesModal/SnippetTab.js | 3 ++- browser/main/modals/PreferencesModal/SnippetTab.styl | 12 ++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/browser/main/modals/PreferencesModal/SnippetList.js b/browser/main/modals/PreferencesModal/SnippetList.js index 22e800f2..e2ba7708 100644 --- a/browser/main/modals/PreferencesModal/SnippetList.js +++ b/browser/main/modals/PreferencesModal/SnippetList.js @@ -58,6 +58,16 @@ class SnippetList extends React.Component { }).catch(err => { throw err }) } + defineSnippetStyleName (snippet) { + const { currentSnippet } = this.props + if (currentSnippet == null) return + if (currentSnippet.id === snippet.id) { + return 'snippet-item-selected' + } else { + return 'snippet-item' + } + } + render () { const { snippets } = this.state return ( @@ -73,7 +83,7 @@ class SnippetList extends React.Component { { snippets.map((snippet) => (
  • this.handleSnippetContextMenu(snippet)} onClick={() => this.handleSnippetClick(snippet)}> diff --git a/browser/main/modals/PreferencesModal/SnippetTab.js b/browser/main/modals/PreferencesModal/SnippetTab.js index ec2a872e..e35ecd69 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.js +++ b/browser/main/modals/PreferencesModal/SnippetTab.js @@ -67,7 +67,8 @@ class SnippetTab extends React.Component {
    {i18n.__('Snippets')}
    + onSnippetDeleted={this.handleDeleteSnippet.bind(this)} + currentSnippet={currentSnippet} />
    {i18n.__('Snippet name')}
    diff --git a/browser/main/modals/PreferencesModal/SnippetTab.styl b/browser/main/modals/PreferencesModal/SnippetTab.styl index fcd73b3e..02307b64 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.styl +++ b/browser/main/modals/PreferencesModal/SnippetTab.styl @@ -122,6 +122,10 @@ &:hover background darken(#f5f5f5, 5) + .snippet-item-selected + @extend .snippet-list .snippet-item + background darken(#f5f5f5, 5) + .snippet-detail width 70% height calc(100% - 200px) @@ -142,6 +146,8 @@ body[data-theme="default"], body[data-theme="white"] background $ui-borderColor &:hover background darken($ui-backgroundColor, 5) + .snippet-item-selected + background darken($ui-backgroundColor, 5) body[data-theme="dark"] .snippets @@ -152,6 +158,8 @@ body[data-theme="dark"] background $ui-dark-borderColor &:hover background darken($ui-dark-backgroundColor, 5) + .snippet-item-selected + background darken($ui-dark-backgroundColor, 5) .snippet-detail color white .group-control-button @@ -166,6 +174,8 @@ body[data-theme="solarized-dark"] background $ui-solarized-dark-borderColor &:hover background darken($ui-solarized-dark-backgroundColor, 5) + .snippet-item-selected + background darken($ui-solarized-dark-backgroundColor, 5) .snippet-detail color white .group-control-button @@ -180,6 +190,8 @@ body[data-theme="monokai"] background $ui-monokai-borderColor &:hover background darken($ui-monokai-backgroundColor, 5) + .snippet-item-selected + background darken($ui-monokai-backgroundColor, 5) .snippet-detail color white .group-control-button From 58354061d808c21b55c9c837c34e98f6f58e02de Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Wed, 27 Jun 2018 19:34:58 +0900 Subject: [PATCH 12/74] set font-family to editor in Preference > Interface --- browser/components/CodeEditor.js | 2 +- browser/lib/consts.js | 10 +++++- browser/main/modals/PreferencesModal/UiTab.js | 32 +++++++++++++++++-- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 91e7683a..b1d63124 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -14,7 +14,7 @@ const { ipcRenderer } = require('electron') CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' -const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] +const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY const buildCMRulers = (rulers, enableRulers) => enableRulers ? rulers.map(ruler => ({column: ruler})) : [] diff --git a/browser/lib/consts.js b/browser/lib/consts.js index ec811007..84b962eb 100644 --- a/browser/lib/consts.js +++ b/browser/lib/consts.js @@ -36,7 +36,15 @@ const consts = { 'Violet Eggplant' ], THEMES: ['default'].concat(themes), - SNIPPET_FILE: snippetFile + SNIPPET_FILE: snippetFile, + DEFAULT_EDITOR_FONT_FAMILY: [ + 'Monaco', + 'Menlo', + 'Ubuntu Mono', + 'Consolas', + 'source-code-pro', + 'monospace' + ] } module.exports = consts diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index ce149f65..55b0050c 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -17,6 +17,8 @@ const OSX = global.process.platform === 'darwin' const electron = require('electron') const ipc = electron.ipcRenderer +const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY + class UiTab extends React.Component { constructor (props) { super(props) @@ -165,6 +167,10 @@ class UiTab extends React.Component { const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};' const enableEditRulersStyle = config.editor.enableRulers ? 'block' : 'none' const customCSS = config.preview.customCSS + let { fontFamily } = config.editor + fontFamily = _.isString(fontFamily) && fontFamily.length > 0 + ? [fontFamily].concat(defaultEditorFontFamily) + : defaultEditorFontFamily return (
    @@ -262,8 +268,16 @@ class UiTab extends React.Component { }) } -
    - (this.codeMirrorInstance = e)} value={codemirrorSampleCode} options={{ lineNumbers: true, readOnly: true, mode: 'javascript', theme: codemirrorTheme }} /> +
    + (this.codeMirrorInstance = e)} + value={codemirrorSampleCode} + options={{ + lineNumbers: true, + readOnly: true, + mode: 'javascript', + theme: codemirrorTheme + }} />
    @@ -596,7 +610,19 @@ class UiTab extends React.Component { type='checkbox' />  {i18n.__('Allow custom CSS for preview')} - this.handleUIChange(e)} ref={e => (this.customCSSCM = e)} value={config.preview.customCSS} options={{ lineNumbers: true, mode: 'css', theme: codemirrorTheme }} /> +
    + this.handleUIChange(e)} + ref={e => (this.customCSSCM = e)} + value={config.preview.customCSS} + options={{ + lineNumbers: true, + mode: 'css', + theme: codemirrorTheme + }} /> +
    From 8f4c92e251fcc0f9189c79408b5656b5ef4f08e0 Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Wed, 27 Jun 2018 19:48:09 +0900 Subject: [PATCH 13/74] extract normalizeEditorFonrFamily --- browser/components/CodeEditor.js | 7 ++----- browser/lib/normalizeEditorFontFamily.js | 9 +++++++++ browser/main/modals/PreferencesModal/UiTab.js | 8 ++------ 3 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 browser/lib/normalizeEditorFontFamily.js diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index b1d63124..de542f49 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -11,10 +11,10 @@ import crypto from 'crypto' import consts from 'browser/lib/consts' import fs from 'fs' const { ipcRenderer } = require('electron') +import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' -const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY const buildCMRulers = (rulers, enableRulers) => enableRulers ? rulers.map(ruler => ({column: ruler})) : [] @@ -495,10 +495,7 @@ export default class CodeEditor extends React.Component { render () { const {className, fontSize} = this.props - let fontFamily = this.props.fontFamily - fontFamily = _.isString(fontFamily) && fontFamily.length > 0 - ? [fontFamily].concat(defaultEditorFontFamily) - : defaultEditorFontFamily + const fontFamily = normalizeEditorFontFamily(this.props.fontFamily) const width = this.props.width return (
    0 + ? [fontFamily].concat(defaultEditorFontFamily) + : defaultEditorFontFamily +} diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index 55b0050c..56d8d19c 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -11,14 +11,13 @@ import 'codemirror-mode-elixir' import _ from 'lodash' import i18n from 'browser/lib/i18n' import { getLanguages } from 'browser/lib/Languages' +import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' const OSX = global.process.platform === 'darwin' const electron = require('electron') const ipc = electron.ipcRenderer -const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY - class UiTab extends React.Component { constructor (props) { super(props) @@ -167,10 +166,7 @@ class UiTab extends React.Component { const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};' const enableEditRulersStyle = config.editor.enableRulers ? 'block' : 'none' const customCSS = config.preview.customCSS - let { fontFamily } = config.editor - fontFamily = _.isString(fontFamily) && fontFamily.length > 0 - ? [fontFamily].concat(defaultEditorFontFamily) - : defaultEditorFontFamily + const fontFamily = normalizeEditorFontFamily(config.editor.fontFamily) return (
    From 95c10a1de7f3e8d75c7c5fd5437326a4be4a461b Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Wed, 27 Jun 2018 20:42:01 +0900 Subject: [PATCH 14/74] add test --- browser/components/CodeEditor.js | 2 +- browser/lib/normalizeEditorFontFamily.js | 4 ++-- tests/lib/normalize-editor-font-family-test.js | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 tests/lib/normalize-editor-font-family-test.js diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index de542f49..a4d2278e 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -506,7 +506,7 @@ export default class CodeEditor extends React.Component { ref='root' tabIndex='-1' style={{ - fontFamily: fontFamily.join(', '), + fontFamily, fontSize: fontSize, width: width }} diff --git a/browser/lib/normalizeEditorFontFamily.js b/browser/lib/normalizeEditorFontFamily.js index 290e452a..a2a2ec31 100644 --- a/browser/lib/normalizeEditorFontFamily.js +++ b/browser/lib/normalizeEditorFontFamily.js @@ -4,6 +4,6 @@ import isString from 'lodash/isString' export default function normalizeEditorFontFamily (fontFamily) { const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY return isString(fontFamily) && fontFamily.length > 0 - ? [fontFamily].concat(defaultEditorFontFamily) - : defaultEditorFontFamily + ? [fontFamily].concat(defaultEditorFontFamily).join(', ') + : defaultEditorFontFamily.join(', ') } diff --git a/tests/lib/normalize-editor-font-family-test.js b/tests/lib/normalize-editor-font-family-test.js new file mode 100644 index 00000000..aacd03ac --- /dev/null +++ b/tests/lib/normalize-editor-font-family-test.js @@ -0,0 +1,16 @@ +/** + * @fileoverview Unit test for browser/lib/normalizeEditorFontFamily + */ +import test from 'ava' +import normalizeEditorFontFamily from '../../browser/lib/normalizeEditorFontFamily' +import consts from '../../browser/lib/consts' +const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY + +test('normalizeEditorFontFamily() should return default font family (string[])', t => { + t.is(normalizeEditorFontFamily(), defaultEditorFontFamily.join(', ')) +}) + +test('normalizeEditorFontFamily(["hoge", "huga"]) should return default font family connected with arg.', t => { + const arg = 'font1, font2' + t.is(normalizeEditorFontFamily(arg), `${arg}, ${defaultEditorFontFamily.join(', ')}`) +}) From 009573584198f50fb484da8e9cd1b485f2e409a0 Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Wed, 27 Jun 2018 20:42:19 +0900 Subject: [PATCH 15/74] fix from eslint --- browser/main/modals/PreferencesModal/UiTab.js | 1 - 1 file changed, 1 deletion(-) diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index 56d8d19c..aa3568e7 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -165,7 +165,6 @@ class UiTab extends React.Component { const { config, codemirrorTheme } = this.state const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};' const enableEditRulersStyle = config.editor.enableRulers ? 'block' : 'none' - const customCSS = config.preview.customCSS const fontFamily = normalizeEditorFontFamily(config.editor.fontFamily) return (
    From cc52cf60dca1672704906f01ec2e9fe0f99db813 Mon Sep 17 00:00:00 2001 From: amedora Date: Thu, 28 Jun 2018 10:57:00 +0900 Subject: [PATCH 16/74] add reference to @susisu/mte-kernel --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 28f1239b..5741287b 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "dependencies": { "@rokt33r/markdown-it-math": "^4.0.1", "@rokt33r/season": "^5.3.0", + "@susisu/mte-kernel": "^2.0.0", "aws-sdk": "^2.48.0", "aws-sdk-mobile-analytics": "^0.9.2", "codemirror": "^5.37.0", From 8a6c86bf656c0829168f954bdc81ab125cae96f7 Mon Sep 17 00:00:00 2001 From: Kelvin Wong Date: Thu, 28 Jun 2018 13:08:39 +0800 Subject: [PATCH 17/74] Add collapsed state for storage The root cause of this issue is that when the folder is clicked, the router pushed the path and the StorageItem component has been refreshed and isOpen has been reset - Add storing collapse state for storage - Add tests - Default as collapsed for fallback fix BoostIo/Boostnote#1979 BoostIo/Boostnote#1911 --- browser/main/SideNav/StorageItem.js | 16 +++++++- browser/main/lib/dataApi/addStorage.js | 6 ++- browser/main/lib/dataApi/index.js | 1 + .../main/lib/dataApi/resolveStorageData.js | 3 +- browser/main/lib/dataApi/toggleStorage.js | 28 ++++++++++++++ browser/main/store.js | 6 +++ tests/dataApi/toggleStorage-test.js | 38 +++++++++++++++++++ 7 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 browser/main/lib/dataApi/toggleStorage.js create mode 100644 tests/dataApi/toggleStorage-test.js diff --git a/browser/main/SideNav/StorageItem.js b/browser/main/SideNav/StorageItem.js index 93e9157f..1c4b4d15 100644 --- a/browser/main/SideNav/StorageItem.js +++ b/browser/main/SideNav/StorageItem.js @@ -21,8 +21,10 @@ class StorageItem extends React.Component { constructor (props) { super(props) + const { storage } = this.props + this.state = { - isOpen: true + isOpen: !!storage.isOpen } } @@ -68,8 +70,18 @@ class StorageItem extends React.Component { } handleToggleButtonClick (e) { + const { storage, dispatch } = this.props + const isOpen = !this.state.isOpen + dataApi.toggleStorage(storage.key, isOpen) + .then((storage) => { + dispatch({ + type: 'EXPAND_STORAGE', + storage: storage, + isOpen: isOpen + }) + }) this.setState({ - isOpen: !this.state.isOpen + isOpen: isOpen }) } diff --git a/browser/main/lib/dataApi/addStorage.js b/browser/main/lib/dataApi/addStorage.js index 630c0bd3..bfd6698a 100644 --- a/browser/main/lib/dataApi/addStorage.js +++ b/browser/main/lib/dataApi/addStorage.js @@ -37,7 +37,8 @@ function addStorage (input) { key, name: input.name, type: input.type, - path: input.path + path: input.path, + isOpen: false } return Promise.resolve(newStorage) @@ -48,7 +49,8 @@ function addStorage (input) { key: newStorage.key, type: newStorage.type, name: newStorage.name, - path: newStorage.path + path: newStorage.path, + isOpen: false }) localStorage.setItem('storages', JSON.stringify(rawStorages)) diff --git a/browser/main/lib/dataApi/index.js b/browser/main/lib/dataApi/index.js index 7c57e016..4e2f0061 100644 --- a/browser/main/lib/dataApi/index.js +++ b/browser/main/lib/dataApi/index.js @@ -1,5 +1,6 @@ const dataApi = { init: require('./init'), + toggleStorage: require('./toggleStorage'), addStorage: require('./addStorage'), renameStorage: require('./renameStorage'), removeStorage: require('./removeStorage'), diff --git a/browser/main/lib/dataApi/resolveStorageData.js b/browser/main/lib/dataApi/resolveStorageData.js index af040c5d..681a102e 100644 --- a/browser/main/lib/dataApi/resolveStorageData.js +++ b/browser/main/lib/dataApi/resolveStorageData.js @@ -8,7 +8,8 @@ function resolveStorageData (storageCache) { key: storageCache.key, name: storageCache.name, type: storageCache.type, - path: storageCache.path + path: storageCache.path, + isOpen: storageCache.isOpen } const boostnoteJSONPath = path.join(storageCache.path, 'boostnote.json') diff --git a/browser/main/lib/dataApi/toggleStorage.js b/browser/main/lib/dataApi/toggleStorage.js new file mode 100644 index 00000000..dbb625c3 --- /dev/null +++ b/browser/main/lib/dataApi/toggleStorage.js @@ -0,0 +1,28 @@ +const _ = require('lodash') +const resolveStorageData = require('./resolveStorageData') + +/** + * @param {String} key + * @param {Boolean} isOpen + * @return {Object} Storage meta data + */ +function toggleStorage (key, isOpen) { + let cachedStorageList + try { + cachedStorageList = JSON.parse(localStorage.getItem('storages')) + if (!_.isArray(cachedStorageList)) throw new Error('invalid storages') + } catch (err) { + console.log('error got') + console.error(err) + return Promise.reject(err) + } + const targetStorage = _.find(cachedStorageList, {key: key}) + if (targetStorage == null) return Promise.reject('Storage') + + targetStorage.isOpen = isOpen + localStorage.setItem('storages', JSON.stringify(cachedStorageList)) + + return resolveStorageData(targetStorage) +} + +module.exports = toggleStorage diff --git a/browser/main/store.js b/browser/main/store.js index 7ea6decb..a1b6b791 100644 --- a/browser/main/store.js +++ b/browser/main/store.js @@ -360,6 +360,12 @@ function data (state = defaultDataMap(), action) { state.storageMap = new Map(state.storageMap) state.storageMap.set(action.storage.key, action.storage) return state + case 'EXPAND_STORAGE': + state = Object.assign({}, state) + state.storageMap = new Map(state.storageMap) + action.storage.isOpen = action.isOpen + state.storageMap.set(action.storage.key, action.storage) + return state } return state } diff --git a/tests/dataApi/toggleStorage-test.js b/tests/dataApi/toggleStorage-test.js new file mode 100644 index 00000000..5169a4f4 --- /dev/null +++ b/tests/dataApi/toggleStorage-test.js @@ -0,0 +1,38 @@ +const test = require('ava') +const toggleStorage = require('browser/main/lib/dataApi/toggleStorage') + +global.document = require('jsdom').jsdom('') +global.window = document.defaultView +global.navigator = window.navigator + +const Storage = require('dom-storage') +const localStorage = window.localStorage = global.localStorage = new Storage(null, { strict: true }) +const path = require('path') +const _ = require('lodash') +const TestDummy = require('../fixtures/TestDummy') +const sander = require('sander') +const os = require('os') + +const storagePath = path.join(os.tmpdir(), 'test/toggle-storage') + +test.beforeEach((t) => { + t.context.storage = TestDummy.dummyStorage(storagePath) + localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) +}) + +test.serial('Toggle a storage location', (t) => { + const storageKey = t.context.storage.cache.key + return Promise.resolve() + .then(function doTest () { + return toggleStorage(storageKey, true) + }) + .then(function assert (data) { + const cachedStorageList = JSON.parse(localStorage.getItem('storages')) + t.true(_.find(cachedStorageList, {key: storageKey}).isOpen === true) + }) +}) + +test.after(function after () { + localStorage.clear() + sander.rimrafSync(storagePath) +}) From 72e3784fa5fe083e4f992ab0a2223dc361a6945f Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Thu, 28 Jun 2018 17:37:36 +0900 Subject: [PATCH 18/74] update codemirror --- package.json | 2 +- yarn.lock | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 28f1239b..99113d6e 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "@rokt33r/season": "^5.3.0", "aws-sdk": "^2.48.0", "aws-sdk-mobile-analytics": "^0.9.2", - "codemirror": "^5.37.0", + "codemirror": "^5.39.0", "codemirror-mode-elixir": "^1.1.1", "electron-config": "^0.2.1", "electron-gh-releases": "^2.0.2", diff --git a/yarn.lock b/yarn.lock index 7bf19d2b..42f51ab9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1727,10 +1727,14 @@ codemirror-mode-elixir@^1.1.1: dependencies: codemirror "^5.20.2" -codemirror@^5.18.2, codemirror@^5.20.2, codemirror@^5.37.0: +codemirror@^5.18.2, codemirror@^5.20.2: version "5.38.0" resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.38.0.tgz#26a9551446e51dbdde36aabe60f72469724fd332" +codemirror@^5.39.0: + version "5.39.0" + resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.39.0.tgz#4654f7d2f7e525e04a62e72d9482348ccb37dce5" + coffee-script@^1.10.0: version "1.12.7" resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53" From b44772441daf6a0560add966d35e05e64cbf50bf Mon Sep 17 00:00:00 2001 From: amedora Date: Thu, 28 Jun 2018 10:38:02 +0900 Subject: [PATCH 19/74] implement TextEditorInterface --- browser/lib/TextEditorInterface.js | 53 ++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 browser/lib/TextEditorInterface.js diff --git a/browser/lib/TextEditorInterface.js b/browser/lib/TextEditorInterface.js new file mode 100644 index 00000000..53ae2337 --- /dev/null +++ b/browser/lib/TextEditorInterface.js @@ -0,0 +1,53 @@ +import { Point } from '@susisu/mte-kernel' + +export default class TextEditorInterface { + constructor (editor) { + this.editor = editor + } + + getCursorPosition () { + const pos = this.editor.getCursor() + return new Point(pos.line, pos.ch) + } + + setCursorPosition (pos) { + this.editor.setCursor({line: pos.row, ch: pos.column}) + } + + setSelectionRange (range) { + this.editor.setSelection({ + anchor: {line: range.start.row, ch: range.start.column}, + head: {line: range.end.row, ch: range.end.column} + }) + } + + getLastRow () { + return this.editor.lastLine() + } + + acceptsTableEdit (row) { + return true + } + + getLine (row) { + return this.editor.getLine(row) + } + + insertLine (row, line) { + this.editor.replaceRange(line, {line: row, ch: 0}) + } + + deleteLine (row) { + this.editor.replaceRange('', {line: row, ch: 0}, {line: row, ch: this.editor.getLine(row).length}) + } + + replaceLines (startRow, endRow, lines) { + endRow-- // because endRow is a first line after a table. + const endRowCh = this.editor.getLine(endRow).length + this.editor.replaceRange(lines, {line: startRow, ch: 0}, {line: endRow, ch: endRowCh}) + } + + transact (func) { + func() + } +} From 7ee12752ecdb7bac7cc9817c5d8d31e8ae51d012 Mon Sep 17 00:00:00 2001 From: amedora Date: Thu, 28 Jun 2018 10:53:56 +0900 Subject: [PATCH 20/74] CodeEditor use TextEditorInterface --- browser/components/CodeEditor.js | 1 + 1 file changed, 1 insertion(+) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 91e7683a..06171660 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -5,6 +5,7 @@ import CodeMirror from 'codemirror' import 'codemirror-mode-elixir' import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement' import convertModeName from 'browser/lib/convertModeName' +import TextEditorInterface from 'browser/lib/TextEditorInterface' import eventEmitter from 'browser/main/lib/eventEmitter' import iconv from 'iconv-lite' import crypto from 'crypto' From c9d05b11172a51147fbfa9c7e02a059520207980 Mon Sep 17 00:00:00 2001 From: amedora Date: Thu, 28 Jun 2018 13:25:37 +0900 Subject: [PATCH 21/74] CodeEditor use TableEditor --- browser/components/CodeEditor.js | 1 + 1 file changed, 1 insertion(+) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 06171660..92c04d1c 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -5,6 +5,7 @@ import CodeMirror from 'codemirror' import 'codemirror-mode-elixir' import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement' import convertModeName from 'browser/lib/convertModeName' +import { options, TableEditor } from '@susisu/mte-kernel' import TextEditorInterface from 'browser/lib/TextEditorInterface' import eventEmitter from 'browser/main/lib/eventEmitter' import iconv from 'iconv-lite' From f0941f47dd67b0a0c1fe70a77b4a0c3decd2ff04 Mon Sep 17 00:00:00 2001 From: amedora Date: Thu, 28 Jun 2018 13:29:24 +0900 Subject: [PATCH 22/74] create TableEditor when CodeEditor mounted --- browser/components/CodeEditor.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 92c04d1c..29d553f8 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -184,6 +184,8 @@ export default class CodeEditor extends React.Component { CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor) CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor) CodeMirror.Vim.map('ZZ', ':q', 'normal') + + this.tableEditor = new TableEditor(new TextEditorInterface(this.editor)) } expandSnippet (line, cursor, cm, snippets) { From 7bacd6f8f0a17d0417b6d71e9ccc71f61d02f31c Mon Sep 17 00:00:00 2001 From: amedora Date: Thu, 28 Jun 2018 13:33:48 +0900 Subject: [PATCH 23/74] CodeEditor can handle 'code:format-table' event --- browser/components/CodeEditor.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 29d553f8..5eda3877 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -50,6 +50,8 @@ export default class CodeEditor extends React.Component { } this.searchHandler = (e, msg) => this.handleSearch(msg) this.searchState = null + + this.formatTable = () => this.handleFormatTable() } handleSearch (msg) { @@ -83,6 +85,10 @@ export default class CodeEditor extends React.Component { }) } + handleFormatTable () { + this.tableEditor.formatAll(options({textWidthOptions: {}})) + } + componentDidMount () { const { rulers, enableRulers } = this.props const expandSnippet = this.expandSnippet.bind(this) @@ -186,6 +192,7 @@ export default class CodeEditor extends React.Component { CodeMirror.Vim.map('ZZ', ':q', 'normal') this.tableEditor = new TableEditor(new TextEditorInterface(this.editor)) + eventEmitter.on('code:format-table', this.formatTable) } expandSnippet (line, cursor, cm, snippets) { @@ -268,6 +275,8 @@ export default class CodeEditor extends React.Component { this.editor.off('scroll', this.scrollHandler) const editorTheme = document.getElementById('editorTheme') editorTheme.removeEventListener('load', this.loadStyleHandler) + + eventEmitter.off('code:format-table', this.formatTable) } componentDidUpdate (prevProps, prevState) { From 82db986bd7d5a7e2c3ec19c6fe393578f2f464d8 Mon Sep 17 00:00:00 2001 From: amedora Date: Tue, 26 Jun 2018 14:48:07 +0900 Subject: [PATCH 24/74] add 'Format Table' in the File menu --- lib/main-menu.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/main-menu.js b/lib/main-menu.js index 9345bd67..c639da0f 100644 --- a/lib/main-menu.js +++ b/lib/main-menu.js @@ -136,6 +136,15 @@ const file = { { type: 'separator' }, + { + label: 'Format Table', + click () { + mainWindow.webContents.send('code:format-table') + } + }, + { + type: 'separator' + }, { label: 'Print', accelerator: 'CommandOrControl+P', From ddd339851b4a418d08c7bdd626ea3af9984f790f Mon Sep 17 00:00:00 2001 From: Kelvin Wong Date: Fri, 29 Jun 2018 10:58:11 +0800 Subject: [PATCH 25/74] Fix code style --- browser/main/SideNav/StorageItem.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/main/SideNav/StorageItem.js b/browser/main/SideNav/StorageItem.js index 1c4b4d15..8cb0e510 100644 --- a/browser/main/SideNav/StorageItem.js +++ b/browser/main/SideNav/StorageItem.js @@ -76,8 +76,8 @@ class StorageItem extends React.Component { .then((storage) => { dispatch({ type: 'EXPAND_STORAGE', - storage: storage, - isOpen: isOpen + storage, + isOpen }) }) this.setState({ From ae493cbd0eae962ac0eb0ccbf462ef4f87436e7f Mon Sep 17 00:00:00 2001 From: Junyoung Choi Date: Sat, 30 Jun 2018 00:51:15 +0900 Subject: [PATCH 26/74] v0.11.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 28f1239b..1c2cb91d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "boost", "productName": "Boostnote", - "version": "0.11.6", + "version": "0.11.7", "main": "index.js", "description": "Boostnote", "license": "GPL-3.0", From 0a42b0f61ff74037a2536a72daaf9fc6265d887f Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Sat, 30 Jun 2018 16:08:13 +0900 Subject: [PATCH 27/74] use context.popup on NoteList --- browser/main/NoteList/index.js | 37 ++++++++++++++++------------------ 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index 3626130d..bcba23cc 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -21,6 +21,7 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import Markdown from '../../lib/markdown' import i18n from 'browser/lib/i18n' import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' +import context from 'browser/lib/context' const { remote } = require('electron') const { Menu, MenuItem, dialog } = remote @@ -491,55 +492,51 @@ class NoteList extends React.Component { const updateLabel = i18n.__('Update Blog') const openBlogLabel = i18n.__('Open Blog') - const menu = new Menu() + const templates = [] if (location.pathname.match(/\/trash/)) { - menu.append(new MenuItem({ + templates.push({ label: restoreNote, click: this.restoreNote - })) - menu.append(new MenuItem({ + }, { label: deleteLabel, click: this.deleteNote - })) + }) } else { if (!location.pathname.match(/\/starred/)) { - menu.append(new MenuItem({ + templates.push({ label: pinLabel, click: this.pinToTop - })) + }) } - menu.append(new MenuItem({ + templates.push({ label: deleteLabel, click: this.deleteNote - })) - menu.append(new MenuItem({ + }, { label: cloneNote, click: this.cloneNote.bind(this) - })) - menu.append(new MenuItem({ + }, { label: copyNoteLink, click: this.copyNoteLink(note) - })) + }) if (note.type === 'MARKDOWN_NOTE') { if (note.blog && note.blog.blogLink && note.blog.blogId) { - menu.append(new MenuItem({ + templates.push({ label: updateLabel, click: this.publishMarkdown.bind(this) - })) - menu.append(new MenuItem({ + }, { label: openBlogLabel, click: () => this.openBlog.bind(this)(note) - })) + }) } else { - menu.append(new MenuItem({ + templates.push({ label: publishLabel, click: this.publishMarkdown.bind(this) - })) + }) } } } - menu.popup() + context.popup(templates) } updateSelectedNotes (updateFunc, cleanSelection = true) { From 64407e5ca63797cf063110e4d21bae62b5228ffb Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Sat, 30 Jun 2018 16:12:23 +0900 Subject: [PATCH 28/74] use context.popup on SnippetNoteDetai; --- browser/main/Detail/SnippetNoteDetail.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index c65f1425..75be4798 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -32,7 +32,7 @@ import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' const electron = require('electron') const { remote } = electron -const { Menu, MenuItem, dialog } = remote +const { dialog } = remote class SnippetNoteDetail extends React.Component { constructor (props) { @@ -451,14 +451,14 @@ class SnippetNoteDetail extends React.Component { } handleModeButtonClick (e, index) { - const menu = new Menu() + const templetes = [] CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => { - menu.append(new MenuItem({ + templetes.push({ label: mode.name, click: (e) => this.handleModeOptionClick(index, mode.name)(e) - })) + }) }) - menu.popup(remote.getCurrentWindow()) + context.popup(templetes) } handleIndentTypeButtonClick (e) { From f6afc756dc056f982fb65748e9c3d3286380af8d Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Sat, 30 Jun 2018 16:16:18 +0900 Subject: [PATCH 29/74] use context.popup on SnippetList --- browser/main/modals/PreferencesModal/SnippetList.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/browser/main/modals/PreferencesModal/SnippetList.js b/browser/main/modals/PreferencesModal/SnippetList.js index 3cf28cf6..fa18cda0 100644 --- a/browser/main/modals/PreferencesModal/SnippetList.js +++ b/browser/main/modals/PreferencesModal/SnippetList.js @@ -4,8 +4,7 @@ import CSSModules from 'browser/lib/CSSModules' import dataApi from 'browser/main/lib/dataApi' import i18n from 'browser/lib/i18n' import eventEmitter from 'browser/main/lib/eventEmitter' -const { remote } = require('electron') -const { Menu, MenuItem } = remote +import context from 'browser/lib/context' class SnippetList extends React.Component { constructor (props) { @@ -25,14 +24,10 @@ class SnippetList extends React.Component { } handleSnippetContextMenu (snippet) { - const menu = new Menu() - menu.append(new MenuItem({ + context.popup([{ label: i18n.__('Delete snippet'), - click: () => { - this.deleteSnippet(snippet) - } - })) - menu.popup() + click: () => this.deleteSnippet(snippet) + }]) } deleteSnippet (snippet) { From 294c3f10ab21a1616e75b1103ddb91a9ab367637 Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Sat, 30 Jun 2018 16:21:35 +0900 Subject: [PATCH 30/74] use context.popup on StorageItem --- browser/main/SideNav/StorageItem.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/browser/main/SideNav/StorageItem.js b/browser/main/SideNav/StorageItem.js index 93e9157f..bf64b030 100644 --- a/browser/main/SideNav/StorageItem.js +++ b/browser/main/SideNav/StorageItem.js @@ -11,9 +11,10 @@ import StorageItemChild from 'browser/components/StorageItem' import _ from 'lodash' import { SortableElement } from 'react-sortable-hoc' import i18n from 'browser/lib/i18n' +import context from 'browser/lib/context' const { remote } = require('electron') -const { Menu, dialog } = remote +const { dialog } = remote const escapeStringRegexp = require('escape-string-regexp') const path = require('path') @@ -27,7 +28,7 @@ class StorageItem extends React.Component { } handleHeaderContextMenu (e) { - const menu = Menu.buildFromTemplate([ + context.popup([ { label: i18n.__('Add Folder'), click: (e) => this.handleAddFolderButtonClick(e) @@ -40,8 +41,6 @@ class StorageItem extends React.Component { click: (e) => this.handleUnlinkStorageClick(e) } ]) - - menu.popup() } handleUnlinkStorageClick (e) { @@ -94,7 +93,7 @@ class StorageItem extends React.Component { } handleFolderButtonContextMenu (e, folder) { - const menu = Menu.buildFromTemplate([ + context.popup([ { label: i18n.__('Rename Folder'), click: (e) => this.handleRenameFolderClick(e, folder) @@ -123,8 +122,6 @@ class StorageItem extends React.Component { click: (e) => this.handleFolderDeleteClick(e, folder) } ]) - - menu.popup() } handleRenameFolderClick (e, folder) { From 47845fd4e3b9ef59f659d320d33b3eff55380f34 Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Sat, 30 Jun 2018 16:23:51 +0900 Subject: [PATCH 31/74] use context.popup on SideNav --- 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 67adf700..d8561b7e 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -19,6 +19,7 @@ import ListButton from './ListButton' import TagButton from './TagButton' import {SortableContainer} from 'react-sortable-hoc' import i18n from 'browser/lib/i18n' +import context from 'browser/lib/context' class SideNav extends React.Component { // TODO: should not use electron stuff v0.7 @@ -254,10 +255,9 @@ class SideNav extends React.Component { handleFilterButtonContextMenu (event) { const { data } = this.props const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey)) - const menu = Menu.buildFromTemplate([ + context.popup([ { label: i18n.__('Empty Trash'), click: () => this.emptyTrash(trashedNotes) } ]) - menu.popup() } render () { From 1038e86196d1d553b1c20c3748791f65ab757750 Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Sat, 30 Jun 2018 16:25:44 +0900 Subject: [PATCH 32/74] use context.popup on StatusBar --- browser/main/StatusBar/index.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/browser/main/StatusBar/index.js b/browser/main/StatusBar/index.js index e5f5ae1a..8b48e3d3 100644 --- a/browser/main/StatusBar/index.js +++ b/browser/main/StatusBar/index.js @@ -4,10 +4,11 @@ import CSSModules from 'browser/lib/CSSModules' import styles from './StatusBar.styl' import ZoomManager from 'browser/main/lib/ZoomManager' import i18n from 'browser/lib/i18n' +import context from 'browser/lib/context' const electron = require('electron') const { remote, ipcRenderer } = electron -const { Menu, MenuItem, dialog } = remote +const { dialog } = remote const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0] @@ -26,16 +27,16 @@ class StatusBar extends React.Component { } handleZoomButtonClick (e) { - const menu = new Menu() + const templates = [] zoomOptions.forEach((zoom) => { - menu.append(new MenuItem({ + templates.push({ label: Math.floor(zoom * 100) + '%', click: () => this.handleZoomMenuItemClick(zoom) - })) + }) }) - menu.popup(remote.getCurrentWindow()) + context.popup(templates) } handleZoomMenuItemClick (zoomFactor) { From d8aad65b241441349c5d7121524718fcbfc3c162 Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Sat, 30 Jun 2018 16:28:17 +0900 Subject: [PATCH 33/74] fix from eslint --- browser/main/NoteList/index.js | 2 +- browser/main/SideNav/index.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index bcba23cc..eeb16a5f 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -24,7 +24,7 @@ import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' import context from 'browser/lib/context' const { remote } = require('electron') -const { Menu, MenuItem, dialog } = remote +const { dialog } = remote const WP_POST_PATH = '/wp/v2/posts' function sortByCreatedAt (a, b) { diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index d8561b7e..c4fa417b 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -1,8 +1,6 @@ import PropTypes from 'prop-types' import React from 'react' import CSSModules from 'browser/lib/CSSModules' -const { remote } = require('electron') -const { Menu } = remote import dataApi from 'browser/main/lib/dataApi' import styles from './SideNav.styl' import { openModal } from 'browser/main/lib/modal' From cefe883025f3668d51b92d3d9e4323d091e70372 Mon Sep 17 00:00:00 2001 From: Kazz Yokomizo Date: Sun, 1 Jul 2018 21:59:08 +0900 Subject: [PATCH 34/74] Update slack invitation url --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 40866e46..6c68efaf 100644 --- a/readme.md +++ b/readme.md @@ -25,7 +25,7 @@ Boostnote is an open source project. It's an independent project with its ongoin ## Community - [Facebook Group](https://www.facebook.com/groups/boostnote/) - [Twitter](https://twitter.com/boostnoteapp) -- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzcwNDU3NDU3ODI0LTU1ZDgwZDNiZTNmN2RhOTY4OTM5ODY0ODUzMTRiNmQ0ZDMzZDRiYzg2YmQ5ZDYzZTQxYjMxYzBlNTM4NjcyYjM) +- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzkxOTk4ODkyNzc0LThkNmMzY2VlZjVhYTNiYjE5YjQyZGVjNTJlYTY1OGMyZTFjNGU5YTUyYjUzOWZhYTU4OTVlNDYyNDFjYWMzNDM) - [Blog](https://boostlog.io/tags/boostnote) - [Reddit](https://www.reddit.com/r/Boostnote/) From d2b2e76a6a4f226662de264b02aaa3c9442902a0 Mon Sep 17 00:00:00 2001 From: JianXu Date: Mon, 2 Jul 2018 21:43:46 +0800 Subject: [PATCH 35/74] Fixed 'Focus Search' shortcut --- browser/main/TopBar/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/browser/main/TopBar/index.js b/browser/main/TopBar/index.js index ae4d9664..a5687ecb 100644 --- a/browser/main/TopBar/index.js +++ b/browser/main/TopBar/index.js @@ -156,8 +156,7 @@ class TopBar extends React.Component { if (this.state.isSearching) { el.blur() } else { - el.focus() - el.setSelectionRange(0, el.value.length) + el.select() } } From 4f79f52524b23cd81d4a3ff5abd75d507d7c6a6e Mon Sep 17 00:00:00 2001 From: yamash723 Date: Tue, 3 Jul 2018 10:03:31 +0900 Subject: [PATCH 36/74] Fix search value for html export path --- browser/components/MarkdownPreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 124bd8c3..297266c3 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -225,7 +225,7 @@ export default class MarkdownPreview extends React.Component { const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath) files.forEach((file) => { - file = file.replace('file://', '') + file = file.replace('file:///', '') exportTasks.push({ src: file, dst: 'css' From 6bc42c564d62540d8b6b6f5404e0026db1ee844b Mon Sep 17 00:00:00 2001 From: JianXu Date: Tue, 3 Jul 2018 12:58:45 +0800 Subject: [PATCH 37/74] Add chartjs --- browser/components/MarkdownPreview.js | 18 ++++++++++++++++- browser/components/markdown.styl | 4 ++-- browser/lib/markdown.js | 3 +++ package.json | 1 + yarn.lock | 28 +++++++++++++++++++++++++++ 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 124bd8c3..a3cc8dc2 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -8,6 +8,7 @@ import consts from 'browser/lib/consts' import Raphael from 'raphael' import flowchart from 'flowchart' import SequenceDiagram from 'js-sequence-diagrams' +import Chart from 'chart.js' import eventEmitter from 'browser/main/lib/eventEmitter' import htmlTextHelper from 'browser/lib/htmlTextHelper' import convertModeName from 'browser/lib/convertModeName' @@ -412,7 +413,7 @@ export default class MarkdownPreview extends React.Component { value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock)) }) } - let renderedHTML = this.markdown.render(value) + const renderedHTML = this.markdown.render(value) attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey) this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath) @@ -496,6 +497,21 @@ export default class MarkdownPreview extends React.Component { el.innerHTML = 'Sequence diagram parse error: ' + e.message } }) + + _.forEach(this.refs.root.contentWindow.document.querySelectorAll('.chart'), (el) => { + try { + const chartConfig = JSON.parse(el.innerHTML) + el.innerHTML = '' + var canvas = document.createElement('canvas') + el.appendChild(canvas) + /* eslint-disable no-new */ + new Chart(canvas, chartConfig) + } catch (e) { + console.error(e) + el.className = 'chart-error' + el.innerHTML = 'chartjs diagram parse error: ' + e.message + } + }) } focus () { diff --git a/browser/components/markdown.styl b/browser/components/markdown.styl index cf94bb8e..03503231 100644 --- a/browser/components/markdown.styl +++ b/browser/components/markdown.styl @@ -68,7 +68,7 @@ body padding 5px margin -5px border-radius 5px - .flowchart-error, .sequence-error + .flowchart-error, .sequence-error .chart-error background-color errorBackgroundColor color errorTextColor padding 5px @@ -213,7 +213,7 @@ pre margin 0 0 1em display flex line-height 1.4em - &.flowchart, &.sequence + &.flowchart, &.sequence, &.chart display flex justify-content center background-color white diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 4dafa4a3..a25bdf76 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -40,6 +40,9 @@ class Markdown { if (langType === 'sequence') { return `
    ${str}
    ` } + if (langType === 'chart') { + return `
    ${str}
    ` + } return '
    ' +
               '' + fileName + '' +
               createGutter(str, firstLineNumber) +
    diff --git a/package.json b/package.json
    index a1d17e57..7dce220b 100644
    --- a/package.json
    +++ b/package.json
    @@ -53,6 +53,7 @@
         "@rokt33r/season": "^5.3.0",
         "aws-sdk": "^2.48.0",
         "aws-sdk-mobile-analytics": "^0.9.2",
    +    "chart.js": "^2.7.2",
         "codemirror": "^5.39.0",
         "codemirror-mode-elixir": "^1.1.1",
         "electron-config": "^0.2.1",
    diff --git a/yarn.lock b/yarn.lock
    index 42f51ab9..c9e13307 100644
    --- a/yarn.lock
    +++ b/yarn.lock
    @@ -1574,6 +1574,26 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.1:
         escape-string-regexp "^1.0.5"
         supports-color "^5.3.0"
     
    +chart.js@^2.7.2:
    +  version "2.7.2"
    +  resolved "http://registry.npm.taobao.org/chart.js/download/chart.js-2.7.2.tgz#3c9fde4dc5b95608211bdefeda7e5d33dffa5714"
    +  dependencies:
    +    chartjs-color "^2.1.0"
    +    moment "^2.10.2"
    +
    +chartjs-color-string@^0.5.0:
    +  version "0.5.0"
    +  resolved "http://registry.npm.taobao.org/chartjs-color-string/download/chartjs-color-string-0.5.0.tgz#8d3752d8581d86687c35bfe2cb80ac5213ceb8c1"
    +  dependencies:
    +    color-name "^1.0.0"
    +
    +chartjs-color@^2.1.0:
    +  version "2.2.0"
    +  resolved "http://registry.npm.taobao.org/chartjs-color/download/chartjs-color-2.2.0.tgz#84a2fb755787ed85c39dd6dd8c7b1d88429baeae"
    +  dependencies:
    +    chartjs-color-string "^0.5.0"
    +    color-convert "^0.5.3"
    +
     chokidar@^1.0.0, chokidar@^1.4.2:
       version "1.7.0"
       resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
    @@ -1750,6 +1770,10 @@ collection-visit@^1.0.0:
         map-visit "^1.0.0"
         object-visit "^1.0.0"
     
    +color-convert@^0.5.3:
    +  version "0.5.3"
    +  resolved "http://registry.npm.taobao.org/color-convert/download/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd"
    +
     color-convert@^1.3.0, color-convert@^1.9.0:
       version "1.9.1"
       resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed"
    @@ -5696,6 +5720,10 @@ mock-require@^3.0.1:
         get-caller-file "^1.0.2"
         normalize-path "^2.1.1"
     
    +moment@^2.10.2:
    +  version "2.22.2"
    +  resolved "http://registry.npm.taobao.org/moment/download/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
    +
     moment@^2.10.3:
       version "2.22.1"
       resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad"
    
    From 883b4c4c26a5602bed148c3d2d8a4e8e9d12229b Mon Sep 17 00:00:00 2001
    From: Max Buranbaev 
    Date: Tue, 3 Jul 2018 10:27:13 +0500
    Subject: [PATCH 38/74] adding timeout on creating a note
    
    ---
     browser/main/modals/NewNoteModal.js | 86 +++++++++++++++++------------
     1 file changed, 52 insertions(+), 34 deletions(-)
    
    diff --git a/browser/main/modals/NewNoteModal.js b/browser/main/modals/NewNoteModal.js
    index 185004e7..59c6d80a 100644
    --- a/browser/main/modals/NewNoteModal.js
    +++ b/browser/main/modals/NewNoteModal.js
    @@ -12,8 +12,7 @@ class NewNoteModal extends React.Component {
       constructor (props) {
         super(props)
     
    -    this.state = {
    -    }
    +    this.state = {}
       }
     
       componentDidMount () {
    @@ -35,19 +34,20 @@ class NewNoteModal extends React.Component {
             title: '',
             content: ''
           })
    -      .then((note) => {
    +      .then(note => {
             const noteHash = note.key
             dispatch({
               type: 'UPDATE_NOTE',
               note: note
             })
    +
             hashHistory.push({
               pathname: location.pathname,
    -          query: {key: noteHash}
    +          query: { key: noteHash }
             })
             ee.emit('list:jump', noteHash)
             ee.emit('detail:focus')
    -        this.props.close()
    +        setTimeout(this.props.close, 200)
           })
       }
     
    @@ -69,13 +69,15 @@ class NewNoteModal extends React.Component {
             folder: folder,
             title: '',
             description: '',
    -        snippets: [{
    -          name: '',
    -          mode: 'text',
    -          content: ''
    -        }]
    +        snippets: [
    +          {
    +            name: '',
    +            mode: 'text',
    +            content: ''
    +          }
    +        ]
           })
    -      .then((note) => {
    +      .then(note => {
             const noteHash = note.key
             dispatch({
               type: 'UPDATE_NOTE',
    @@ -83,7 +85,7 @@ class NewNoteModal extends React.Component {
             })
             hashHistory.push({
               pathname: location.pathname,
    -          query: {key: noteHash}
    +          query: { key: noteHash }
             })
             ee.emit('list:jump', noteHash)
             ee.emit('detail:focus')
    @@ -106,49 +108,65 @@ class NewNoteModal extends React.Component {
     
       render () {
         return (
    -      
    this.handleKeyDown(e)} + onKeyDown={e => this.handleKeyDown(e)} >
    {i18n.__('Make a note')}
    - this.handleCloseButtonClick(e)} /> + this.handleCloseButtonClick(e)} + />
    - -
    -
    {i18n.__('Tab to switch format')}
    +
    + {i18n.__('Tab to switch format')} +
    ) } } -NewNoteModal.propTypes = { -} +NewNoteModal.propTypes = {} export default CSSModules(NewNoteModal, styles) From 3c8337cf544eb7c63e14bc809cddb1bfa05ed39a Mon Sep 17 00:00:00 2001 From: Max Buranbaev Date: Tue, 3 Jul 2018 10:29:22 +0500 Subject: [PATCH 39/74] adding timeout one creating a snippet --- browser/main/modals/NewNoteModal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/main/modals/NewNoteModal.js b/browser/main/modals/NewNoteModal.js index 59c6d80a..b748587c 100644 --- a/browser/main/modals/NewNoteModal.js +++ b/browser/main/modals/NewNoteModal.js @@ -89,7 +89,7 @@ class NewNoteModal extends React.Component { }) ee.emit('list:jump', noteHash) ee.emit('detail:focus') - this.props.close() + setTimeout(this.props.close, 200) }) } From 866a0e75347892e5860bcc5892902cf53e1854cb Mon Sep 17 00:00:00 2001 From: JianXu Date: Tue, 3 Jul 2018 15:10:08 +0800 Subject: [PATCH 40/74] Add mermaid support --- browser/components/MarkdownPreview.js | 5 + browser/components/render/MermaidRender.js | 28 ++ browser/lib/markdown.js | 3 + package.json | 1 + yarn.lock | 293 ++++++++++++++++++++- 5 files changed, 324 insertions(+), 6 deletions(-) create mode 100644 browser/components/render/MermaidRender.js diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 124bd8c3..e148f576 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -7,6 +7,7 @@ import 'codemirror-mode-elixir' import consts from 'browser/lib/consts' import Raphael from 'raphael' import flowchart from 'flowchart' +import mermaidRender from './render/MermaidRender' import SequenceDiagram from 'js-sequence-diagrams' import eventEmitter from 'browser/main/lib/eventEmitter' import htmlTextHelper from 'browser/lib/htmlTextHelper' @@ -496,6 +497,10 @@ export default class MarkdownPreview extends React.Component { el.innerHTML = 'Sequence diagram parse error: ' + e.message } }) + + _.forEach(this.refs.root.contentWindow.document.querySelectorAll('.mermaid'), (el) => { + mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML)) + }) } focus () { diff --git a/browser/components/render/MermaidRender.js b/browser/components/render/MermaidRender.js new file mode 100644 index 00000000..e057cf34 --- /dev/null +++ b/browser/components/render/MermaidRender.js @@ -0,0 +1,28 @@ +import mermaidAPI from 'mermaid' + +function getRandomInt (min, max) { + return Math.floor(Math.random() * (max - min)) + min +} + +function getId () { + var pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' + var id = 'm-' + for (var i = 0; i < 7; i++) { + id += pool[getRandomInt(0, 16)] + } + return id +} + +function render (element, content) { + try { + mermaidAPI.render(getId(), content, (svgGraph) => { + element.innerHTML = svgGraph + }) + } catch (e) { + console.error(e) + element.className = 'mermaid-error' + element.innerHTML = 'mermaid diagram parse error: ' + e.message + } +} + +export default render diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 4dafa4a3..48532538 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -40,6 +40,9 @@ class Markdown { if (langType === 'sequence') { return `
    ${str}
    ` } + if (langType === 'mermaid') { + return `
    ${str}
    ` + } return '
    ' +
               '' + fileName + '' +
               createGutter(str, firstLineNumber) +
    diff --git a/package.json b/package.json
    index a1d17e57..42839940 100644
    --- a/package.json
    +++ b/package.json
    @@ -81,6 +81,7 @@
         "markdown-it-plantuml": "^1.1.0",
         "markdown-it-smartarrows": "^1.0.1",
         "mdurl": "^1.0.1",
    +    "mermaid": "^8.0.0-rc.8",
         "moment": "^2.10.3",
         "mousetrap": "^1.6.1",
         "mousetrap-global-bind": "^1.1.0",
    diff --git a/yarn.lock b/yarn.lock
    index 42f51ab9..10d28022 100644
    --- a/yarn.lock
    +++ b/yarn.lock
    @@ -1806,6 +1806,10 @@ combined-stream@~0.0.4, combined-stream@~0.0.5:
       dependencies:
         delayed-stream "0.0.5"
     
    +commander@2:
    +  version "2.16.0"
    +  resolved "http://registry.npm.taobao.org/commander/download/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50"
    +
     commander@2.3.0:
       version "2.3.0"
       resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873"
    @@ -2146,12 +2150,250 @@ currently-unhandled@^0.4.1:
       dependencies:
         array-find-index "^1.0.1"
     
    +d3-array@1, d3-array@1.2.1, d3-array@^1.2.0:
    +  version "1.2.1"
    +  resolved "http://registry.npm.taobao.org/d3-array/download/d3-array-1.2.1.tgz#d1ca33de2f6ac31efadb8e050a021d7e2396d5dc"
    +
    +d3-axis@1.0.8:
    +  version "1.0.8"
    +  resolved "http://registry.npm.taobao.org/d3-axis/download/d3-axis-1.0.8.tgz#31a705a0b535e65759de14173a31933137f18efa"
    +
    +d3-brush@1.0.4:
    +  version "1.0.4"
    +  resolved "http://registry.npm.taobao.org/d3-brush/download/d3-brush-1.0.4.tgz#00c2f238019f24f6c0a194a26d41a1530ffe7bc4"
    +  dependencies:
    +    d3-dispatch "1"
    +    d3-drag "1"
    +    d3-interpolate "1"
    +    d3-selection "1"
    +    d3-transition "1"
    +
    +d3-chord@1.0.4:
    +  version "1.0.4"
    +  resolved "http://registry.npm.taobao.org/d3-chord/download/d3-chord-1.0.4.tgz#7dec4f0ba886f713fe111c45f763414f6f74ca2c"
    +  dependencies:
    +    d3-array "1"
    +    d3-path "1"
    +
    +d3-collection@1, d3-collection@1.0.4:
    +  version "1.0.4"
    +  resolved "http://registry.npm.taobao.org/d3-collection/download/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2"
    +
    +d3-color@1:
    +  version "1.2.0"
    +  resolved "http://registry.npm.taobao.org/d3-color/download/d3-color-1.2.0.tgz#d1ea19db5859c86854586276ec892cf93148459a"
    +
    +d3-color@1.0.3:
    +  version "1.0.3"
    +  resolved "http://registry.npm.taobao.org/d3-color/download/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b"
    +
    +d3-dispatch@1, d3-dispatch@1.0.3:
    +  version "1.0.3"
    +  resolved "http://registry.npm.taobao.org/d3-dispatch/download/d3-dispatch-1.0.3.tgz#46e1491eaa9b58c358fce5be4e8bed626e7871f8"
    +
    +d3-drag@1, d3-drag@1.2.1:
    +  version "1.2.1"
    +  resolved "http://registry.npm.taobao.org/d3-drag/download/d3-drag-1.2.1.tgz#df8dd4c502fb490fc7462046a8ad98a5c479282d"
    +  dependencies:
    +    d3-dispatch "1"
    +    d3-selection "1"
    +
    +d3-dsv@1, d3-dsv@1.0.8:
    +  version "1.0.8"
    +  resolved "http://registry.npm.taobao.org/d3-dsv/download/d3-dsv-1.0.8.tgz#907e240d57b386618dc56468bacfe76bf19764ae"
    +  dependencies:
    +    commander "2"
    +    iconv-lite "0.4"
    +    rw "1"
    +
    +d3-ease@1, d3-ease@1.0.3:
    +  version "1.0.3"
    +  resolved "http://registry.npm.taobao.org/d3-ease/download/d3-ease-1.0.3.tgz#68bfbc349338a380c44d8acc4fbc3304aa2d8c0e"
    +
    +d3-force@1.1.0:
    +  version "1.1.0"
    +  resolved "http://registry.npm.taobao.org/d3-force/download/d3-force-1.1.0.tgz#cebf3c694f1078fcc3d4daf8e567b2fbd70d4ea3"
    +  dependencies:
    +    d3-collection "1"
    +    d3-dispatch "1"
    +    d3-quadtree "1"
    +    d3-timer "1"
    +
    +d3-format@1:
    +  version "1.3.0"
    +  resolved "http://registry.npm.taobao.org/d3-format/download/d3-format-1.3.0.tgz#a3ac44269a2011cdb87c7b5693040c18cddfff11"
    +
    +d3-format@1.2.2:
    +  version "1.2.2"
    +  resolved "http://registry.npm.taobao.org/d3-format/download/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a"
    +
    +d3-geo@1.9.1:
    +  version "1.9.1"
    +  resolved "http://registry.npm.taobao.org/d3-geo/download/d3-geo-1.9.1.tgz#157e3b0f917379d0f73bebfff3be537f49fa7356"
    +  dependencies:
    +    d3-array "1"
    +
    +d3-hierarchy@1.1.5:
    +  version "1.1.5"
    +  resolved "http://registry.npm.taobao.org/d3-hierarchy/download/d3-hierarchy-1.1.5.tgz#a1c845c42f84a206bcf1c01c01098ea4ddaa7a26"
    +
    +d3-interpolate@1:
    +  version "1.2.0"
    +  resolved "http://registry.npm.taobao.org/d3-interpolate/download/d3-interpolate-1.2.0.tgz#40d81bd8e959ff021c5ea7545bc79b8d22331c41"
    +  dependencies:
    +    d3-color "1"
    +
    +d3-interpolate@1.1.6:
    +  version "1.1.6"
    +  resolved "http://registry.npm.taobao.org/d3-interpolate/download/d3-interpolate-1.1.6.tgz#2cf395ae2381804df08aa1bf766b7f97b5f68fb6"
    +  dependencies:
    +    d3-color "1"
    +
    +d3-path@1, d3-path@1.0.5:
    +  version "1.0.5"
    +  resolved "http://registry.npm.taobao.org/d3-path/download/d3-path-1.0.5.tgz#241eb1849bd9e9e8021c0d0a799f8a0e8e441764"
    +
    +d3-polygon@1.0.3:
    +  version "1.0.3"
    +  resolved "http://registry.npm.taobao.org/d3-polygon/download/d3-polygon-1.0.3.tgz#16888e9026460933f2b179652ad378224d382c62"
    +
    +d3-quadtree@1, d3-quadtree@1.0.3:
    +  version "1.0.3"
    +  resolved "http://registry.npm.taobao.org/d3-quadtree/download/d3-quadtree-1.0.3.tgz#ac7987e3e23fe805a990f28e1b50d38fcb822438"
    +
    +d3-queue@3.0.7:
    +  version "3.0.7"
    +  resolved "http://registry.npm.taobao.org/d3-queue/download/d3-queue-3.0.7.tgz#c93a2e54b417c0959129d7d73f6cf7d4292e7618"
    +
    +d3-random@1.1.0:
    +  version "1.1.0"
    +  resolved "http://registry.npm.taobao.org/d3-random/download/d3-random-1.1.0.tgz#6642e506c6fa3a648595d2b2469788a8d12529d3"
    +
    +d3-request@1.0.6:
    +  version "1.0.6"
    +  resolved "http://registry.npm.taobao.org/d3-request/download/d3-request-1.0.6.tgz#a1044a9ef4ec28c824171c9379fae6d79474b19f"
    +  dependencies:
    +    d3-collection "1"
    +    d3-dispatch "1"
    +    d3-dsv "1"
    +    xmlhttprequest "1"
    +
    +d3-scale@1.0.7:
    +  version "1.0.7"
    +  resolved "http://registry.npm.taobao.org/d3-scale/download/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d"
    +  dependencies:
    +    d3-array "^1.2.0"
    +    d3-collection "1"
    +    d3-color "1"
    +    d3-format "1"
    +    d3-interpolate "1"
    +    d3-time "1"
    +    d3-time-format "2"
    +
    +d3-selection@1, d3-selection@1.3.0, d3-selection@^1.1.0:
    +  version "1.3.0"
    +  resolved "http://registry.npm.taobao.org/d3-selection/download/d3-selection-1.3.0.tgz#d53772382d3dc4f7507bfb28bcd2d6aed2a0ad6d"
    +
    +d3-shape@1.2.0:
    +  version "1.2.0"
    +  resolved "http://registry.npm.taobao.org/d3-shape/download/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777"
    +  dependencies:
    +    d3-path "1"
    +
    +d3-time-format@2, d3-time-format@2.1.1:
    +  version "2.1.1"
    +  resolved "http://registry.npm.taobao.org/d3-time-format/download/d3-time-format-2.1.1.tgz#85b7cdfbc9ffca187f14d3c456ffda268081bb31"
    +  dependencies:
    +    d3-time "1"
    +
    +d3-time@1, d3-time@1.0.8:
    +  version "1.0.8"
    +  resolved "http://registry.npm.taobao.org/d3-time/download/d3-time-1.0.8.tgz#dbd2d6007bf416fe67a76d17947b784bffea1e84"
    +
    +d3-timer@1, d3-timer@1.0.7:
    +  version "1.0.7"
    +  resolved "http://registry.npm.taobao.org/d3-timer/download/d3-timer-1.0.7.tgz#df9650ca587f6c96607ff4e60cc38229e8dd8531"
    +
    +d3-transition@1, d3-transition@1.1.1:
    +  version "1.1.1"
    +  resolved "http://registry.npm.taobao.org/d3-transition/download/d3-transition-1.1.1.tgz#d8ef89c3b848735b060e54a39b32aaebaa421039"
    +  dependencies:
    +    d3-color "1"
    +    d3-dispatch "1"
    +    d3-ease "1"
    +    d3-interpolate "1"
    +    d3-selection "^1.1.0"
    +    d3-timer "1"
    +
    +d3-voronoi@1.1.2:
    +  version "1.1.2"
    +  resolved "http://registry.npm.taobao.org/d3-voronoi/download/d3-voronoi-1.1.2.tgz#1687667e8f13a2d158c80c1480c5a29cb0d8973c"
    +
    +d3-zoom@1.7.1:
    +  version "1.7.1"
    +  resolved "http://registry.npm.taobao.org/d3-zoom/download/d3-zoom-1.7.1.tgz#02f43b3c3e2db54f364582d7e4a236ccc5506b63"
    +  dependencies:
    +    d3-dispatch "1"
    +    d3-drag "1"
    +    d3-interpolate "1"
    +    d3-selection "1"
    +    d3-transition "1"
    +
    +d3@^4.13.0:
    +  version "4.13.0"
    +  resolved "http://registry.npm.taobao.org/d3/download/d3-4.13.0.tgz#ab236ff8cf0cfc27a81e69bf2fb7518bc9b4f33d"
    +  dependencies:
    +    d3-array "1.2.1"
    +    d3-axis "1.0.8"
    +    d3-brush "1.0.4"
    +    d3-chord "1.0.4"
    +    d3-collection "1.0.4"
    +    d3-color "1.0.3"
    +    d3-dispatch "1.0.3"
    +    d3-drag "1.2.1"
    +    d3-dsv "1.0.8"
    +    d3-ease "1.0.3"
    +    d3-force "1.1.0"
    +    d3-format "1.2.2"
    +    d3-geo "1.9.1"
    +    d3-hierarchy "1.1.5"
    +    d3-interpolate "1.1.6"
    +    d3-path "1.0.5"
    +    d3-polygon "1.0.3"
    +    d3-quadtree "1.0.3"
    +    d3-queue "3.0.7"
    +    d3-random "1.1.0"
    +    d3-request "1.0.6"
    +    d3-scale "1.0.7"
    +    d3-selection "1.3.0"
    +    d3-shape "1.2.0"
    +    d3-time "1.0.8"
    +    d3-time-format "2.1.1"
    +    d3-timer "1.0.7"
    +    d3-transition "1.1.1"
    +    d3-voronoi "1.1.2"
    +    d3-zoom "1.7.1"
    +
     d@1:
       version "1.0.0"
       resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
       dependencies:
         es5-ext "^0.10.9"
     
    +dagre-d3-renderer@^0.5.8:
    +  version "0.5.8"
    +  resolved "http://registry.npm.taobao.org/dagre-d3-renderer/download/dagre-d3-renderer-0.5.8.tgz#aa071bb71d3c4d67426925906f3f6ddead49c1a3"
    +  dependencies:
    +    dagre-layout "^0.8.8"
    +    lodash "^4.17.5"
    +
    +dagre-layout@^0.8.8:
    +  version "0.8.8"
    +  resolved "http://registry.npm.taobao.org/dagre-layout/download/dagre-layout-0.8.8.tgz#9b6792f24229f402441c14162c1049e3f261f6d9"
    +  dependencies:
    +    graphlibrary "^2.2.0"
    +    lodash "^4.17.5"
    +
     dashdash@^1.12.0:
       version "1.14.1"
       resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
    @@ -3685,6 +3927,12 @@ graceful-fs@~1.2.0:
       version "1.2.3"
       resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364"
     
    +graphlibrary@^2.2.0:
    +  version "2.2.0"
    +  resolved "http://registry.npm.taobao.org/graphlibrary/download/graphlibrary-2.2.0.tgz#017a14899775228dec4497a39babfdd6bf56eac6"
    +  dependencies:
    +    lodash "^4.17.5"
    +
     growly@^1.3.0:
       version "1.3.0"
       resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
    @@ -3874,6 +4122,10 @@ hawk@~2.3.0:
         hoek "2.x.x"
         sntp "1.x.x"
     
    +he@^1.1.1:
    +  version "1.1.1"
    +  resolved "http://registry.npm.taobao.org/he/download/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
    +
     highlight.js@^9.3.0:
       version "9.12.0"
       resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e"
    @@ -4037,16 +4289,16 @@ i18n-2@^0.7.2:
         debug "^3.1.0"
         sprintf "^0.1.5"
     
    -iconv-lite@0.4.19:
    -  version "0.4.19"
    -  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
    -
    -iconv-lite@^0.4.19, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
    +iconv-lite@0.4, iconv-lite@^0.4.19, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
       version "0.4.23"
       resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
       dependencies:
         safer-buffer ">= 2.1.2 < 3"
     
    +iconv-lite@0.4.19:
    +  version "0.4.19"
    +  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
    +
     iconv-lite@~0.2.11:
       version "0.2.11"
       resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.2.11.tgz#1ce60a3a57864a292d1321ff4609ca4bb965adc8"
    @@ -5298,7 +5550,7 @@ lodash@^3.5.0:
       version "3.10.1"
       resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
     
    -lodash@^4.0.0, lodash@^4.0.1, lodash@^4.11.1, lodash@^4.12.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.1:
    +lodash@^4.0.0, lodash@^4.0.1, lodash@^4.11.1, lodash@^4.12.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.1:
       version "4.17.10"
       resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
     
    @@ -5527,6 +5779,19 @@ merge@^1.1.3:
       version "1.2.0"
       resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
     
    +mermaid@^8.0.0-rc.8:
    +  version "8.0.0-rc.8"
    +  resolved "http://registry.npm.taobao.org/mermaid/download/mermaid-8.0.0-rc.8.tgz#74ed54d0d46e9ee71c4db2730b2d83d516a21e72"
    +  dependencies:
    +    d3 "^4.13.0"
    +    dagre-d3-renderer "^0.5.8"
    +    dagre-layout "^0.8.8"
    +    graphlibrary "^2.2.0"
    +    he "^1.1.1"
    +    lodash "^4.17.5"
    +    moment "^2.21.0"
    +    scope-css "^1.0.5"
    +
     methods@~1.1.2:
       version "1.1.2"
       resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
    @@ -5700,6 +5965,10 @@ moment@^2.10.3:
       version "2.22.1"
       resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad"
     
    +moment@^2.21.0:
    +  version "2.22.2"
    +  resolved "http://registry.npm.taobao.org/moment/download/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
    +
     mousetrap-global-bind@^1.1.0:
       version "1.1.0"
       resolved "https://registry.yarnpkg.com/mousetrap-global-bind/-/mousetrap-global-bind-1.1.0.tgz#cd7de9222bd0646fa2e010d54c84a74c26a88edd"
    @@ -7345,6 +7614,10 @@ run-series@^1.1.1:
       version "1.1.8"
       resolved "https://registry.yarnpkg.com/run-series/-/run-series-1.1.8.tgz#2c4558f49221e01cd6371ff4e0a1e203e460fc36"
     
    +rw@1:
    +  version "1.3.3"
    +  resolved "http://registry.npm.taobao.org/rw/download/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
    +
     rx-lite@^3.1.2:
       version "3.1.2"
       resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
    @@ -7422,6 +7695,10 @@ sax@>=0.6.0, sax@^1.2.1, sax@^1.2.4, sax@~1.2.1:
       version "1.2.4"
       resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
     
    +scope-css@^1.0.5:
    +  version "1.1.0"
    +  resolved "http://registry.npm.taobao.org/scope-css/download/scope-css-1.1.0.tgz#74eff45461bc9d3f3b29ed575b798cd722fa1256"
    +
     semver-diff@^2.0.0:
       version "2.1.0"
       resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
    @@ -8822,6 +9099,10 @@ xmldom@0.1.x:
       version "0.1.27"
       resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
     
    +xmlhttprequest@1:
    +  version "1.8.0"
    +  resolved "http://registry.npm.taobao.org/xmlhttprequest/download/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
    +
     xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
       version "4.0.1"
       resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
    
    From 09eac890861c5a6d1c447c9fe8560a959a73a039 Mon Sep 17 00:00:00 2001
    From: JianXu 
    Date: Wed, 4 Jul 2018 09:59:06 +0800
    Subject: [PATCH 41/74] Hotkey for toggle editor fullscreen
    
    ---
     lib/main-menu.js | 13 +++++++++++++
     1 file changed, 13 insertions(+)
    
    diff --git a/lib/main-menu.js b/lib/main-menu.js
    index 9345bd67..b7727224 100644
    --- a/lib/main-menu.js
    +++ b/lib/main-menu.js
    @@ -267,6 +267,19 @@ const view = {
             mainWindow.setFullScreen(!mainWindow.isFullScreen())
           }
         },
    +    {
    +      type: 'separator'
    +    },
    +    {
    +      label: 'Toggle Side Bar',
    +      accelerator: 'CommandOrControl+B',
    +      click () {
    +        mainWindow.webContents.send('editor:fullscreen')
    +      }
    +    },
    +    {
    +      type: 'separator'
    +    },
         {
           role: 'zoomin',
           accelerator: macOS ? 'CommandOrControl+Plus' : 'Control+='
    
    From 05488e66aeaadf99f874281b425b7cfd8ce62f2e Mon Sep 17 00:00:00 2001
    From: JianXu 
    Date: Wed, 4 Jul 2018 11:38:43 +0800
    Subject: [PATCH 42/74] Add  tooltip(CommandOrCtrl+B) for fullscreen
    
    ---
     browser/main/Detail/FullscreenButton.js | 4 +++-
     1 file changed, 3 insertions(+), 1 deletion(-)
    
    diff --git a/browser/main/Detail/FullscreenButton.js b/browser/main/Detail/FullscreenButton.js
    index 3d29c264..ee212603 100644
    --- a/browser/main/Detail/FullscreenButton.js
    +++ b/browser/main/Detail/FullscreenButton.js
    @@ -4,12 +4,14 @@ import CSSModules from 'browser/lib/CSSModules'
     import styles from './FullscreenButton.styl'
     import i18n from 'browser/lib/i18n'
     
    +const OSX = global.process.platform === 'darwin'
    +const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
     const FullscreenButton = ({
      onClick
     }) => (
       
     )
     
    
    From c2f0147cff70f19f5aeac6d9aa2364cc50edb89c Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?=
     
    Date: Wed, 4 Jul 2018 13:50:05 +0700
    Subject: [PATCH 43/74] updated new escape html function
    
    ---
     browser/lib/utils.js | 79 ++++++++++++++++++++------------------------
     1 file changed, 36 insertions(+), 43 deletions(-)
    
    diff --git a/browser/lib/utils.js b/browser/lib/utils.js
    index ee4d4ad0..10df31b2 100644
    --- a/browser/lib/utils.js
    +++ b/browser/lib/utils.js
    @@ -6,52 +6,45 @@ export function lastFindInArray (array, callback) {
       }
     }
     
    -export function escapeHtmlCharacters (text) {
    -  const matchHtmlRegExp = /["'&<>]/
    -  const str = '' + text
    -  const match = matchHtmlRegExp.exec(str)
    +function escapeHtmlCharacters (html) {
    +  const matchHtmlRegExp = /["'&<>]/g
    +  const escapes = ['"', '&', ''', '<', '>']
    +  let match = null
    +  const replaceAt = (str, index, replace) =>
    +    str.substr(0, index) +
    +    replace +
    +    str.substr(index + replace.length - (replace.length - 1))
     
    -  if (!match) {
    -    return str
    -  }
    -
    -  let escape
    -  let html = ''
    -  let index = 0
    -  let lastIndex = 0
    -
    -  for (index = match.index; index < str.length; index++) {
    -    switch (str.charCodeAt(index)) {
    -      case 34: // "
    -        escape = '"'
    -        break
    -      case 38: // &
    -        escape = '&ssssss;'
    -        break
    -      case 39: // '
    -        escape = '''
    -        break
    -      case 60: // <
    -        escape = '<'
    -        break
    -      case 62: // >
    -        escape = '>'
    -        break
    -      default:
    -        continue
    +  while ((match = matchHtmlRegExp.exec(html)) != null) {
    +    const current = { char: match[0], index: match.index }
    +    if (current.char === '&') {
    +      let nextStr = ''
    +      let nextIndex = current.index
    +      let escapedStr = false
    +      // maximum length of an escape string is 5. For example ('"')
    +      while (nextStr.length <= 5) {
    +        nextStr += html[nextIndex]
    +        nextIndex++
    +        if (escapes.indexOf(nextStr) !== -1) {
    +          escapedStr = true
    +          break
    +        }
    +      }
    +      if (!escapedStr) {
    +        // this & char is not a part of an escaped string
    +        html = replaceAt(html, current.index, '&')
    +      }
    +    } else if (current.char === '"') {
    +      html = replaceAt(html, current.index, '"')
    +    } else if (current.char === "'") {
    +      html = replaceAt(html, current.index, ''')
    +    } else if (current.char === '<') {
    +      html = replaceAt(html, current.index, '<')
    +    } else if (current.char === '>') {
    +      html = replaceAt(html, current.index, '>')
         }
    -
    -    if (lastIndex !== index) {
    -      html += str.substring(lastIndex, index)
    -    }
    -
    -    lastIndex = index + 1
    -    html += escape
       }
    -
    -  return lastIndex !== index
    -      ? html + str.substring(lastIndex, index)
    -      : html
    +  return html
     }
     
     export default {
    
    From 55d86d853abe139838eb9dda8b9df1db8f358d9d Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?=
     
    Date: Wed, 4 Jul 2018 15:27:30 +0700
    Subject: [PATCH 44/74] improved escape function
    
    ---
     browser/lib/utils.js | 19 ++++++++++++++++++-
     1 file changed, 18 insertions(+), 1 deletion(-)
    
    diff --git a/browser/lib/utils.js b/browser/lib/utils.js
    index c8420d91..6fc6ae27 100644
    --- a/browser/lib/utils.js
    +++ b/browser/lib/utils.js
    @@ -6,7 +6,7 @@ export function lastFindInArray (array, callback) {
       }
     }
     
    -function escapeHtmlCharacters (html) {
    +export function escapeHtmlCharacters (html) {
       const matchHtmlRegExp = /["'&<>]/g
       const escapes = ['"', '&', ''', '<', '>']
       let match = null
    @@ -15,8 +15,25 @@ function escapeHtmlCharacters (html) {
         replace +
         str.substr(index + replace.length - (replace.length - 1))
     
    +  // detecting code block
       while ((match = matchHtmlRegExp.exec(html)) != null) {
         const current = { char: match[0], index: match.index }
    +    // position of the nearest line start
    +    let previousLineEnd = current.index - 1
    +    while (html[previousLineEnd] !== '\n' && previousLineEnd !== -1) {
    +      previousLineEnd--
    +    }
    +    // 4 spaces means this character is in a code block
    +    if (
    +      html[previousLineEnd + 1] === ' ' &&
    +      html[previousLineEnd + 2] === ' ' &&
    +      html[previousLineEnd + 3] === ' ' &&
    +      html[previousLineEnd + 4] === ' '
    +    ) {
    +      // so skip it
    +      continue
    +    }
    +    // otherwise, escape it !!!
         if (current.char === '&') {
           let nextStr = ''
           let nextIndex = current.index
    
    From 9cc7b8bcc62ae0f8970c0a91012d0230e4d7a202 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?=
     
    Date: Wed, 4 Jul 2018 15:35:46 +0700
    Subject: [PATCH 45/74] fixed redundant code
    
    ---
     browser/components/MarkdownPreview.js | 3 +--
     1 file changed, 1 insertion(+), 2 deletions(-)
    
    diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js
    index 889074e1..31d7e631 100755
    --- a/browser/components/MarkdownPreview.js
    +++ b/browser/components/MarkdownPreview.js
    @@ -15,7 +15,6 @@ import copy from 'copy-to-clipboard'
     import mdurl from 'mdurl'
     import exportNote from 'browser/main/lib/dataApi/exportNote'
     import { escapeHtmlCharacters } from 'browser/lib/utils'
    -import ConfigManager from 'browser/main/lib/ConfigManager'
     
     const { remote } = require('electron')
     const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
    @@ -413,7 +412,7 @@ export default class MarkdownPreview extends React.Component {
             value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
           })
         }
    -    let renderedHTML = this.markdown.render(value)
    +    const renderedHTML = this.markdown.render(value)
         attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey)
         this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath)
     
    
    From 680eaa1d4ac448d2e8905983a47eafcfd11cf46d Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=98=D0=B2=D0=B0?=
     =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= 
    Date: Wed, 4 Jul 2018 13:33:47 +0300
    Subject: [PATCH 46/74] Filtering displayed notes in Detail component
    
    ---
     browser/main/Detail/index.js | 32 ++++++++++++++++++++++++++++++--
     1 file changed, 30 insertions(+), 2 deletions(-)
    
    diff --git a/browser/main/Detail/index.js b/browser/main/Detail/index.js
    index 2c451085..3e6e450e 100644
    --- a/browser/main/Detail/index.js
    +++ b/browser/main/Detail/index.js
    @@ -9,6 +9,7 @@ import ee from 'browser/main/lib/eventEmitter'
     import StatusBar from '../StatusBar'
     import i18n from 'browser/lib/i18n'
     import debounceRender from 'react-debounce-render'
    +import searchFromNotes from 'browser/lib/search'
     
     const OSX = global.process.platform === 'darwin'
     
    @@ -35,11 +36,38 @@ class Detail extends React.Component {
       }
     
       render () {
    -    const { location, data, config } = this.props
    +    const { location, data, params, config } = this.props
         let note = null
         if (location.query.key != null) {
           const noteKey = location.query.key
    -      note = data.noteMap.get(noteKey)
    +      let displayedNotes, noteKeys
    +
    +      if (location.pathname.match(/\/home/) || location.pathname.match(/alltags/)) {
    +        displayedNotes = data.noteMap.map(note => note)
    +      }
    +      if (location.pathname.match(/\/starred/)) {
    +        displayedNotes = data.starredSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
    +      }
    +      if (location.pathname.match(/\/searched/)) {
    +        displayedNotes = searchFromNotes(
    +          data.noteMap.map(note => note),
    +          params.searchword
    +        )
    +      }
    +      if (location.pathname.match(/\/trashed/)) {
    +        displayedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
    +      }
    +      if (location.pathname.match(/\/tags/)) {
    +        const listOfTags = params.tagname.split(' ')
    +        displayedNotes = data.noteMap.map(note => note).filter(note =>
    +          listOfTags.every(tag => note.tags.includes(tag))
    +        )
    +      }
    +
    +      noteKeys = displayedNotes.map(note => note.key)
    +      if (noteKeys.includes(noteKey)) {
    +        note = data.noteMap.get(noteKey)
    +      }
         }
     
         if (note == null) {
    
    From c69be5465576691d1c37914d2cbc87944dd6ccb6 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=98=D0=B2=D0=B0?=
     =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= 
    Date: Wed, 4 Jul 2018 14:02:26 +0300
    Subject: [PATCH 47/74] Fixing empty string searching
    
    ---
     browser/main/Detail/index.js | 12 ++++++------
     1 file changed, 6 insertions(+), 6 deletions(-)
    
    diff --git a/browser/main/Detail/index.js b/browser/main/Detail/index.js
    index 3e6e450e..75a3f91a 100644
    --- a/browser/main/Detail/index.js
    +++ b/browser/main/Detail/index.js
    @@ -40,7 +40,7 @@ class Detail extends React.Component {
         let note = null
         if (location.query.key != null) {
           const noteKey = location.query.key
    -      let displayedNotes, noteKeys
    +      let displayedNotes = []
     
           if (location.pathname.match(/\/home/) || location.pathname.match(/alltags/)) {
             displayedNotes = data.noteMap.map(note => note)
    @@ -49,10 +49,10 @@ class Detail extends React.Component {
             displayedNotes = data.starredSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
           }
           if (location.pathname.match(/\/searched/)) {
    -        displayedNotes = searchFromNotes(
    -          data.noteMap.map(note => note),
    -          params.searchword
    -        )
    +        const searchStr = params.searchword
    +        const allNotes = data.noteMap.map(note => note)
    +        displayedNotes = searchStr === undefined || searchStr === '' ? allNotes
    +          : searchFromNotes(allNotes, searchStr)
           }
           if (location.pathname.match(/\/trashed/)) {
             displayedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
    @@ -64,7 +64,7 @@ class Detail extends React.Component {
             )
           }
     
    -      noteKeys = displayedNotes.map(note => note.key)
    +      const noteKeys = displayedNotes.map(note => note.key)
           if (noteKeys.includes(noteKey)) {
             note = data.noteMap.get(noteKey)
           }
    
    From 4a3602099a55bc6afe1f34269aa2cecbbf9986be Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=98=D0=B2=D0=B0?=
     =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= 
    Date: Wed, 4 Jul 2018 16:09:49 +0300
    Subject: [PATCH 48/74] Difference home and searched notes from trashed units
    
    ---
     browser/main/Detail/index.js | 19 ++++++++++++++++---
     1 file changed, 16 insertions(+), 3 deletions(-)
    
    diff --git a/browser/main/Detail/index.js b/browser/main/Detail/index.js
    index 75a3f91a..90edf10e 100644
    --- a/browser/main/Detail/index.js
    +++ b/browser/main/Detail/index.js
    @@ -38,30 +38,43 @@ class Detail extends React.Component {
       render () {
         const { location, data, params, config } = this.props
         let note = null
    +
    +    function differenceWithTrashed (notes) {
    +      const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
    +      return _.differenceWith(notes, trashedNotes, (note, trashed) => note.key === trashed.key)
    +    }
    +
         if (location.query.key != null) {
           const noteKey = location.query.key
           let displayedNotes = []
     
           if (location.pathname.match(/\/home/) || location.pathname.match(/alltags/)) {
    -        displayedNotes = data.noteMap.map(note => note)
    +        const allNotes = data.noteMap.map(note => note)
    +        displayedNotes = differenceWithTrashed(allNotes)
           }
    +
           if (location.pathname.match(/\/starred/)) {
             displayedNotes = data.starredSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
           }
    +
           if (location.pathname.match(/\/searched/)) {
             const searchStr = params.searchword
             const allNotes = data.noteMap.map(note => note)
    -        displayedNotes = searchStr === undefined || searchStr === '' ? allNotes
    +        const searchedNotes = searchStr === undefined || searchStr === '' ? allNotes
               : searchFromNotes(allNotes, searchStr)
    +        displayedNotes = differenceWithTrashed(searchedNotes)
           }
    +
           if (location.pathname.match(/\/trashed/)) {
             displayedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
           }
    +
           if (location.pathname.match(/\/tags/)) {
             const listOfTags = params.tagname.split(' ')
    -        displayedNotes = data.noteMap.map(note => note).filter(note =>
    +        const searchedNotes = data.noteMap.map(note => note).filter(note =>
               listOfTags.every(tag => note.tags.includes(tag))
             )
    +        displayedNotes = differenceWithTrashed(searchedNotes)
           }
     
           const noteKeys = displayedNotes.map(note => note.key)
    
    From 806a5daa865a275452aa65e8b5f42c96029fd30c Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=98=D0=B2=D0=B0?=
     =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= 
    Date: Thu, 5 Jul 2018 11:23:57 +0300
    Subject: [PATCH 49/74] Processing all location's pathnames
    
    ---
     browser/main/Detail/index.js | 35 +++++++++++------------------------
     1 file changed, 11 insertions(+), 24 deletions(-)
    
    diff --git a/browser/main/Detail/index.js b/browser/main/Detail/index.js
    index 90edf10e..b6b6ef14 100644
    --- a/browser/main/Detail/index.js
    +++ b/browser/main/Detail/index.js
    @@ -39,42 +39,29 @@ class Detail extends React.Component {
         const { location, data, params, config } = this.props
         let note = null
     
    -    function differenceWithTrashed (notes) {
    -      const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
    -      return _.differenceWith(notes, trashedNotes, (note, trashed) => note.key === trashed.key)
    -    }
    -
         if (location.query.key != null) {
           const noteKey = location.query.key
    -      let displayedNotes = []
    -
    -      if (location.pathname.match(/\/home/) || location.pathname.match(/alltags/)) {
    -        const allNotes = data.noteMap.map(note => note)
    -        displayedNotes = differenceWithTrashed(allNotes)
    -      }
    -
    -      if (location.pathname.match(/\/starred/)) {
    -        displayedNotes = data.starredSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
    -      }
    +      const allNotes = data.noteMap.map(note => note)
    +      const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
    +      let displayedNotes = allNotes
     
           if (location.pathname.match(/\/searched/)) {
             const searchStr = params.searchword
    -        const allNotes = data.noteMap.map(note => note)
    -        const searchedNotes = searchStr === undefined || searchStr === '' ? allNotes
    +        displayedNotes = searchStr === undefined || searchStr === '' ? allNotes
               : searchFromNotes(allNotes, searchStr)
    -        displayedNotes = differenceWithTrashed(searchedNotes)
    -      }
    -
    -      if (location.pathname.match(/\/trashed/)) {
    -        displayedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
           }
     
           if (location.pathname.match(/\/tags/)) {
             const listOfTags = params.tagname.split(' ')
    -        const searchedNotes = data.noteMap.map(note => note).filter(note =>
    +        displayedNotes = data.noteMap.map(note => note).filter(note =>
               listOfTags.every(tag => note.tags.includes(tag))
             )
    -        displayedNotes = differenceWithTrashed(searchedNotes)
    +      }
    +
    +      if (location.pathname.match(/\/trashed/)) {
    +        displayedNotes = trashedNotes
    +      } else {
    +        displayedNotes = _.differenceWith(displayedNotes, trashedNotes, (note, trashed) => note.key === trashed.key)
           }
     
           const noteKeys = displayedNotes.map(note => note.key)
    
    From dfcf6d2729ac3a010926a5a7661f1e738b927ca4 Mon Sep 17 00:00:00 2001
    From: Kazz Yokomizo 
    Date: Fri, 6 Jul 2018 15:37:10 +0900
    Subject: [PATCH 50/74] Update readme
    
    ---
     readme.md | 8 +++++---
     1 file changed, 5 insertions(+), 3 deletions(-)
    
    diff --git a/readme.md b/readme.md
    index 6c68efaf..c6674902 100644
    --- a/readme.md
    +++ b/readme.md
    @@ -1,4 +1,4 @@
    -:mega: We've launched [Boostnote Bounty Program](http://bit.ly/2I5Tpik).
    +:mega: The Boostnote team launches [IssueHunt](https://issuehunt.io/) for sustainable open-source ecosystem.
     
     ![Boostnote app screenshot](./resources/repository/top.png)
     
    @@ -19,8 +19,10 @@ Thank you to all the people who already contributed to Boostnote!
     
     
     ## Supporting Boostnote
    -Boostnote is an open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [backers](https://github.com/BoostIO/Boostnote/blob/master/Backers.md). If you'd like to join them, please consider:
    -- [Become a backer or sponsor on Open Collective.](https://opencollective.com/boostnoteio)
    +Boostnote is an open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome backers. 
    +
    +Any issues on Boostnote can be funded by anyone and that money will be distributed to contributors and maintainers. If you'd like to join them, please consider:
    +- [Become a backer on IssueHunt](https://issuehunt.io/repos/53266139).
     
     ## Community
     - [Facebook Group](https://www.facebook.com/groups/boostnote/)
    
    From 7bfb094a40eae4661ca8ec516e08ec636791f12c Mon Sep 17 00:00:00 2001
    From: Junyoung Choi 
    Date: Fri, 6 Jul 2018 22:07:06 +0900
    Subject: [PATCH 51/74] use lighter color for scroll bar
    
    ---
     browser/components/MarkdownPreview.js | 27 +++++++++++++++++++++++++--
     browser/main/global.styl              |  9 +++++++--
     2 files changed, 32 insertions(+), 4 deletions(-)
    
    diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js
    index 0348996d..f2157882 100755
    --- a/browser/components/MarkdownPreview.js
    +++ b/browser/components/MarkdownPreview.js
    @@ -132,7 +132,15 @@ const scrollBarStyle = `
     }
     
     ::-webkit-scrollbar-thumb {
    -  box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
    +  background-color: rgba(0, 0, 0, 0.15);
    +}
    +`
    +const scrollBarDarkStyle = `
    +::-webkit-scrollbar {
    +  width: 12px;
    +}
    +
    +::-webkit-scrollbar-thumb {
       background-color: rgba(0, 0, 0, 0.3);
     }
     `
    @@ -301,6 +309,21 @@ export default class MarkdownPreview extends React.Component {
         }
       }
     
    +  getScrollBarStyle () {
    +    const {
    +      theme
    +    } = this.props
    +
    +    switch (theme) {
    +      case 'dark':
    +      case 'solarized-dark':
    +      case 'monokai':
    +        return scrollBarDarkStyle
    +      default:
    +        return scrollBarStyle
    +    }
    +  }
    +
       componentDidMount () {
         this.refs.root.setAttribute('sandbox', 'allow-scripts')
         this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
    @@ -310,7 +333,7 @@ export default class MarkdownPreview extends React.Component {
           
           
           
         `
     
    diff --git a/browser/main/global.styl b/browser/main/global.styl
    index 9a967085..8f3216ef 100644
    --- a/browser/main/global.styl
    +++ b/browser/main/global.styl
    @@ -19,8 +19,7 @@ body
       width 12px
     
     ::-webkit-scrollbar-thumb
    -  box-shadow inset 0 0 6px rgba(0, 0, 0, 0.3)
    -  background-color rgba(0, 0, 0, 0.3)
    +  background-color rgba(0, 0, 0, 0.15)
     
     button, input, select, textarea
       font-family DEFAULT_FONTS
    @@ -95,6 +94,8 @@ modalBackColor = white
     
     
     body[data-theme="dark"]
    +  ::-webkit-scrollbar-thumb
    +    background-color rgba(0, 0, 0, 0.3)
       .ModalBase
         .modalBack
           background-color $ui-dark-backgroundColor
    @@ -135,6 +136,8 @@ body[data-theme="dark"]
       z-index modalZIndex + 5
     
     body[data-theme="solarized-dark"]
    +  ::-webkit-scrollbar-thumb
    +    background-color rgba(0, 0, 0, 0.3)
       .ModalBase
         .modalBack
           background-color $ui-solarized-dark-backgroundColor
    @@ -142,6 +145,8 @@ body[data-theme="solarized-dark"]
         color: $ui-solarized-dark-text-color
     
     body[data-theme="monokai"]
    +  ::-webkit-scrollbar-thumb
    +    background-color rgba(0, 0, 0, 0.3)
       .ModalBase
         .modalBack
           background-color $ui-monokai-backgroundColor
    
    From 1d9b3ac2b56ce995f0b434789adf6e5ef13d9d0f Mon Sep 17 00:00:00 2001
    From: Junyoung Choi 
    Date: Sat, 7 Jul 2018 01:22:11 +0900
    Subject: [PATCH 52/74] Add sanitization for code fence
    
    ---
     browser/components/MarkdownPreview.js    | 2 +-
     browser/lib/markdown-it-sanitize-html.js | 3 +++
     2 files changed, 4 insertions(+), 1 deletion(-)
    
    diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js
    index c5abd57a..a5a9e9ee 100755
    --- a/browser/components/MarkdownPreview.js
    +++ b/browser/components/MarkdownPreview.js
    @@ -449,7 +449,7 @@ export default class MarkdownPreview extends React.Component {
             value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
           })
         }
    -    let renderedHTML = this.markdown.render(value)
    +    const renderedHTML = this.markdown.render(value)
         attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey)
         this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath)
     
    diff --git a/browser/lib/markdown-it-sanitize-html.js b/browser/lib/markdown-it-sanitize-html.js
    index beec9566..6d1a44b6 100644
    --- a/browser/lib/markdown-it-sanitize-html.js
    +++ b/browser/lib/markdown-it-sanitize-html.js
    @@ -10,6 +10,9 @@ module.exports = function sanitizePlugin (md, options) {
           if (state.tokens[tokenIdx].type === 'html_block') {
             state.tokens[tokenIdx].content = sanitizeHtml(state.tokens[tokenIdx].content, options)
           }
    +      if (state.tokens[tokenIdx].type === 'fence') {
    +        state.tokens[tokenIdx].content = state.tokens[tokenIdx].content.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"')
    +      }
           if (state.tokens[tokenIdx].type === 'inline') {
             const inlineTokens = state.tokens[tokenIdx].children
             for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
    
    From bc640834cdc5d7eec8dc679d5c99aa960fc06c86 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?=
     
    Date: Fri, 6 Jul 2018 23:45:18 +0700
    Subject: [PATCH 53/74] allow detect code block or not in escapeHtml function
    
    ---
     browser/components/MarkdownPreview.js |  2 +-
     browser/lib/utils.js                  | 32 ++++++++++++++-------------
     2 files changed, 18 insertions(+), 16 deletions(-)
    
    diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js
    index 31d7e631..c3d3a730 100755
    --- a/browser/components/MarkdownPreview.js
    +++ b/browser/components/MarkdownPreview.js
    @@ -219,7 +219,7 @@ export default class MarkdownPreview extends React.Component {
           const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
     
           const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
    -      let body = this.markdown.render(escapeHtmlCharacters(noteContent))
    +      let body = this.markdown.render(escapeHtmlCharacters(noteContent, { detectCodeBlock: true }))
     
           const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
           const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath)
    diff --git a/browser/lib/utils.js b/browser/lib/utils.js
    index 6fc6ae27..564ed3d2 100644
    --- a/browser/lib/utils.js
    +++ b/browser/lib/utils.js
    @@ -6,7 +6,7 @@ export function lastFindInArray (array, callback) {
       }
     }
     
    -export function escapeHtmlCharacters (html) {
    +export function escapeHtmlCharacters (html, opt = { detectCodeBlock: false }) {
       const matchHtmlRegExp = /["'&<>]/g
       const escapes = ['"', '&', ''', '<', '>']
       let match = null
    @@ -18,20 +18,22 @@ export function escapeHtmlCharacters (html) {
       // detecting code block
       while ((match = matchHtmlRegExp.exec(html)) != null) {
         const current = { char: match[0], index: match.index }
    -    // position of the nearest line start
    -    let previousLineEnd = current.index - 1
    -    while (html[previousLineEnd] !== '\n' && previousLineEnd !== -1) {
    -      previousLineEnd--
    -    }
    -    // 4 spaces means this character is in a code block
    -    if (
    -      html[previousLineEnd + 1] === ' ' &&
    -      html[previousLineEnd + 2] === ' ' &&
    -      html[previousLineEnd + 3] === ' ' &&
    -      html[previousLineEnd + 4] === ' '
    -    ) {
    -      // so skip it
    -      continue
    +    if (opt.detectCodeBlock) {
    +      // position of the nearest line start
    +      let previousLineEnd = current.index - 1
    +      while (html[previousLineEnd] !== '\n' && previousLineEnd !== -1) {
    +        previousLineEnd--
    +      }
    +      // 4 spaces means this character is in a code block
    +      if (
    +        html[previousLineEnd + 1] === ' ' &&
    +        html[previousLineEnd + 2] === ' ' &&
    +        html[previousLineEnd + 3] === ' ' &&
    +        html[previousLineEnd + 4] === ' '
    +      ) {
    +        // so skip it
    +        continue
    +      }
         }
         // otherwise, escape it !!!
         if (current.char === '&') {
    
    From 563fdcba94b60b62e308d9cfa8d6b03ae7a15234 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?=
     
    Date: Sat, 7 Jul 2018 00:04:11 +0700
    Subject: [PATCH 54/74] added tests escape html function
    
    ---
     tests/lib/escapeHtmlCharacters-test.js | 48 ++++++++++++++++++++++++++
     1 file changed, 48 insertions(+)
     create mode 100644 tests/lib/escapeHtmlCharacters-test.js
    
    diff --git a/tests/lib/escapeHtmlCharacters-test.js b/tests/lib/escapeHtmlCharacters-test.js
    new file mode 100644
    index 00000000..f13ab297
    --- /dev/null
    +++ b/tests/lib/escapeHtmlCharacters-test.js
    @@ -0,0 +1,48 @@
    +const { escapeHtmlCharacters } = require('browser/lib/utils')
    +const test = require('ava')
    +
    +test('escapeHtmlCharacters should return the original string if nothing needed to escape', t => {
    +  const input = 'Nothing to be escaped'
    +  const expected = 'Nothing to be escaped'
    +  const actual = escapeHtmlCharacters(input)
    +  t.is(actual, expected)
    +})
    +
    +test('escapeHtmlCharacters should skip code block if that option is enabled', t => {
    +  const input = `    
    +`
    +  const expected = `    
    +<escapeMe>`
    +  const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
    +  t.is(actual, expected)
    +})
    +
    +test('escapeHtmlCharacters should NOT skip character not in code block but start with 4 spaces', t => {
    +  const input = '4 spaces    &'
    +  const expected = '4 spaces    &'
    +  const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
    +  t.is(actual, expected)
    +})
    +
    +test('escapeHtmlCharacters should NOT skip code block if that option is NOT enabled', t => {
    +  const input = `    
    +`
    +  const expected = `    <no escape>
    +<escapeMe>`
    +  const actual = escapeHtmlCharacters(input)
    +  t.is(actual, expected)
    +})
    +
    +test('escapeHtmlCharacters should NOT escape & character if it\'s a part of an escaped character', t => {
    +  const input = 'Do not escape & or " but do escape &'
    +  const expected = 'Do not escape & or " but do escape &'
    +  const actual = escapeHtmlCharacters(input)
    +  t.is(actual, expected)
    +})
    +
    +test('escapeHtmlCharacters should return the correct result', t => {
    +  const input = '& < > " \''
    +  const expected = '& < > " ''
    +  const actual = escapeHtmlCharacters(input)
    +  t.is(actual, expected)
    +})
    
    From e18239048013682a6811b1b02c6b1e53f30ef877 Mon Sep 17 00:00:00 2001
    From: JianXu 
    Date: Sat, 7 Jul 2018 23:02:17 +0800
    Subject: [PATCH 55/74] Replace shortcut for 'Next Note' and 'Previous Note'
    
    ---
     lib/main-menu.js | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/lib/main-menu.js b/lib/main-menu.js
    index 9345bd67..5a3f06d1 100644
    --- a/lib/main-menu.js
    +++ b/lib/main-menu.js
    @@ -235,14 +235,14 @@ const view = {
         },
         {
           label: 'Next Note',
    -      accelerator: 'Control+J',
    +      accelerator: 'CommandOrControl+]',
           click () {
             mainWindow.webContents.send('list:next')
           }
         },
         {
           label: 'Previous Note',
    -      accelerator: 'Control+K',
    +      accelerator: 'CommandOrControl+[',
           click () {
             mainWindow.webContents.send('list:prior')
           }
    
    From 933e38eca9b34ca1ece6186cddbea8e515589c33 Mon Sep 17 00:00:00 2001
    From: Eric 
    Date: Mon, 9 Jul 2018 18:37:54 -0500
    Subject: [PATCH 56/74] improve bracket autoclosing
    
    ---
     browser/components/CodeEditor.js                      | 7 ++++++-
     browser/main/modals/PreferencesModal/SnippetEditor.js | 7 ++++++-
     2 files changed, 12 insertions(+), 2 deletions(-)
    
    diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js
    index a4d2278e..6fb6b0ec 100644
    --- a/browser/components/CodeEditor.js
    +++ b/browser/components/CodeEditor.js
    @@ -113,7 +113,12 @@ export default class CodeEditor extends React.Component {
           dragDrop: false,
           foldGutter: true,
           gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
    -      autoCloseBrackets: true,
    +      autoCloseBrackets: {
    +        pairs: '()[]{}\'\'""$$**``',
    +        triples: '```"""\'\'\'',
    +        explode: '[]{}``$$',
    +        override: true
    +      },
           extraKeys: {
             Tab: function (cm) {
               const cursor = cm.getCursor()
    diff --git a/browser/main/modals/PreferencesModal/SnippetEditor.js b/browser/main/modals/PreferencesModal/SnippetEditor.js
    index f0e93dec..4ce5dc34 100644
    --- a/browser/main/modals/PreferencesModal/SnippetEditor.js
    +++ b/browser/main/modals/PreferencesModal/SnippetEditor.js
    @@ -27,7 +27,12 @@ class SnippetEditor extends React.Component {
           dragDrop: false,
           foldGutter: true,
           gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
    -      autoCloseBrackets: true,
    +      autoCloseBrackets: {
    +        pairs: '()[]{}\'\'""$$**``',
    +        triples: '```"""\'\'\'',
    +        explode: '[]{}``$$',
    +        override: true
    +      },
           mode: 'null'
         })
         this.cm.setSize('100%', '100%')
    
    From a46b9fb2befeaa2ac7b3f4d0511d9fafb0361827 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Santiago=20Ag=C3=BCero?= 
    Date: Sun, 15 Jul 2018 01:37:47 -0300
    Subject: [PATCH 57/74] Fix attachment interop between win and nix
    
    ---
     .../main/lib/dataApi/attachmentManagement.js  | 43 +++++++++++-----
     tests/dataApi/attachmentManagement.test.js    | 49 +++++++++++++++++++
     2 files changed, 80 insertions(+), 12 deletions(-)
    
    diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js
    index a4c420bd..8c784817 100644
    --- a/browser/main/lib/dataApi/attachmentManagement.js
    +++ b/browser/main/lib/dataApi/attachmentManagement.js
    @@ -10,6 +10,7 @@ import i18n from 'browser/lib/i18n'
     
     const STORAGE_FOLDER_PLACEHOLDER = ':storage'
     const DESTINATION_FOLDER = 'attachments'
    +const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp(path.win32.sep)
     
     /**
      * @description
    @@ -74,6 +75,8 @@ function createAttachmentDestinationFolder (destinationStoragePath, noteKey) {
       }
     }
     
    +// TODO: This function does not have unit test. Is it working?
    +// TODO: Can we rewrite it to use markdownContent instead of renderedHTML? That way we can keep one function
     /**
      * @description Moves attachments from the old location ('/images') to the new one ('/attachments/noteKey)
      * @param renderedHTML HTML of the current note
    @@ -106,7 +109,10 @@ function migrateAttachments (renderedHTML, storagePath, noteKey) {
      * @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
      */
     function fixLocalURLS (renderedHTML, storagePath) {
    -  return renderedHTML.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
    +  return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + ".*?\"", 'g'), function (match) {
    +    var encoded_path_separators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g')
    +    return match.replace(encoded_path_separators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
    +  });
     }
     
     /**
    @@ -186,12 +192,23 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
     }
     
     /**
    - * @description Returns all attachment paths of the given markdown
    - * @param {String} markdownContent content in which the attachment paths should be found
    +* @description Returns all attachment paths of the given markdown
    +* @param {String} markdownContent content in which the attachment paths should be found
    +* @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown
    +*/
    +function getAttachmentsInMarkdownContent (markdownContent) {
    +  const preparedInput = markdownContent.replace(new RegExp('[' + PATH_SEPARATORS + ']', 'g'), path.sep)
    +  const regexp = new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + ')' + '?([a-zA-Z0-9]|-)*' + '(' + escapeStringRegexp(path.sep) + ')' + '([a-zA-Z0-9]|\\.)+(\\.[a-zA-Z0-9]+)?', 'g')
    +  return preparedInput.match(regexp)
    +}
    +
    +/**
    + * @description Returns all attachment paths of the given renderedHTML
    + * @param {String} renderedHTML content in which the attachment paths should be found
      * @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown
      */
    -function getAttachmentsInContent (markdownContent) {
    -  const preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep)
    +function getAttachmentsInContent (renderedHTML) {
    +  const preparedInput = renderedHTML.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep)
       const regexp = new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + '|/)' + '?([a-zA-Z0-9]|-)*' + '(' + escapeStringRegexp(path.sep) + '|/)' + '([a-zA-Z0-9]|\\.)+(\\.[a-zA-Z0-9]+)?', 'g')
       return preparedInput.match(regexp)
     }
    @@ -203,7 +220,7 @@ function getAttachmentsInContent (markdownContent) {
      * @returns {String[]} Absolute paths of the referenced attachments
      */
     function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
    -  const temp = getAttachmentsInContent(markdownContent) || []
    +  const temp = getAttachmentsInMarkdownContent(markdownContent) || []
       const result = []
       for (const relativePath of temp) {
         result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER)))
    @@ -239,7 +256,8 @@ function moveAttachments (oldPath, newPath, noteKey, newNoteKey, noteContent) {
      */
     function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) {
       if (noteContent) {
    -    return noteContent.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
    +    const preparedInput = noteContent.replace(new RegExp('[' + PATH_SEPARATORS + ']', 'g'), path.sep)
    +    return preparedInput.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
       }
       return noteContent
     }
    @@ -277,7 +295,7 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
       }
       const targetStorage = findStorage.findStorage(storageKey)
       const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
    -  const attachmentsInNote = getAttachmentsInContent(markdownContent)
    +  const attachmentsInNote = getAttachmentsInMarkdownContent(markdownContent)
       const attachmentsInNoteOnlyFileNames = []
       if (attachmentsInNote) {
         for (let i = 0; i < attachmentsInNote.length; i++) {
    @@ -348,7 +366,7 @@ function generateFileNotFoundMarkdown () {
      */
     function isAttachmentLink (text) {
       if (text) {
    -    return text.match(new RegExp('.*\\[.*\\]\\( *' + escapeStringRegexp(STORAGE_FOLDER_PLACEHOLDER) + escapeStringRegexp(path.sep) + '.*\\).*', 'gi')) != null
    +    return text.match(new RegExp('.*\\[.*\\]\\( *' + escapeStringRegexp(STORAGE_FOLDER_PLACEHOLDER) + '[' + PATH_SEPARATORS + ']' + '.*\\).*', 'gi')) != null
       }
       return false
     }
    @@ -364,7 +382,7 @@ function isAttachmentLink (text) {
     function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
       if (storageKey != null && noteKey != null && linkText != null) {
         const storagePath = findStorage.findStorage(storageKey).path
    -    const attachments = getAttachmentsInContent(linkText) || []
    +    const attachments = getAttachmentsInMarkdownContent(linkText) || []
         const replaceInstructions = []
         const copies = []
         for (const attachment of attachments) {
    @@ -373,13 +391,13 @@ function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
             sander.exists(absPathOfAttachment)
               .then((fileExists) => {
                 if (!fileExists) {
    -              const fileNotFoundRegexp = new RegExp('!?' + escapeStringRegexp('[') + '[\\w|\\d|\\s|\\.]*\\]\\(\\s*' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + escapeStringRegexp(path.sep) + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + escapeStringRegexp(')'))
    +              const fileNotFoundRegexp = new RegExp('!?' + escapeStringRegexp('[') + '[\\w|\\d|\\s|\\.]*\\]\\(\\s*' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + PATH_SEPARATORS + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + escapeStringRegexp(')'))
                   replaceInstructions.push({regexp: fileNotFoundRegexp, replacement: this.generateFileNotFoundMarkdown()})
                   return Promise.resolve()
                 }
                 return this.copyAttachment(absPathOfAttachment, storageKey, noteKey)
                   .then((fileName) => {
    -                const replaceLinkRegExp = new RegExp(escapeStringRegexp('(') + ' *' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + escapeStringRegexp(path.sep) + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + ' *' + escapeStringRegexp(')'))
    +                const replaceLinkRegExp = new RegExp(escapeStringRegexp('(') + ' *' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + PATH_SEPARATORS + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + ' *' + escapeStringRegexp(')'))
                     replaceInstructions.push({
                       regexp: replaceLinkRegExp,
                       replacement: '(' + path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName) + ')'
    @@ -409,6 +427,7 @@ module.exports = {
       handleAttachmentDrop,
       handlePastImageEvent,
       getAttachmentsInContent,
    +  getAttachmentsInMarkdownContent,
       getAbsolutePathsOfAttachmentsInContent,
       removeStorageAndNoteReferences,
       deleteAttachmentFolder,
    diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js
    index 4ce031a7..f68eb390 100644
    --- a/tests/dataApi/attachmentManagement.test.js
    +++ b/tests/dataApi/attachmentManagement.test.js
    @@ -169,6 +169,43 @@ it('should replace the all ":storage" path with the actual storage path', functi
       expect(actual).toEqual(expectedOutput)
     })
     
    +it('should replace the ":storage" path with the actual storage path when they have different path separators', function () {
    +  const storageFolder = systemUnderTest.DESTINATION_FOLDER
    +  const testInput =
    +    '\n' +
    +    '    \n' +
    +    '        //header\n' +
    +    '    \n' +
    +    '    \n' +
    +    '        

    Headline

    \n' + + '

    \n' + + ' dummyImage.png\n' + + '

    \n' + + '

    \n' + + ' dummyPDF.pdf\n' + + '

    \n' + + ' \n' + + '' + const storagePath = '<>' + const expectedOutput = + '\n' + + ' \n' + + ' //header\n' + + ' \n' + + ' \n' + + '

    Headline

    \n' + + '

    \n' + + ' dummyImage.png\n' + + '

    \n' + + '

    \n' + + ' dummyPDF.pdf\n' + + '

    \n' + + ' \n' + + '' + const actual = systemUnderTest.fixLocalURLS(testInput, storagePath) + expect(actual).toEqual(expectedOutput) +}) + it('should test that generateAttachmentMarkdown works correct both with previews and without', function () { const fileName = 'fileName' const path = 'path' @@ -204,6 +241,18 @@ it('should test that getAttachmentsInContent finds all attachments', function () expect(actual).toEqual(expect.arrayContaining(expected)) }) +it('should test that getAttachmentsInMarkdownContent finds all attachments when they have different path separators', function () { + const testInput = '"# Test\n' + + '\n' + + '![Screenshot1](:storage' + path.win32.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.win32.sep + '0.3b88d0dc.png)\n' + + '![Screenshot2](:storage' + path.posix.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.posix.sep + '2cb8875c.pdf)\n' + + '![Screenshot3](:storage' + path.win32.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.posix.sep + 'bbf49b02.jpg)"' + + const actual = systemUnderTest.getAttachmentsInMarkdownContent(testInput) + const expected = [':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.3b88d0dc.png', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '2cb8875c.pdf', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'bbf49b02.jpg'] + expect(actual).toEqual(expect.arrayContaining(expected)) +}) + it('should test that getAbsolutePathsOfAttachmentsInContent returns all absolute paths', function () { const dummyStoragePath = 'dummyStoragePath' const testInput = From 5cc52f91cbe87ad34d7c5805592687f8ce43f24d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santiago=20Ag=C3=BCero?= Date: Sun, 15 Jul 2018 12:07:27 -0300 Subject: [PATCH 58/74] Fix lint errors --- browser/main/lib/dataApi/attachmentManagement.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index 8c784817..18407730 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -109,10 +109,10 @@ function migrateAttachments (renderedHTML, storagePath, noteKey) { * @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths. */ function fixLocalURLS (renderedHTML, storagePath) { - return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + ".*?\"", 'g'), function (match) { - var encoded_path_separators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g') - return match.replace(encoded_path_separators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER)) - }); + return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '.*?"', 'g'), function (match) { + var encodedPathSeparators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g') + return match.replace(encodedPathSeparators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER)) + }) } /** From c37b780ca4dc72d9c1efa54e4644fb27944b6eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santiago=20Ag=C3=BCero?= Date: Tue, 17 Jul 2018 00:13:13 -0300 Subject: [PATCH 59/74] Use markdown content for migrateAttachments --- browser/components/MarkdownPreview.js | 2 +- .../main/lib/dataApi/attachmentManagement.js | 20 ++-------- tests/dataApi/attachmentManagement.test.js | 40 +++++++++---------- 3 files changed, 22 insertions(+), 40 deletions(-) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index a5a9e9ee..1797a393 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -450,7 +450,7 @@ export default class MarkdownPreview extends React.Component { }) } const renderedHTML = this.markdown.render(value) - attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey) + attachmentManagement.migrateAttachments(value, storagePath, noteKey) this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath) _.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => { diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index 18407730..088fb054 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -75,17 +75,15 @@ function createAttachmentDestinationFolder (destinationStoragePath, noteKey) { } } -// TODO: This function does not have unit test. Is it working? -// TODO: Can we rewrite it to use markdownContent instead of renderedHTML? That way we can keep one function /** * @description Moves attachments from the old location ('/images') to the new one ('/attachments/noteKey) - * @param renderedHTML HTML of the current note + * @param markdownContent of the current note * @param storagePath Storage path of the current note * @param noteKey Key of the current note */ -function migrateAttachments (renderedHTML, storagePath, noteKey) { +function migrateAttachments (markdownContent, storagePath, noteKey) { if (sander.existsSync(path.join(storagePath, 'images'))) { - const attachments = getAttachmentsInContent(renderedHTML) || [] + const attachments = getAttachmentsInMarkdownContent(markdownContent) || [] if (attachments !== []) { createAttachmentDestinationFolder(storagePath, noteKey) } @@ -202,17 +200,6 @@ function getAttachmentsInMarkdownContent (markdownContent) { return preparedInput.match(regexp) } -/** - * @description Returns all attachment paths of the given renderedHTML - * @param {String} renderedHTML content in which the attachment paths should be found - * @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown - */ -function getAttachmentsInContent (renderedHTML) { - const preparedInput = renderedHTML.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep) - const regexp = new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + '|/)' + '?([a-zA-Z0-9]|-)*' + '(' + escapeStringRegexp(path.sep) + '|/)' + '([a-zA-Z0-9]|\\.)+(\\.[a-zA-Z0-9]+)?', 'g') - return preparedInput.match(regexp) -} - /** * @description Returns an array of the absolute paths of the attachments referenced in the given markdown code * @param {String} markdownContent content in which the attachment paths should be found @@ -426,7 +413,6 @@ module.exports = { generateAttachmentMarkdown, handleAttachmentDrop, handlePastImageEvent, - getAttachmentsInContent, getAttachmentsInMarkdownContent, getAbsolutePathsOfAttachmentsInContent, removeStorageAndNoteReferences, diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index f68eb390..853e0431 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -217,28 +217,24 @@ it('should test that generateAttachmentMarkdown works correct both with previews expect(actual).toEqual(expected) }) -it('should test that getAttachmentsInContent finds all attachments', function () { - const testInput = - '\n' + - ' \n' + - ' //header\n' + - ' \n' + - ' \n' + - '

    Headline

    \n' + - '

    \n' + - ' dummyImage.png\n' + - '

    \n' + - '

    \n' + - ' dummyPDF.pdf\n' + - '

    \n' + - '

    \n' + - ' dummyImage2.jpg\n' + - '

    \n' + - ' \n' + - '' - const actual = systemUnderTest.getAttachmentsInContent(testInput) - const expected = [':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp.png', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx.pdf', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg'] - expect(actual).toEqual(expect.arrayContaining(expected)) +it('should test that migrateAttachments work when they have different path separators', function () { + sander.existsSync = jest.fn(() => true) + const dummyStoragePath = 'dummyStoragePath' + const imagesPath = path.join(dummyStoragePath, 'images') + const attachmentsPath = path.join(dummyStoragePath, 'attachments') + const noteKey = 'noteKey' + const testInput = '"# Test\n' + + '\n' + + '![Screenshot1](:storage' + path.win32.sep + '0.3b88d0dc.png)\n' + + '![Screenshot2](:storage' + path.posix.sep + '0.2cb8875c.pdf)"' + + systemUnderTest.migrateAttachments(testInput, dummyStoragePath, noteKey) + + expect(sander.existsSync.mock.calls[0][0]).toBe(imagesPath) + expect(sander.existsSync.mock.calls[1][0]).toBe(path.join(imagesPath, '0.3b88d0dc.png')) + expect(sander.existsSync.mock.calls[2][0]).toBe(path.join(attachmentsPath, '0.3b88d0dc.png')) + expect(sander.existsSync.mock.calls[3][0]).toBe(path.join(imagesPath, '0.2cb8875c.pdf')) + expect(sander.existsSync.mock.calls[4][0]).toBe(path.join(attachmentsPath, '0.2cb8875c.pdf')) }) it('should test that getAttachmentsInMarkdownContent finds all attachments when they have different path separators', function () { From 8de3b3bd8dbbff0e3adb5327386412488121f32f Mon Sep 17 00:00:00 2001 From: Junyoung Choi Date: Tue, 17 Jul 2018 15:55:03 +0900 Subject: [PATCH 60/74] Update yarn.lock --- yarn.lock | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index a5a4a89c..a89d695b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -79,6 +79,12 @@ fs-plus "2.x" optimist "~0.4.0" +"@susisu/mte-kernel@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@susisu/mte-kernel/-/mte-kernel-2.0.0.tgz#da3354d6a07ea27f36ec91d9fccf6bfa9010d00e" + dependencies: + meaw "^2.0.0" + "@types/node@^8.0.24": version "8.10.17" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.17.tgz#d48cf10f0dc6dcf59f827f5a3fc7a4a6004318d3" @@ -5746,6 +5752,10 @@ mdurl@^1.0.1, mdurl@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" +meaw@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/meaw/-/meaw-2.0.0.tgz#7c3467efee5618cb865661dfaa38d6948dc23f7a" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -5985,7 +5995,7 @@ mock-require@^3.0.1: get-caller-file "^1.0.2" normalize-path "^2.1.1" -moment@^2.10.2: +moment@^2.10.2, moment@^2.21.0: version "2.22.2" resolved "http://registry.npm.taobao.org/moment/download/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" @@ -5993,10 +6003,6 @@ moment@^2.10.3: version "2.22.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad" -moment@^2.21.0: - version "2.22.2" - resolved "http://registry.npm.taobao.org/moment/download/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" - mousetrap-global-bind@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mousetrap-global-bind/-/mousetrap-global-bind-1.1.0.tgz#cd7de9222bd0646fa2e010d54c84a74c26a88edd" From ad847a2f5d326dffea8fd4fabf7c823a2ffbda69 Mon Sep 17 00:00:00 2001 From: JianXu Date: Tue, 17 Jul 2018 17:17:51 +0800 Subject: [PATCH 61/74] New Feature: Shortcuts for focusing tag editor(CmdOrControl+T) --- browser/main/Detail/SnippetNoteDetail.js | 2 +- browser/main/Detail/TagSelect.js | 11 +++++++++++ lib/main-menu.js | 10 ++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 75be4798..652d1f53 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -441,7 +441,7 @@ class SnippetNoteDetail extends React.Component { const isSuper = global.process.platform === 'darwin' ? e.metaKey : e.ctrlKey - if (isSuper) { + if (isSuper && !e.shiftKey) { e.preventDefault() this.addSnippet() } diff --git a/browser/main/Detail/TagSelect.js b/browser/main/Detail/TagSelect.js index d14c7a8c..e251dd42 100644 --- a/browser/main/Detail/TagSelect.js +++ b/browser/main/Detail/TagSelect.js @@ -5,6 +5,7 @@ import styles from './TagSelect.styl' import _ from 'lodash' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import i18n from 'browser/lib/i18n' +import ee from 'browser/main/lib/eventEmitter' class TagSelect extends React.Component { constructor (props) { @@ -13,16 +14,26 @@ class TagSelect extends React.Component { this.state = { newTag: '' } + this.addtagHandler = this.handleAddTag.bind(this) } componentDidMount () { this.value = this.props.value + ee.on('editor:add-tag', this.addtagHandler) } componentDidUpdate () { this.value = this.props.value } + componentWillUnmount () { + ee.off('editor:add-tag', this.addtagHandler) + } + + handleAddTag () { + this.refs.newTag.focus() + } + handleNewTagInputKeyDown (e) { switch (e.keyCode) { case 9: diff --git a/lib/main-menu.js b/lib/main-menu.js index 1d816ef3..cda964c5 100644 --- a/lib/main-menu.js +++ b/lib/main-menu.js @@ -218,6 +218,16 @@ const edit = { label: 'Select All', accelerator: 'Command+A', selector: 'selectAll:' + }, + { + type: 'separator' + }, + { + label: 'Add Tag', + accelerator: 'CommandOrControl+Shift+T', + click () { + mainWindow.webContents.send('editor:add-tag') + } } ] } From 7de77723397a4591ae3a78d595e1dcf48516f95c Mon Sep 17 00:00:00 2001 From: William Grant Date: Tue, 17 Jul 2018 14:41:22 +0200 Subject: [PATCH 62/74] Fixed infoButton panel in trash positioning --- browser/main/Detail/InfoPanel.styl | 1 + 1 file changed, 1 insertion(+) diff --git a/browser/main/Detail/InfoPanel.styl b/browser/main/Detail/InfoPanel.styl index ac91bbec..2a73ca7e 100644 --- a/browser/main/Detail/InfoPanel.styl +++ b/browser/main/Detail/InfoPanel.styl @@ -33,6 +33,7 @@ .control-infoButton-panel-trash z-index 200 margin-top 0px + top 50px right 0px position absolute padding 20px 25px 0 25px From be972781ee482c49dda91abadbd6ba33651baacf Mon Sep 17 00:00:00 2001 From: "Chad Wade Day, Jr" Date: Wed, 18 Jul 2018 11:58:54 -0700 Subject: [PATCH 63/74] Fix for BoostIO/Boostnote#2204 --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index a3fd3b91..57863871 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "lodash": "^4.11.1", "lodash-move": "^1.1.1", "markdown-it": "^6.0.1", - "markdown-it-admonition": "https://github.com/johannbre/markdown-it-admonition.git", + "markdown-it-admonition": "^1.0.4", "markdown-it-emoji": "^1.1.1", "markdown-it-footnote": "^3.0.0", "markdown-it-imsize": "^2.0.1", diff --git a/yarn.lock b/yarn.lock index a89d695b..f77252ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5650,9 +5650,9 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -"markdown-it-admonition@https://github.com/johannbre/markdown-it-admonition.git": - version "1.0.2" - resolved "https://github.com/johannbre/markdown-it-admonition.git#e0c0fcd59e9119d6d60ed209aa3d0f1177ec0166" +markdown-it-admonition@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/markdown-it-admonition/-/markdown-it-admonition-1.0.4.tgz#d7bbc7eb1fe6168fc8cc304de7a9d8c993acb2f5" markdown-it-emoji@^1.1.1: version "1.4.0" From 6cad2ab4df1143a9d46d231e14cb0ae5e5465cf0 Mon Sep 17 00:00:00 2001 From: Junyoung Choi Date: Thu, 19 Jul 2018 15:21:35 +0900 Subject: [PATCH 64/74] v0.11.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a3fd3b91..f3b4b892 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "boost", "productName": "Boostnote", - "version": "0.11.7", + "version": "0.11.8", "main": "index.js", "description": "Boostnote", "license": "GPL-3.0", From 1e5a7356f498ca3a00355687b305719e4f704fef Mon Sep 17 00:00:00 2001 From: Steve Klein Date: Fri, 20 Jul 2018 02:00:11 -0700 Subject: [PATCH 65/74] Add code snippets to search scope --- browser/lib/search.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/browser/lib/search.js b/browser/lib/search.js index b42fd389..cf5b3b1b 100644 --- a/browser/lib/search.js +++ b/browser/lib/search.js @@ -23,7 +23,9 @@ function findByWordOrTag (notes, block) { return true } if (note.type === 'SNIPPET_NOTE') { - return note.description.match(wordRegExp) + return note.description.match(wordRegExp) || note.snippets.some((snippet) => { + return snippet.name.match(wordRegExp) || snippet.content.match(wordRegExp) + }) } else if (note.type === 'MARKDOWN_NOTE') { return note.content.match(wordRegExp) } From ee6b9a223fe6f368901c14756e9bbe211f7bebfd Mon Sep 17 00:00:00 2001 From: yamash723 Date: Sat, 21 Jul 2018 15:25:09 +0900 Subject: [PATCH 66/74] Change output path format of html file by platform --- browser/components/MarkdownPreview.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 297266c3..e6f89411 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -225,7 +225,12 @@ export default class MarkdownPreview extends React.Component { const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath) files.forEach((file) => { - file = file.replace('file:///', '') + if (global.process.platform === 'win32') { + file = file.replace('file:///', '') + } else { + file = file.replace('file://', '') + } + exportTasks.push({ src: file, dst: 'css' From eff56c251436520ed0a5b67ed55e4b0b17e0154e Mon Sep 17 00:00:00 2001 From: William Grant Date: Sat, 21 Jul 2018 19:20:20 +1000 Subject: [PATCH 67/74] mermaid diagram rendering for dark themes is now fixed --- browser/components/MarkdownPreview.js | 2 +- browser/components/render/MermaidRender.js | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 1cec5cdd..37c9e9f6 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -556,7 +556,7 @@ export default class MarkdownPreview extends React.Component { _.forEach( this.refs.root.contentWindow.document.querySelectorAll('.mermaid'), (el) => { - mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML)) + mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme) } ) } diff --git a/browser/components/render/MermaidRender.js b/browser/components/render/MermaidRender.js index e057cf34..fcbc97eb 100644 --- a/browser/components/render/MermaidRender.js +++ b/browser/components/render/MermaidRender.js @@ -1,5 +1,11 @@ import mermaidAPI from 'mermaid' +// fixes bad styling in the mermaid dark theme +const darkThemeStyling = ` +.loopText tspan { + fill: white; +}` + function getRandomInt (min, max) { return Math.floor(Math.random() * (max - min)) + min } @@ -13,8 +19,13 @@ function getId () { return id } -function render (element, content) { +function render (element, content, theme) { try { + let isDarkTheme = theme === "dark" || theme === "solarized-dark" || theme === "monokai" + mermaidAPI.initialize({ + theme: isDarkTheme ? "dark" : "default", + themeCSS: isDarkTheme ? darkThemeStyling : "" + }) mermaidAPI.render(getId(), content, (svgGraph) => { element.innerHTML = svgGraph }) From 16c62cd46fd8acf6fc71b514e56955c84162f978 Mon Sep 17 00:00:00 2001 From: William Grant Date: Sat, 21 Jul 2018 19:31:40 +1000 Subject: [PATCH 68/74] changed double quotes to single quotes --- browser/components/render/MermaidRender.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/browser/components/render/MermaidRender.js b/browser/components/render/MermaidRender.js index fcbc97eb..12dce327 100644 --- a/browser/components/render/MermaidRender.js +++ b/browser/components/render/MermaidRender.js @@ -21,10 +21,10 @@ function getId () { function render (element, content, theme) { try { - let isDarkTheme = theme === "dark" || theme === "solarized-dark" || theme === "monokai" + let isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai' mermaidAPI.initialize({ - theme: isDarkTheme ? "dark" : "default", - themeCSS: isDarkTheme ? darkThemeStyling : "" + theme: isDarkTheme ? 'dark' : 'default', + themeCSS: isDarkTheme ? darkThemeStyling : '' }) mermaidAPI.render(getId(), content, (svgGraph) => { element.innerHTML = svgGraph From 59b53ece2bbe744881f792fd380fcad93a9c8caa Mon Sep 17 00:00:00 2001 From: amedora Date: Mon, 23 Jul 2018 11:11:48 +0900 Subject: [PATCH 69/74] add Ditaa support (resolve #1454) --- browser/lib/markdown.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 7ebe5a81..49fd2f86 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -163,6 +163,22 @@ class Markdown { } }) + // Ditaa support + this.md.use(require('markdown-it-plantuml'), { + openMarker: '@startditaa', + closeMarker: '@endditaa', + generateSource: function (umlCode) { + const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url + // Currently PlantUML server doesn't support Ditaa in SVG, so we set the format as PNG at the moment. + const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/png' + const s = unescape(encodeURIComponent(umlCode)) + const zippedCode = deflate.encode64( + deflate.zip_deflate(`@startditaa\n${s}\n@endditaa`, 9) + ) + return `${serverAddress}/${zippedCode}` + } + }) + // Override task item this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) { let content, terminate, i, l, token From 1e8397cf17dd9701c678d04e66afeb8e5da9eda5 Mon Sep 17 00:00:00 2001 From: narukami894 Date: Tue, 24 Jul 2018 15:57:12 +0900 Subject: [PATCH 70/74] add and improve translation, fix typo --- docs/jp/build.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/jp/build.md b/docs/jp/build.md index 4d0fab33..b8d2e414 100644 --- a/docs/jp/build.md +++ b/docs/jp/build.md @@ -1,9 +1,16 @@ # Build +## 環境 +* npm: 4.x +* node: 7.x + +`npm v5.x` だと `$ grunt pre-build` が失敗するので、 `npm v4.x` を使用してください。 + ## 開発 Webpack HRMを使います。 -次の命令から私達がしておいた設定を使うことができます。 +Boostnoteの最上位ディレクトリにて以下のコマンドを実行して、 +デフォルトの設定の開発環境を起動させます。 依存するパッケージをインストールします。 @@ -27,15 +34,15 @@ $ yarn run dev-start > ### 注意 > 時々、直接リフレッシュをする必要があります。 -> 1. コンポネントのコンストラクター関数を編集する場合 -> 2. 新しいCSSクラスを追加する場合(1.の理由と同じ: CSSクラス名はコンポネントごとに書きなおされまが、この作業はコンストラクターで行われます。) +> 1. コンポーネントのコンストラクタ関数を編集する場合 +> 2. 新しいCSSクラスを追加する場合(1.の理由と同じ: CSSクラス名はコンポーネントごとに書きなおされますが、この作業はコンストラクタで行われます。) ## 配布 Gruntを使います。 -実際の配布は`grunt`で実行できます。しかし、これにはCodesignとAuthenticodeの仮定が含まれるので、使っては行けないです。 +実際の配布は`grunt`で実行できます。しかし、これにはCodesignとAuthenticodeを実行するタスクが含まれるので、使用しないでください。 -それで、実行ファイルを作るスクリプトを用意しておきました。 +代わりに、実行ファイルを作るスクリプトを用意しておきました。 このビルドはnpm v5.3.0では動かないのでv5.2.0で動かす必要があります。 From 1d1ab65edd61d5b0c8056795d59d11c112c62c55 Mon Sep 17 00:00:00 2001 From: Maciek Date: Sat, 28 Jul 2018 22:23:14 +0200 Subject: [PATCH 71/74] BUG FIX: snippet notes are not displaying in markdown #2088 Fix for issue https://github.com/BoostIo/Boostnote/issues/2088. In specific situation, when all below conditions are met : - one of the snippets notes tabs is a Markdown tab, - the migrateAttachments code is called on a snippet note, - historical attachments location '/images' exists in snippet storage folder Following exception is being thrown : path.js:28 Uncaught TypeError: Path must be a string. Received undefined The exception is a result of an undefined noteKey variable (which is defined only for Markdown notes). The solution is to skip migration of attachments for notes without noteKey (which wouldn't be possible anyway, since noteKey is a necessary for creating folder for attachments). --- browser/main/lib/dataApi/attachmentManagement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index 088fb054..46560a1a 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -82,7 +82,7 @@ function createAttachmentDestinationFolder (destinationStoragePath, noteKey) { * @param noteKey Key of the current note */ function migrateAttachments (markdownContent, storagePath, noteKey) { - if (sander.existsSync(path.join(storagePath, 'images'))) { + if (noteKey !== undefined && sander.existsSync(path.join(storagePath, 'images'))) { const attachments = getAttachmentsInMarkdownContent(markdownContent) || [] if (attachments !== []) { createAttachmentDestinationFolder(storagePath, noteKey) From 4a3bcaba0657dd17e4494ebb94122de85e15c617 Mon Sep 17 00:00:00 2001 From: Maciek Date: Sat, 28 Jul 2018 22:25:10 +0200 Subject: [PATCH 72/74] BUG FIX: Change the way of checking for empty array The original condition : attachments !== [] always returns true, for empty array, as well as for array with elements. --- browser/main/lib/dataApi/attachmentManagement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index 46560a1a..d1e0ab62 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -84,7 +84,7 @@ function createAttachmentDestinationFolder (destinationStoragePath, noteKey) { function migrateAttachments (markdownContent, storagePath, noteKey) { if (noteKey !== undefined && sander.existsSync(path.join(storagePath, 'images'))) { const attachments = getAttachmentsInMarkdownContent(markdownContent) || [] - if (attachments !== []) { + if (attachments.length) { createAttachmentDestinationFolder(storagePath, noteKey) } for (const attachment of attachments) { From 09188bed4856450e21b047ac62870c52e8b77636 Mon Sep 17 00:00:00 2001 From: ehhc Date: Mon, 30 Jul 2018 18:09:02 +0200 Subject: [PATCH 73/74] might fixes #1786 --> export attachments for markdown notes --- browser/components/MarkdownPreview.js | 15 ++++++++++++++- tests/dataApi/attachmentManagement.test.js | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 1cec5cdd..2e80acf6 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -232,7 +232,20 @@ export default class MarkdownPreview extends React.Component { } handleSaveAsMd () { - this.exportAsDocument('md') + this.exportAsDocument('md', (noteContent, exportTasks) => { + let result = noteContent + if (this.props && this.props.storagePath && this.props.noteKey) { + const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath) + attachmentsAbsolutePaths.forEach((attachment) => { + exportTasks.push({ + src: attachment, + dst: attachmentManagement.DESTINATION_FOLDER + }) + }) + result = attachmentManagement.removeStorageAndNoteReferences(noteContent, this.props.noteKey) + } + return result + }) } handleSaveAsHtml () { diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index 853e0431..a4cc8082 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -319,6 +319,21 @@ it('should remove the all ":storage" and noteKey references', function () { expect(actual).toEqual(expectedOutput) }) +it('should make sure that "removeStorageAndNoteReferences" works with markdown content as well', function () { + const noteKey = 'noteKey' + const testInput = + 'Test input' + + '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + noteKey + path.sep + 'image.jpg](imageName}) \n' + + '[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + noteKey + path.sep + 'pdf.pdf](pdf})' + + const expectedOutput = + 'Test input' + + '![' + systemUnderTest.DESTINATION_FOLDER + path.sep + 'image.jpg](imageName}) \n' + + '[' + systemUnderTest.DESTINATION_FOLDER + path.sep + 'pdf.pdf](pdf})' + const actual = systemUnderTest.removeStorageAndNoteReferences(testInput, noteKey) + expect(actual).toEqual(expectedOutput) +}) + it('should delete the correct attachment folder if a note is deleted', function () { const dummyStorage = {path: 'dummyStoragePath'} const storageKey = 'storageKey' From f0df787bbea6ff91bd2a2c9b6882fba4704b2a61 Mon Sep 17 00:00:00 2001 From: Nguyen Viet Hung Date: Thu, 9 Aug 2018 15:08:52 +0700 Subject: [PATCH 74/74] Fix escape codeblock (#2230) * updated package-lock * added fix and test for escape html in code block * fixed markdown preview render bug * updated comment in escape function * improved escape function * Delete package-lock.json --- browser/components/MarkdownPreview.js | 7 --- browser/lib/markdown-it-sanitize-html.js | 17 +++++-- browser/lib/utils.js | 63 +++++++++++++++++++++--- package-lock.json | 35 ------------- tests/lib/escapeHtmlCharacters-test.js | 27 +++++++++- 5 files changed, 96 insertions(+), 53 deletions(-) delete mode 100644 package-lock.json diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 1cec5cdd..af599f95 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -444,13 +444,6 @@ export default class MarkdownPreview extends React.Component { let { value, codeBlockTheme } = this.props this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme) - - const codeBlocks = value.match(/(```)(.|[\n])*?(```)/g) - if (codeBlocks !== null) { - codeBlocks.forEach((codeBlock) => { - value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock)) - }) - } const renderedHTML = this.markdown.render(value) attachmentManagement.migrateAttachments(value, storagePath, noteKey) this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath) diff --git a/browser/lib/markdown-it-sanitize-html.js b/browser/lib/markdown-it-sanitize-html.js index 6d1a44b6..05e5e7be 100644 --- a/browser/lib/markdown-it-sanitize-html.js +++ b/browser/lib/markdown-it-sanitize-html.js @@ -1,6 +1,7 @@ 'use strict' import sanitizeHtml from 'sanitize-html' +import { escapeHtmlCharacters } from './utils' module.exports = function sanitizePlugin (md, options) { options = options || {} @@ -8,16 +9,26 @@ module.exports = function sanitizePlugin (md, options) { md.core.ruler.after('linkify', 'sanitize_inline', state => { for (let tokenIdx = 0; tokenIdx < state.tokens.length; tokenIdx++) { if (state.tokens[tokenIdx].type === 'html_block') { - state.tokens[tokenIdx].content = sanitizeHtml(state.tokens[tokenIdx].content, options) + state.tokens[tokenIdx].content = sanitizeHtml( + state.tokens[tokenIdx].content, + options + ) } if (state.tokens[tokenIdx].type === 'fence') { - state.tokens[tokenIdx].content = state.tokens[tokenIdx].content.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"') + // escapeHtmlCharacters has better performance + state.tokens[tokenIdx].content = escapeHtmlCharacters( + state.tokens[tokenIdx].content, + { skipSingleQuote: true } + ) } if (state.tokens[tokenIdx].type === 'inline') { 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, options) + inlineTokens[childIdx].content = sanitizeHtml( + inlineTokens[childIdx].content, + options + ) } } } diff --git a/browser/lib/utils.js b/browser/lib/utils.js index 564ed3d2..1d15b722 100644 --- a/browser/lib/utils.js +++ b/browser/lib/utils.js @@ -6,8 +6,12 @@ export function lastFindInArray (array, callback) { } } -export function escapeHtmlCharacters (html, opt = { detectCodeBlock: false }) { +export function escapeHtmlCharacters ( + html, + opt = { detectCodeBlock: false, skipSingleQuote: false } +) { const matchHtmlRegExp = /["'&<>]/g + const matchCodeBlockRegExp = /```/g const escapes = ['"', '&', ''', '<', '>'] let match = null const replaceAt = (str, index, replace) => @@ -15,11 +19,18 @@ export function escapeHtmlCharacters (html, opt = { detectCodeBlock: false }) { replace + str.substr(index + replace.length - (replace.length - 1)) - // detecting code block - while ((match = matchHtmlRegExp.exec(html)) != null) { + while ((match = matchHtmlRegExp.exec(html)) !== null) { const current = { char: match[0], index: match.index } + const codeBlockIndexs = [] + let openCodeBlock = null + // if the detectCodeBlock option is activated then this function should skip + // characters that needed to be escape but located in code block if (opt.detectCodeBlock) { - // position of the nearest line start + // The first type of code block is lines that start with 4 spaces + // Here we check for the \n character located before the character that + // needed to be escape. It means we check for the begining of the line that + // contain that character, then we check if there are 4 spaces next to the + // \n character (the line start with 4 spaces) let previousLineEnd = current.index - 1 while (html[previousLineEnd] !== '\n' && previousLineEnd !== -1) { previousLineEnd-- @@ -31,16 +42,54 @@ export function escapeHtmlCharacters (html, opt = { detectCodeBlock: false }) { html[previousLineEnd + 3] === ' ' && html[previousLineEnd + 4] === ' ' ) { - // so skip it + // skip the current character + continue + } + // The second type of code block is lines that wrapped in ``` + // We will get the position of each ``` + // then push it into an array + // then the array returned will be like this: + // [startCodeblock, endCodeBlock, startCodeBlock, endCodeBlock] + while ((openCodeBlock = matchCodeBlockRegExp.exec(html)) !== null) { + codeBlockIndexs.push(openCodeBlock.index) + } + let shouldSkipChar = false + // we loop through the array of positions + // we skip 2 element as the i index position is the position of ``` that + // open the codeblock and the i + 1 is the position of the ``` that close + // the code block + for (let i = 0; i < codeBlockIndexs.length; i += 2) { + // the i index position is the position of the ``` that open code block + // so we have to + 2 as that position is the position of the first ` in the ```` + // but we need to make sure that the position current character is larger + // that the last ` in the ``` that open the code block so we have to take + // the position of the first ` and + 2 + // the i + 1 index position is the closing ``` so the char must less than it + if ( + current.index > codeBlockIndexs[i] + 2 && + current.index < codeBlockIndexs[i + 1] + ) { + // skip it + shouldSkipChar = true + break + } + } + if (shouldSkipChar) { + // skip the current character continue } } // otherwise, escape it !!! if (current.char === '&') { + // when escaping character & we have to be becareful as the & could be a part + // of an escaped character like " will be came &quot; let nextStr = '' let nextIndex = current.index let escapedStr = false - // maximum length of an escape string is 5. For example ('"') + // maximum length of an escaped string is 5. For example ('"') + // we take the next 5 character of the next string if it is one of the string: + // ['"', '&', ''', '<', '>'] then we will not escape the & character + // as it is a part of the escaped string and should not be escaped while (nextStr.length <= 5) { nextStr += html[nextIndex] nextIndex++ @@ -55,7 +104,7 @@ export function escapeHtmlCharacters (html, opt = { detectCodeBlock: false }) { } } else if (current.char === '"') { html = replaceAt(html, current.index, '"') - } else if (current.char === "'") { + } else if (current.char === "'" && !opt.skipSingleQuote) { html = replaceAt(html, current.index, ''') } else if (current.char === '<') { html = replaceAt(html, current.index, '<') diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 8150228b..00000000 --- a/package-lock.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "boost", - "version": "0.10.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "i18n-2": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/i18n-2/-/i18n-2-0.7.2.tgz", - "integrity": "sha512-Rdh6vfpNhL7q61cNf27x7QGULTi1TcGLVdFb5OJ6dOiJo+EkOTqEg0+3xgyeEMgYhopUBsh2IiSkFkjM+EhEmA==", - "requires": { - "debug": "3.1.0", - "sprintf": "0.1.5" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "sprintf": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/sprintf/-/sprintf-0.1.5.tgz", - "integrity": "sha1-j4PjmpMXwaUCy324BQ5Rxnn27c8=" - } - } -} diff --git a/tests/lib/escapeHtmlCharacters-test.js b/tests/lib/escapeHtmlCharacters-test.js index f13ab297..672ef917 100644 --- a/tests/lib/escapeHtmlCharacters-test.js +++ b/tests/lib/escapeHtmlCharacters-test.js @@ -33,13 +33,38 @@ test('escapeHtmlCharacters should NOT skip code block if that option is NOT enab t.is(actual, expected) }) -test('escapeHtmlCharacters should NOT escape & character if it\'s a part of an escaped character', t => { +test("escapeHtmlCharacters should NOT escape & character if it's a part of an escaped character", t => { const input = 'Do not escape & or " but do escape &' const expected = 'Do not escape & or " but do escape &' const actual = escapeHtmlCharacters(input) t.is(actual, expected) }) +test('escapeHtmlCharacters should skip char if in code block', t => { + const input = ` +\`\`\` + +\`\`\` +dasdasd +dasdasdasd +\`\`\` + +\`\`\` +` + const expected = ` +\`\`\` + +\`\`\` +das<das>dasd +dasdasdasd +\`\`\` + +\`\`\` +` + const actual = escapeHtmlCharacters(input, { detectCodeBlock: true }) + t.is(actual, expected) +}) + test('escapeHtmlCharacters should return the correct result', t => { const input = '& < > " \'' const expected = '& < > " ''