diff --git a/.eslintrc b/.eslintrc index 67b6c8feb..94de23cf0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,6 @@ { "extends": ["standard", "standard-jsx", "plugin:react/recommended", "prettier"], - "plugins": ["react", "prettier"], + "plugins": ["react", "prettier", "jest"], "rules": { "no-useless-escape": 0, "prefer-const": ["warn", { @@ -13,8 +13,7 @@ "react/no-string-refs": 0, "react/no-find-dom-node": "warn", "react/no-render-return-value": "warn", - "react/no-deprecated": "warn", - "prettier/prettier": ["error"] + "react/no-deprecated": "warn" }, "globals": { "FileReader": true, diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..c85426819 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +node_modules +compiled +dist +*.md \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 515c6cd51..83228faf3 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,6 @@ { "singleQuote": true, "semi": false, + "tabWidth": 2, "jsxSingleQuote": true } \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 71b656710..3a7db4953 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ deploy: 'on': branch: master provider: script - script: if [ ${TRAVIS_NODE_VERSION} = "stable" ];then docker run -v $(pwd):$(pwd) -t snapcore/snapcraft sh -c "apt update -qq + script: + if [ ${TRAVIS_NODE_VERSION} = "stable" ];then docker run -v $(pwd):$(pwd) -t snapcore/snapcraft sh -c "apt update -qq && cd $(pwd) && snapcraft && snapcraft push *.snap --release edge"; fi skip_cleanup: true diff --git a/.vscode/launch.json b/.vscode/launch.json index 9d1cc4ec4..e81264c31 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,7 +4,6 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - { "type": "node", "request": "launch", @@ -38,4 +37,4 @@ "configurations": ["BoostNote Main", "BoostNote Renderer"] } ] -} \ No newline at end of file +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index c6664225f..31c62b41d 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -10,10 +10,10 @@ "script": "watch", "isBackground": true, "presentation": { - "reveal": "always", + "reveal": "always" }, "problemMatcher": { - "pattern":[ + "pattern": [ { "regexp": "^([^\\\\s].*)\\\\((\\\\d+,\\\\d+)\\\\):\\\\s*(.*)$", "file": 1, @@ -24,4 +24,4 @@ } } ] -} \ No newline at end of file +} diff --git a/appdmg.json b/appdmg.json index 8d0ecad40..ff6edf1f9 100644 --- a/appdmg.json +++ b/appdmg.json @@ -5,7 +5,11 @@ "icon-size": 80, "contents": [ { "x": 448, "y": 344, "type": "link", "path": "/Applications" }, - { "x": 192, "y": 344, "type": "file", "path": "dist/Boostnote-darwin-x64/Boostnote.app" } + { + "x": 192, + "y": 344, + "type": "file", + "path": "dist/Boostnote-darwin-x64/Boostnote.app" + } ] - } diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index a5bb009ad..383848a9d 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -39,7 +39,7 @@ const buildCMRulers = (rulers, enableRulers) => })) : [] -function translateHotkey(hotkey) { +function translateHotkey (hotkey) { return hotkey .replace(/\s*\+\s*/g, '-') .replace(/Command/g, 'Cmd') @@ -47,7 +47,7 @@ function translateHotkey(hotkey) { } export default class CodeEditor extends React.Component { - constructor(props) { + constructor (props) { super(props) this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, { @@ -102,7 +102,7 @@ export default class CodeEditor extends React.Component { this.formatTable = () => this.handleFormatTable() if (props.switchPreview !== 'RIGHTCLICK') { - this.contextMenuHandler = function(editor, event) { + this.contextMenuHandler = function (editor, event) { const menu = buildEditorContextMenu(editor, event) if (menu != null) { setTimeout(() => menu.popup(remote.getCurrentWindow()), 30) @@ -115,24 +115,24 @@ export default class CodeEditor extends React.Component { this.turndownService = createTurndownService() } - handleSearch(msg) { + handleSearch (msg) { const cm = this.editor const component = this if (component.searchState) cm.removeOverlay(component.searchState) if (msg.length < 1) return - cm.operation(function() { + cm.operation(function () { component.searchState = makeOverlay(msg, 'searching') cm.addOverlay(component.searchState) - function makeOverlay(query, style) { + function makeOverlay (query, style) { query = new RegExp( query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), 'gi' ) return { - token: function(stream) { + token: function (stream) { query.lastIndex = stream.pos var match = query.exec(stream.string) if (match && match.index === stream.pos) { @@ -149,7 +149,7 @@ export default class CodeEditor extends React.Component { }) } - handleFormatTable() { + handleFormatTable () { this.tableEditor.formatAll( options({ textWidthOptions: {} @@ -157,19 +157,19 @@ export default class CodeEditor extends React.Component { ) } - handleEditorActivity() { + handleEditorActivity () { if (!this.textEditorInterface.transaction) { this.updateTableEditorState() } } - updateDefaultKeyMap() { + updateDefaultKeyMap () { const { hotkey } = this.props const self = this const expandSnippet = snippetManager.expandSnippet this.defaultKeyMap = CodeMirror.normalizeKeyMap({ - Tab: function(cm) { + Tab: function (cm) { const cursor = cm.getCursor() const line = cm.getLine(cursor.line) const cursorPosition = cursor.ch @@ -211,17 +211,17 @@ export default class CodeEditor extends React.Component { } } }, - 'Cmd-Left': function(cm) { + 'Cmd-Left': function (cm) { cm.execCommand('goLineLeft') }, - 'Cmd-T': function(cm) { + 'Cmd-T': function (cm) { // Do nothing }, - [translateHotkey(hotkey.insertDate)]: function(cm) { + [translateHotkey(hotkey.insertDate)]: function (cm) { const dateNow = new Date() cm.replaceSelection(dateNow.toLocaleDateString()) }, - [translateHotkey(hotkey.insertDateTime)]: function(cm) { + [translateHotkey(hotkey.insertDateTime)]: function (cm) { const dateNow = new Date() cm.replaceSelection(dateNow.toLocaleString()) }, @@ -273,7 +273,7 @@ export default class CodeEditor extends React.Component { }) } - updateTableEditorState() { + updateTableEditorState () { const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions) if (active) { if (this.extraKeysMode !== 'editor') { @@ -289,7 +289,7 @@ export default class CodeEditor extends React.Component { } } - componentDidMount() { + componentDidMount () { const { rulers, enableRulers, enableMarkdownLint, RTL } = this.props eventEmitter.on('line:jump', this.scrollToLineHandeler) @@ -500,7 +500,7 @@ export default class CodeEditor extends React.Component { this.initialHighlighting() } - getWordBeforeCursor(line, lineNumber, cursorPosition) { + getWordBeforeCursor (line, lineNumber, cursorPosition) { let wordBeforeCursor = '' const originCursorPosition = cursorPosition const emptyChars = /\t|\s|\r|\n|\$/ @@ -537,11 +537,11 @@ export default class CodeEditor extends React.Component { } } - quitEditor() { + quitEditor () { document.querySelector('textarea').blur() } - componentWillUnmount() { + componentWillUnmount () { this.editor.off('focus', this.focusHandler) this.editor.off('blur', this.blurHandler) this.editor.off('change', this.changeHandler) @@ -556,7 +556,7 @@ export default class CodeEditor extends React.Component { eventEmitter.off('code:format-table', this.formatTable) } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate (prevProps, prevState) { let needRefresh = false const { rulers, @@ -695,7 +695,7 @@ export default class CodeEditor extends React.Component { } } - getCodeEditorLintConfig() { + getCodeEditorLintConfig () { const { mode } = this.props const checkMarkdownNoteIsOpen = mode === 'Boost Flavored Markdown' @@ -707,7 +707,7 @@ export default class CodeEditor extends React.Component { : false } - validatorOfMarkdown(text, updateLinting) { + validatorOfMarkdown (text, updateLinting) { const { customMarkdownLintConfig } = this.props let lintConfigJson try { @@ -747,7 +747,7 @@ export default class CodeEditor extends React.Component { }) } - setMode(mode) { + setMode (mode) { let syntax = CodeMirror.findModeByName(convertModeName(mode || 'text')) if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') @@ -755,7 +755,7 @@ export default class CodeEditor extends React.Component { CodeMirror.autoLoadMode(this.editor, syntax.mode) } - handleChange(editor, changeObject) { + handleChange (editor, changeObject) { spellcheck.handleChange(editor, changeObject) // The current note contains an toc. We'll check for changes on headlines. @@ -798,13 +798,13 @@ export default class CodeEditor extends React.Component { } } - linePossibleContainsHeadline(currentLine) { + linePossibleContainsHeadline (currentLine) { // We can't check if the line start with # because when some write text before // the # we also need to update the toc return currentLine.includes('# ') } - incrementLines(start, linesAdded, linesRemoved, editor) { + incrementLines (start, linesAdded, linesRemoved, editor) { const highlightedLines = editor.options.linesHighlighted const totalHighlightedLines = highlightedLines.length @@ -839,7 +839,7 @@ export default class CodeEditor extends React.Component { } } - handleHighlight(editor, changeObject) { + handleHighlight (editor, changeObject) { const lines = editor.options.linesHighlighted if (!lines.includes(changeObject)) { @@ -862,7 +862,7 @@ export default class CodeEditor extends React.Component { } } - updateHighlight(editor, changeObject) { + updateHighlight (editor, changeObject) { const linesAdded = changeObject.text.length - 1 const linesRemoved = changeObject.removed.length - 1 @@ -893,9 +893,9 @@ export default class CodeEditor extends React.Component { this.incrementLines(start, linesAdded, linesRemoved, editor) } - moveCursorTo(row, col) {} + moveCursorTo (row, col) {} - scrollToLine(event, num) { + scrollToLine (event, num) { const cursor = { line: num, ch: 1 @@ -906,15 +906,15 @@ export default class CodeEditor extends React.Component { this.editor.scrollTo(null, top - middleHeight - 5) } - focus() { + focus () { this.editor.focus() } - blur() { + blur () { this.editor.blur() } - reload() { + reload () { // Change event shouldn't be fired when switch note this.editor.off('change', this.changeHandler) this.value = this.props.value @@ -925,7 +925,7 @@ export default class CodeEditor extends React.Component { this.editor.refresh() } - setValue(value) { + setValue (value) { const cursor = this.editor.getCursor() this.editor.setValue(value) this.editor.setCursor(cursor) @@ -936,7 +936,7 @@ export default class CodeEditor extends React.Component { * @param {Number} lineNumber * @param {String} content */ - setLineContent(lineNumber, content) { + setLineContent (lineNumber, content) { const prevContent = this.editor.getLine(lineNumber) const prevContentLength = prevContent ? prevContent.length : 0 this.editor.replaceRange( @@ -946,7 +946,7 @@ export default class CodeEditor extends React.Component { ) } - handleDropImage(dropEvent) { + handleDropImage (dropEvent) { dropEvent.preventDefault() const { storageKey, noteKey } = this.props attachmentManagement.handleAttachmentDrop( @@ -957,16 +957,16 @@ export default class CodeEditor extends React.Component { ) } - insertAttachmentMd(imageMd) { + insertAttachmentMd (imageMd) { this.editor.replaceSelection(imageMd) } - autoDetectLanguage(content) { + autoDetectLanguage (content) { const res = hljs.highlightAuto(content, Object.keys(languageMaps)) this.setMode(languageMaps[res.language]) } - handlePaste(editor, forceSmartPaste) { + handlePaste (editor, forceSmartPaste) { const { storageKey, noteKey, fetchUrlTitle, enableSmartPaste } = this.props const isURL = str => @@ -1078,13 +1078,13 @@ export default class CodeEditor extends React.Component { } } - handleScroll(e) { + handleScroll (e) { if (this.props.onScroll) { this.props.onScroll(e) } } - handlePasteUrl(editor, pastedTxt) { + handlePasteUrl (editor, pastedTxt) { let taggedUrl = `<${pastedTxt}>` let urlToFetch = pastedTxt let titleMark = '' @@ -1134,16 +1134,16 @@ export default class CodeEditor extends React.Component { }) } - handlePasteHtml(editor, pastedHtml) { + handlePasteHtml (editor, pastedHtml) { const markdown = this.turndownService.turndown(pastedHtml) editor.replaceSelection(markdown) } - handlePasteText(editor, pastedTxt) { + handlePasteText (editor, pastedTxt) { editor.replaceSelection(pastedTxt) } - mapNormalResponse(response, pastedTxt) { + mapNormalResponse (response, pastedTxt) { return this.decodeResponse(response).then(body => { return new Promise((resolve, reject) => { try { @@ -1165,7 +1165,7 @@ export default class CodeEditor extends React.Component { }) } - initialHighlighting() { + initialHighlighting () { if (this.editor.options.linesHighlighted == null) { return } @@ -1187,12 +1187,12 @@ export default class CodeEditor extends React.Component { } } - restartHighlighting() { + restartHighlighting () { this.editor.options.linesHighlighted = this.props.linesHighlighted this.initialHighlighting() } - mapImageResponse(response, pastedTxt) { + mapImageResponse (response, pastedTxt) { return new Promise((resolve, reject) => { try { const url = response.url @@ -1205,7 +1205,7 @@ export default class CodeEditor extends React.Component { }) } - decodeResponse(response) { + decodeResponse (response) { const headers = response.headers const _charset = headers.has('content-type') ? this.extractContentTypeCharset(headers.get('content-type')) @@ -1225,7 +1225,7 @@ export default class CodeEditor extends React.Component { }) } - extractContentTypeCharset(contentType) { + extractContentTypeCharset (contentType) { return contentType .split(';') .filter(str => { @@ -1239,7 +1239,7 @@ export default class CodeEditor extends React.Component { })[0] } - render() { + render () { const { className, fontSize } = this.props const fontFamily = normalizeEditorFontFamily(this.props.fontFamily) const width = this.props.width @@ -1258,7 +1258,7 @@ export default class CodeEditor extends React.Component { ) } - createSpellCheckPanel() { + createSpellCheckPanel () { const panel = document.createElement('div') panel.className = 'panel bottom' panel.id = 'editor-bottom-panel' diff --git a/browser/components/ColorPicker.js b/browser/components/ColorPicker.js index 4d4e80e45..da2c9cf1a 100644 --- a/browser/components/ColorPicker.js +++ b/browser/components/ColorPicker.js @@ -7,7 +7,7 @@ import styles from './ColorPicker.styl' const componentHeight = 330 class ColorPicker extends React.Component { - constructor(props) { + constructor (props) { super(props) this.state = { @@ -18,21 +18,21 @@ class ColorPicker extends React.Component { this.handleConfirm = this.handleConfirm.bind(this) } - componentWillReceiveProps(nextProps) { + componentWillReceiveProps (nextProps) { this.onColorChange(nextProps.color) } - onColorChange(color) { + onColorChange (color) { this.setState({ color }) } - handleConfirm() { + handleConfirm () { this.props.onConfirm(this.state.color) } - render() { + render () { const { onReset, onCancel, targetRect } = this.props const { color } = this.state diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index a8b888914..ed88df5c1 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -10,7 +10,7 @@ import ConfigManager from 'browser/main/lib/ConfigManager' import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement' class MarkdownEditor extends React.Component { - constructor(props) { + constructor (props) { super(props) // char codes for ctrl + w @@ -32,29 +32,29 @@ class MarkdownEditor extends React.Component { this.lockEditorCode = () => this.handleLockEditor() } - componentDidMount() { + componentDidMount () { this.value = this.refs.code.value eventEmitter.on('editor:lock', this.lockEditorCode) eventEmitter.on('editor:focus', this.focusEditor.bind(this)) } - componentDidUpdate() { + componentDidUpdate () { this.value = this.refs.code.value } - componentWillReceiveProps(props) { + componentWillReceiveProps (props) { if (props.value !== this.props.value) { this.queueRendering(props.value) } } - componentWillUnmount() { + componentWillUnmount () { this.cancelQueue() eventEmitter.off('editor:lock', this.lockEditorCode) eventEmitter.off('editor:focus', this.focusEditor.bind(this)) } - focusEditor() { + focusEditor () { this.setState( { status: 'CODE' @@ -65,33 +65,33 @@ class MarkdownEditor extends React.Component { ) } - queueRendering(value) { + queueRendering (value) { clearTimeout(this.renderTimer) this.renderTimer = setTimeout(() => { this.renderPreview(value) }, 500) } - cancelQueue() { + cancelQueue () { clearTimeout(this.renderTimer) } - renderPreview(value) { + renderPreview (value) { this.setState({ renderValue: value }) } - setValue(value) { + setValue (value) { this.refs.code.setValue(value) } - handleChange(e) { + handleChange (e) { this.value = this.refs.code.value this.props.onChange(e) } - handleContextMenu(e) { + handleContextMenu (e) { if (this.state.isLocked) return const { config } = this.props if (config.editor.switchPreview === 'RIGHTCLICK') { @@ -116,7 +116,7 @@ class MarkdownEditor extends React.Component { } } - handleBlur(e) { + handleBlur (e) { if (this.state.isLocked) return this.setState({ keyPressed: new Set() }) const { config } = this.props @@ -139,7 +139,7 @@ class MarkdownEditor extends React.Component { } } - handleDoubleClick(e) { + handleDoubleClick (e) { if (this.state.isLocked) return this.setState({ keyPressed: new Set() }) const { config } = this.props @@ -156,11 +156,11 @@ class MarkdownEditor extends React.Component { } } - handlePreviewMouseDown(e) { + handlePreviewMouseDown (e) { this.previewMouseDownedAt = new Date() } - handlePreviewMouseUp(e) { + handlePreviewMouseUp (e) { const { config } = this.props if ( config.editor.switchPreview === 'BLUR' && @@ -178,7 +178,7 @@ class MarkdownEditor extends React.Component { } } - handleCheckboxClick(e) { + handleCheckboxClick (e) { e.preventDefault() e.stopPropagation() const idMatch = /checkbox-([0-9]+)/ @@ -204,7 +204,7 @@ class MarkdownEditor extends React.Component { } } - focus() { + focus () { if (this.state.status === 'PREVIEW') { this.setState( { @@ -220,13 +220,13 @@ class MarkdownEditor extends React.Component { eventEmitter.emit('topbar:togglelockbutton', this.state.status) } - reload() { + reload () { this.refs.code.reload() this.cancelQueue() this.renderPreview(this.props.value) } - handleKeyDown(e) { + handleKeyDown (e) { const { config } = this.props if (this.state.status !== 'CODE') return false const keyPressed = this.state.keyPressed @@ -253,7 +253,7 @@ class MarkdownEditor extends React.Component { } } - addMdAroundWord(mdElement) { + addMdAroundWord (mdElement) { if (this.refs.code.editor.getSelection()) { return this.addMdAroundSelection(mdElement) } @@ -267,13 +267,13 @@ class MarkdownEditor extends React.Component { }) } - addMdAroundSelection(mdElement) { + addMdAroundSelection (mdElement) { this.refs.code.editor.replaceSelection( `${mdElement}${this.refs.code.editor.getSelection()}${mdElement}` ) } - handleDropImage(dropEvent) { + handleDropImage (dropEvent) { dropEvent.preventDefault() const { storageKey, noteKey } = this.props @@ -298,17 +298,17 @@ class MarkdownEditor extends React.Component { ) } - handleKeyUp(e) { + handleKeyUp (e) { const keyPressed = this.state.keyPressed keyPressed.delete(e.keyCode) this.setState({ keyPressed }) } - handleLockEditor() { + handleLockEditor () { this.setState({ isLocked: !this.state.isLocked }) } - render() { + render () { const { className, value, diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 75635c89b..e242fc8a2 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types' import React from 'react' +import { connect } from 'react-redux' import Markdown from 'browser/lib/markdown' import _ from 'lodash' import CodeMirror from 'codemirror' @@ -20,6 +21,7 @@ import { escapeHtmlCharacters } from 'browser/lib/utils' import yaml from 'js-yaml' import { render } from 'react-dom' import Carousel from 'react-image-carousel' +import { push } from 'connected-react-router' import ConfigManager from '../main/lib/ConfigManager' import uiThemes from 'browser/lib/ui-themes' import i18n from 'browser/lib/i18n' @@ -57,7 +59,7 @@ const CSS_FILES = [ * @param {String} [opts.customCSS] Will be added to bottom, only if `opts.allowCustomCSS` is truthy * @returns {String} */ -function buildStyle(opts) { +function buildStyle (opts) { const { fontFamily, fontSize, @@ -229,7 +231,7 @@ const defaultCodeBlockFontFamily = [ // return the line number of the line that used to generate the specified element // return -1 if the line is not found -function getSourceLineNumberByElement(element) { +function getSourceLineNumberByElement (element) { let isHasLineNumber = element.dataset.line !== undefined let parent = element while (!isHasLineNumber && parent.parentElement !== null) { @@ -239,8 +241,8 @@ function getSourceLineNumberByElement(element) { return parent.dataset.line !== undefined ? parseInt(parent.dataset.line) : -1 } -export default class MarkdownPreview extends React.Component { - constructor(props) { +class MarkdownPreview extends React.Component { + constructor (props) { super(props) this.contextMenuHandler = e => this.handleContextMenu(e) @@ -264,7 +266,7 @@ export default class MarkdownPreview extends React.Component { this.initMarkdown() } - initMarkdown() { + initMarkdown () { const { smartQuotes, sanitize, breaks } = this.props this.markdown = new Markdown({ typographer: smartQuotes, @@ -273,17 +275,17 @@ export default class MarkdownPreview extends React.Component { }) } - handleCheckboxClick(e) { + handleCheckboxClick (e) { this.props.onCheckboxClick(e) } - handleScroll(e) { + handleScroll (e) { if (this.props.onScroll) { this.props.onScroll(e) } } - handleContextMenu(event) { + handleContextMenu (event) { const menu = buildMarkdownPreviewContextMenu(this, event) const switchPreview = ConfigManager.get().editor.switchPreview if (menu != null && switchPreview !== 'RIGHTCLICK') { @@ -293,11 +295,11 @@ export default class MarkdownPreview extends React.Component { } } - handleDoubleClick(e) { + handleDoubleClick (e) { if (this.props.onDoubleClick != null) this.props.onDoubleClick(e) } - handleMouseDown(e) { + handleMouseDown (e) { const config = ConfigManager.get() const clickElement = e.target const targetTag = clickElement.tagName // The direct parent HTML of where was clicked ie "BODY" or "DIV" @@ -327,7 +329,7 @@ export default class MarkdownPreview extends React.Component { this.props.onMouseDown(e) } - handleMouseUp(e) { + handleMouseUp (e) { if (!this.props.onMouseUp) return if (e.target != null && e.target.tagName === 'A') { return null @@ -335,15 +337,15 @@ export default class MarkdownPreview extends React.Component { if (this.props.onMouseUp != null) this.props.onMouseUp(e) } - handleSaveAsText() { + handleSaveAsText () { this.exportAsDocument('txt') } - handleSaveAsMd() { + handleSaveAsMd () { this.exportAsDocument('md') } - htmlContentFormatter(noteContent, exportTasks, targetDir) { + htmlContentFormatter (noteContent, exportTasks, targetDir) { const { fontFamily, fontSize, @@ -400,7 +402,7 @@ export default class MarkdownPreview extends React.Component { ` } - handleSaveAsHtml() { + handleSaveAsHtml () { this.exportAsDocument('html', (noteContent, exportTasks, targetDir) => Promise.resolve( this.htmlContentFormatter(noteContent, exportTasks, targetDir) @@ -408,7 +410,7 @@ export default class MarkdownPreview extends React.Component { ) } - handleSaveAsPdf() { + handleSaveAsPdf () { this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => { const printout = new remote.BrowserWindow({ show: false, @@ -430,11 +432,11 @@ export default class MarkdownPreview extends React.Component { }) } - handlePrint() { + handlePrint () { this.refs.root.contentWindow.print() } - exportAsDocument(fileType, contentFormatter) { + exportAsDocument (fileType, contentFormatter) { const options = { filters: [{ name: 'Documents', extensions: [fileType] }], properties: ['openFile', 'createDirectory'] @@ -465,7 +467,7 @@ export default class MarkdownPreview extends React.Component { }) } - fixDecodedURI(node) { + fixDecodedURI (node) { if ( node && node.children.length === 1 && @@ -482,7 +484,7 @@ export default class MarkdownPreview extends React.Component { * @param {string[]} splitWithCodeTag Array of HTML strings separated by three ``` * @returns {string} HTML in which special characters between three ``` have been converted */ - escapeHtmlCharactersInCodeTag(splitWithCodeTag) { + escapeHtmlCharactersInCodeTag (splitWithCodeTag) { for (let index = 0; index < splitWithCodeTag.length; index++) { const codeTagRequired = splitWithCodeTag[index] !== '```' && index < splitWithCodeTag.length - 1 @@ -503,7 +505,7 @@ export default class MarkdownPreview extends React.Component { return result } - getScrollBarStyle() { + getScrollBarStyle () { const { theme } = this.props return uiThemes.some(item => item.name === theme && item.isDark) @@ -511,7 +513,7 @@ export default class MarkdownPreview extends React.Component { : scrollBarStyle } - componentDidMount() { + componentDidMount () { const { onDrop } = this.props this.refs.root.setAttribute('sandbox', 'allow-scripts') @@ -569,7 +571,7 @@ export default class MarkdownPreview extends React.Component { eventEmitter.on('print', this.printHandler) } - componentWillUnmount() { + componentWillUnmount () { const { onDrop } = this.props this.refs.root.contentWindow.document.body.removeEventListener( @@ -611,7 +613,7 @@ export default class MarkdownPreview extends React.Component { eventEmitter.off('print', this.printHandler) } - componentDidUpdate(prevProps) { + componentDidUpdate (prevProps) { // actual rewriteIframe function should be called only once let needsRewriteIframe = false if (prevProps.value !== this.props.value) needsRewriteIframe = true @@ -653,7 +655,7 @@ export default class MarkdownPreview extends React.Component { } } - getStyleParams() { + getStyleParams () { const { fontSize, lineNumber, @@ -694,7 +696,7 @@ export default class MarkdownPreview extends React.Component { } } - applyStyle() { + applyStyle () { const { fontFamily, fontSize, @@ -724,7 +726,7 @@ export default class MarkdownPreview extends React.Component { }) } - getCodeThemeLink(name) { + getCodeThemeLink (name) { const theme = consts.THEMES.find(theme => theme.name === name) return theme != null @@ -732,7 +734,7 @@ export default class MarkdownPreview extends React.Component { : `${appPath}/node_modules/codemirror/theme/elegant.css` } - rewriteIframe() { + rewriteIframe () { _.forEach( this.refs.root.contentWindow.document.querySelectorAll( 'input[type="checkbox"]' @@ -965,7 +967,7 @@ export default class MarkdownPreview extends React.Component { } } - setImgOnClickEventHelper(img, rect) { + setImgOnClickEventHelper (img, rect) { img.onclick = () => { const widthMagnification = document.body.clientWidth / img.width const heightMagnification = document.body.clientHeight / img.height @@ -1036,7 +1038,7 @@ export default class MarkdownPreview extends React.Component { } } - handleResize() { + handleResize () { _.forEach( this.refs.root.contentWindow.document.querySelectorAll('svg[ratio]'), el => { @@ -1045,11 +1047,11 @@ export default class MarkdownPreview extends React.Component { ) } - focus() { + focus () { this.refs.root.focus() } - getWindow() { + getWindow () { return this.refs.root.contentWindow } @@ -1057,7 +1059,7 @@ export default class MarkdownPreview extends React.Component { * @public * @param {Number} targetRow */ - scrollToRow(targetRow) { + scrollToRow (targetRow) { const blocks = this.getWindow().document.querySelectorAll( 'body>[data-line]' ) @@ -1078,16 +1080,16 @@ export default class MarkdownPreview extends React.Component { * @param {Number} x * @param {Number} y */ - scrollTo(x, y) { + scrollTo (x, y) { this.getWindow().document.body.scrollTo(x, y) } - preventImageDroppedHandler(e) { + preventImageDroppedHandler (e) { e.preventDefault() e.stopPropagation() } - notify(title, options) { + notify (title, options) { if (global.process.platform === 'win32') { options.icon = path.join( 'file://', @@ -1098,11 +1100,12 @@ export default class MarkdownPreview extends React.Component { return new window.Notification(title, options) } - handleLinkClick(e) { + handleLinkClick (e) { e.preventDefault() e.stopPropagation() const rawHref = e.target.getAttribute('href') + const { dispatch } = this.props if (!rawHref) return // not checked href because parser will create file://... string for [empty link]() const parser = document.createElement('a') @@ -1110,6 +1113,8 @@ export default class MarkdownPreview extends React.Component { const isStartWithHash = rawHref[0] === '#' const { href, hash } = parser + if (!rawHref) return // not checked href because parser will create file://... string for [empty link]() + const linkHash = hash === '' ? rawHref : hash // needed because we're having special link formats that are removed by parser e.g. :line:10 const extractIdRegex = /file:\/\/.*main.?\w*.html#/ // file://path/to/main(.development.)html @@ -1156,11 +1161,18 @@ export default class MarkdownPreview extends React.Component { return } + const regexIsTagLink = /^:tag:#([\w]+)$/ + if (regexIsTagLink.test(rawHref)) { + const tag = rawHref.match(regexIsTagLink)[1] + dispatch(push(`/tags/${encodeURIComponent(tag)}`)) + return + } + // other case this.openExternal(href) } - openExternal(href) { + openExternal (href) { try { const success = shell.openExternal(href) || shell.openExternal(decodeURI(href)) @@ -1171,7 +1183,7 @@ export default class MarkdownPreview extends React.Component { } } - render() { + render () { const { className, style, tabIndex } = this.props return (