diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index c36a50c1..41d71622 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -14,6 +14,8 @@ import consts from 'browser/lib/consts' import fs from 'fs' const { ipcRenderer } = require('electron') import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' +import TurndownService from 'turndown' +import { gfm } from 'turndown-plugin-gfm' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' @@ -57,6 +59,7 @@ export default class CodeEditor extends React.Component { } this.searchHandler = (e, msg) => this.handleSearch(msg) this.searchState = null + this.scrollToLineHandeler = this.scrollToLine.bind(this) this.formatTable = () => this.handleFormatTable() this.editorActivityHandler = () => this.handleEditorActivity() @@ -125,6 +128,7 @@ export default class CodeEditor extends React.Component { componentDidMount () { const { rulers, enableRulers } = this.props const expandSnippet = this.expandSnippet.bind(this) + eventEmitter.on('line:jump', this.scrollToLineHandeler) const defaultSnippet = [ { @@ -475,7 +479,13 @@ export default class CodeEditor extends React.Component { moveCursorTo (row, col) {} - scrollToLine (num) {} + scrollToLine (event, num) { + const cursor = { + line: num, + ch: 1 + } + this.editor.setCursor(cursor) + } focus () { this.editor.focus() @@ -538,7 +548,11 @@ export default class CodeEditor extends React.Component { ) return prevChar === '](' && nextChar === ')' } - if (dataTransferItem.type.match('image')) { + + const pastedHtml = clipboardData.getData('text/html') + if (pastedHtml !== '') { + this.handlePasteHtml(e, editor, pastedHtml) + } else if (dataTransferItem.type.match('image')) { attachmentManagement.handlePastImageEvent( this, storageKey, @@ -608,6 +622,12 @@ export default class CodeEditor extends React.Component { }) } + handlePasteHtml (e, editor, pastedHtml) { + e.preventDefault() + const markdown = this.turndownService.turndown(pastedHtml) + editor.replaceSelection(markdown) + } + mapNormalResponse (response, pastedTxt) { return this.decodeResponse(response).then(body => { return new Promise((resolve, reject) => { diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index 70df16a0..89f7746c 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -64,6 +64,10 @@ class MarkdownEditor extends React.Component { }) } + setValue (value) { + this.refs.code.setValue(value) + } + handleChange (e) { this.value = this.refs.code.value this.props.onChange(e) @@ -300,6 +304,7 @@ class MarkdownEditor extends React.Component { noteKey={noteKey} customCSS={config.preview.customCSS} allowCustomCSS={config.preview.allowCustomCSS} + lineThroughCheckbox={config.preview.lineThroughCheckbox} /> ) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index d8986647..0af16e31 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -16,7 +16,6 @@ import convertModeName from 'browser/lib/convertModeName' 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 context from 'browser/lib/context' import i18n from 'browser/lib/i18n' import fs from 'fs' @@ -80,7 +79,6 @@ function buildStyle ( url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'), url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype'); } -${allowCustomCSS ? customCSS : ''} ${markdownStyle} body { @@ -88,6 +86,11 @@ body { font-size: ${fontSize}px; ${scrollPastEnd && 'padding-bottom: 90vh;'} } +@media print { + body { + padding-bottom: initial; + } +} code { font-family: '${codeBlockFontFamily.join("','")}'; background-color: rgba(0,0,0,0.04); @@ -144,6 +147,8 @@ body p { display: none } } + +${allowCustomCSS ? customCSS : ''} ` } @@ -325,9 +330,7 @@ export default class MarkdownPreview extends React.Component { allowCustomCSS, customCSS ) - let body = this.markdown.render( - escapeHtmlCharacters(noteContent, { detectCodeBlock: true }) - ) + let body = this.markdown.render(noteContent) const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent( noteContent, @@ -484,10 +487,6 @@ export default class MarkdownPreview extends React.Component { eventEmitter.on('export:save-md', this.saveAsMdHandler) eventEmitter.on('export:save-html', this.saveAsHtmlHandler) eventEmitter.on('print', this.printHandler) - eventEmitter.on('config-renew', () => { - this.markdown.updateConfig() - this.rewriteIframe() - }) } componentWillUnmount () { @@ -531,7 +530,8 @@ export default class MarkdownPreview extends React.Component { prevProps.smartQuotes !== this.props.smartQuotes || prevProps.sanitize !== this.props.sanitize || prevProps.smartArrows !== this.props.smartArrows || - prevProps.breaks !== this.props.breaks + prevProps.breaks !== this.props.breaks || + prevProps.lineThroughCheckbox !== this.props.lineThroughCheckbox ) { this.initMarkdown() this.rewriteIframe() @@ -864,6 +864,15 @@ export default class MarkdownPreview extends React.Component { return } + const regexIsLine = /^:line:[0-9]/ + if (regexIsLine.test(linkHash)) { + const numberPattern = /\d+/g + + const lineNumber = parseInt(linkHash.match(numberPattern)[0]) + eventEmitter.emit('line:jump', lineNumber) + return + } + // this will match the old link format storage.key-note.key // e.g. // 877f99c3268608328037-1c211eb7dcb463de6490 diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index d714125a..ca2d3108 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -20,12 +20,18 @@ class MarkdownSplitEditor extends React.Component { } } + setValue (value) { + this.refs.code.setValue(value) + } + handleOnChange () { this.value = this.refs.code.value this.props.onChange() } handleScroll (e) { + if (!this.props.config.preview.scrollSync) return + const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document') const codeDoc = _.get(this, 'refs.code.editor.doc') let srcTop, srcHeight, targetTop, targetHeight @@ -192,6 +198,7 @@ class MarkdownSplitEditor extends React.Component { noteKey={noteKey} customCSS={config.preview.customCSS} allowCustomCSS={config.preview.allowCustomCSS} + lineThroughCheckbox={config.preview.lineThroughCheckbox} /> ) diff --git a/browser/components/NoteItem.js b/browser/components/NoteItem.js index 600b7e2d..2fc70a39 100644 --- a/browser/components/NoteItem.js +++ b/browser/components/NoteItem.js @@ -24,16 +24,19 @@ const TagElement = ({ tagName }) => ( /** * @description Tag element list component. * @param {Array|null} tags + * @param {boolean} showTagsAlphabetically * @return {React.Component} */ -const TagElementList = tags => { +const TagElementList = (tags, showTagsAlphabetically) => { if (!isArray(tags)) { return [] } - const tagElements = tags.map(tag => TagElement({ tagName: tag })) - - return tagElements + if (showTagsAlphabetically) { + return _.sortBy(tags).map(tag => TagElement({ tagName: tag })) + } else { + return tags.map(tag => TagElement({ tagName: tag })) + } } /** @@ -55,7 +58,8 @@ const NoteItem = ({ pathname, storageName, folderName, - viewType + viewType, + showTagsAlphabetically }) => (
{percentageOfTodo}%
onClearCheckboxClick(e)}>clear
+{i18n.__('Dear Boostnote users,')}
-{i18n.__('Thank you for using Boostnote!')}
-{i18n.__('Boostnote is used in about 200 different countries and regions by an awesome community of developers.')}
{i18n.__('To support our growing userbase, and satisfy community expectations,')}
-{i18n.__('we would like to invest more time and resources in this project.')}
+{i18n.__('We launched IssueHunt which is an issue-based crowdfunding / sourcing platform for open source projects.')}
+{i18n.__('Anyone can put a bounty on not only a bug but also on OSS feature requests listed on IssueHunt. Collected funds will be distributed to project owners and contributors.')}
{i18n.__('If you use Boostnote and see its potential, help us out by supporting the project on OpenCollective!')}
+{i18n.__('### Sustainable Open Source Ecosystem')}
+{i18n.__('We discussed about open-source ecosystem and IssueHunt concept with the Boostnote team repeatedly. We actually also discussed with Matz who father of Ruby.')}
+{i18n.__('The original reason why we made IssueHunt was to reward our contributors of Boostnote project. We’ve got tons of Github stars and hundred of contributors in two years.')}
+{i18n.__('We thought that it will be nice if we can pay reward for our contributors.')}
{i18n.__('Thanks,')}
+{i18n.__('### We believe Meritocracy')}
+{i18n.__('We think developers who has skill and did great things must be rewarded properly.')}
+{i18n.__('OSS projects are used in everywhere on the internet, but no matter how they great, most of owners of those projects need to have another job to sustain their living.')}
+{i18n.__('It sometimes looks like exploitation.')}
+{i18n.__('We’ve realized IssueHunt could enhance sustainability of open-source ecosystem.')}
+{i18n.__('As same as issues of Boostnote are already funded on IssueHunt, your open-source projects can be also started funding from now.')}
+{i18n.__('Thank you,')}
{i18n.__('The Boostnote Team')}