diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index f02a146a..d0e2f505 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -279,6 +279,7 @@ class MarkdownEditor extends React.Component { lineNumber={config.preview.lineNumber} indentSize={editorIndentSize} scrollPastEnd={config.preview.scrollPastEnd} + smartQuotes={config.preview.smartQuotes} ref='preview' onContextMenu={(e) => this.handleContextMenu(e)} onDoubleClick={(e) => this.handleDoubleClick(e)} diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 2bb42291..e4298a71 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types' import React from 'react' -import markdown from 'browser/lib/markdown' +import Markdown from 'browser/lib/markdown' import _ from 'lodash' import CodeMirror from 'codemirror' import 'codemirror-mode-elixir' @@ -130,6 +130,13 @@ export default class MarkdownPreview extends React.Component { this.printHandler = () => this.handlePrint() this.linkClickHandler = this.handlelinkClick.bind(this) + this.initMarkdown = this.initMarkdown.bind(this) + this.initMarkdown() + } + + initMarkdown () { + const { smartQuotes } = this.props + this.markdown = new Markdown({ typographer: smartQuotes }) } handlePreviewAnchorClick (e) { @@ -198,7 +205,7 @@ export default class MarkdownPreview extends React.Component { const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams() const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber) - const body = markdown.render(noteContent) + const body = this.markdown.render(noteContent) const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] files.forEach((file) => { @@ -217,6 +224,7 @@ export default class MarkdownPreview extends React.Component { return `
+ ${styles} @@ -310,6 +318,10 @@ export default class MarkdownPreview extends React.Component { componentDidUpdate (prevProps) { if (prevProps.value !== this.props.value) this.rewriteIframe() + if (prevProps.smartQuotes !== this.props.smartQuotes) { + this.initMarkdown() + this.rewriteIframe() + } if (prevProps.fontFamily !== this.props.fontFamily || prevProps.fontSize !== this.props.fontSize || prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily || @@ -375,7 +387,7 @@ export default class MarkdownPreview extends React.Component { value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock)) }) } - this.refs.root.contentWindow.document.body.innerHTML = markdown.render(value) + this.refs.root.contentWindow.document.body.innerHTML = this.markdown.render(value) _.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => { this.fixDecodedURI(el) @@ -391,9 +403,9 @@ export default class MarkdownPreview extends React.Component { }) _.forEach(this.refs.root.contentWindow.document.querySelectorAll('img'), (el) => { - el.src = markdown.normalizeLinkText(el.src) + el.src = this.markdown.normalizeLinkText(el.src) if (!/\/:storage/.test(el.src)) return - el.src = `file:///${markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.src)))}` + el.src = `file:///${this.markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.src)))}` }) codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme) @@ -420,9 +432,9 @@ export default class MarkdownPreview extends React.Component { el.innerHTML = '' if (codeBlockTheme.indexOf('solarized') === 0) { const [refThema, color] = codeBlockTheme.split(' ') - el.parentNode.className += ` cm-s-${refThema} cm-s-${color} CodeMirror` + el.parentNode.className += ` cm-s-${refThema} cm-s-${color}` } else { - el.parentNode.className += ` cm-s-${codeBlockTheme} CodeMirror` + el.parentNode.className += ` cm-s-${codeBlockTheme}` } CodeMirror.runMode(content, syntax.mime, el, { tabSize: indentSize @@ -505,9 +517,20 @@ export default class MarkdownPreview extends React.Component { handlelinkClick (e) { const noteHash = e.target.href.split('/').pop() - const regexIsNoteLink = /^(.{20})-(.{20})$/ + // this will match the new uuid v4 hash and the old hash + // e.g. + // :note:1c211eb7dcb463de6490 and + // :note:7dd23275-f2b4-49cb-9e93-3454daf1af9c + const regexIsNoteLink = /^:note:([a-zA-Z0-9-]{20,36})$/ if (regexIsNoteLink.test(noteHash)) { - eventEmitter.emit('list:jump', noteHash) + eventEmitter.emit('list:jump', noteHash.replace(':note:', '')) + } + // this will match the old link format storage.key-note.key + // e.g. + // 877f99c3268608328037-1c211eb7dcb463de6490 + const regexIsLegacyNoteLink = /^(.{20})-(.{20})$/ + if (regexIsLegacyNoteLink.test(noteHash)) { + eventEmitter.emit('list:jump', noteHash.split('-')[1]) } } @@ -534,5 +557,6 @@ MarkdownPreview.propTypes = { className: PropTypes.string, value: PropTypes.string, showCopyNotification: PropTypes.bool, - storagePath: PropTypes.string + storagePath: PropTypes.string, + smartQuotes: PropTypes.bool } diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index 505fbaf4..0aa2d16c 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -127,6 +127,7 @@ class MarkdownSplitEditor extends React.Component { codeBlockFontFamily={config.editor.fontFamily} lineNumber={config.preview.lineNumber} scrollPastEnd={config.preview.scrollPastEnd} + smartQuotes={config.preview.smartQuotes} ref='preview' tabInde='0' value={value} diff --git a/browser/components/NoteItem.js b/browser/components/NoteItem.js index 253faa81..2013cfa0 100644 --- a/browser/components/NoteItem.js +++ b/browser/components/NoteItem.js @@ -62,9 +62,9 @@ const NoteItem = ({ ? 'item--active' : 'item' } - key={`${note.storage}-${note.key}`} - onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)} - onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)} + key={note.key} + onClick={e => handleNoteClick(e, note.key)} + onContextMenu={e => handleNoteContextMenu(e, note.key)} onDragStart={e => handleDragStart(e, note)} draggable='true' > diff --git a/browser/components/NoteItem.styl b/browser/components/NoteItem.styl index f213348a..5203ccea 100644 --- a/browser/components/NoteItem.styl +++ b/browser/components/NoteItem.styl @@ -117,7 +117,7 @@ $control-height = 30px font-size 12px line-height 20px overflow ellipsis - display flex + display block .item-bottom-tagList flex 1 diff --git a/browser/components/NoteItemSimple.js b/browser/components/NoteItemSimple.js index 0d2465e9..8262ea1d 100644 --- a/browser/components/NoteItemSimple.js +++ b/browser/components/NoteItemSimple.js @@ -28,9 +28,9 @@ const NoteItemSimple = ({ ? 'item-simple--active' : 'item-simple' } - key={`${note.storage}-${note.key}`} - onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)} - onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)} + key={note.key} + onClick={e => handleNoteClick(e, note.key)} + onContextMenu={e => handleNoteContextMenu(e, note.key)} onDragStart={e => handleDragStart(e, note)} draggable='true' > diff --git a/browser/components/StorageItem.js b/browser/components/StorageItem.js index 25be3c57..c92579da 100644 --- a/browser/components/StorageItem.js +++ b/browser/components/StorageItem.js @@ -6,6 +6,7 @@ import React from 'react' import styles from './StorageItem.styl' import CSSModules from 'browser/lib/CSSModules' import _ from 'lodash' +import { SortableHandle } from 'react-sortable-hoc' /** * @param {boolean} isActive @@ -23,32 +24,35 @@ import _ from 'lodash' const StorageItem = ({ isActive, handleButtonClick, handleContextMenu, folderName, folderColor, isFolded, noteCount, handleDrop, handleDragEnter, handleDragLeave -}) => ( - + ) +} StorageItem.propTypes = { isActive: PropTypes.bool.isRequired, diff --git a/browser/lib/keygen.js b/browser/lib/keygen.js index f4937a83..814efedd 100644 --- a/browser/lib/keygen.js +++ b/browser/lib/keygen.js @@ -1,7 +1,11 @@ const crypto = require('crypto') const _ = require('lodash') +const uuidv4 = require('uuid/v4') -module.exports = function (length) { - if (!_.isFinite(length)) length = 10 +module.exports = function (uuid) { + if (typeof uuid === typeof true && uuid) { + return uuidv4() + } + const length = 10 return crypto.randomBytes(length).toString('hex') } diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index b97f9d56..a2b9da51 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -20,181 +20,188 @@ function createGutter (str, firstLineNumber) { return '' + lines.join('') + '' } -var md = markdownit({ - typographer: true, - linkify: true, - html: true, - xhtmlOut: true, - breaks: true, - highlight: function (str, lang) { - const delimiter = ':' - const langInfo = lang.split(delimiter) - const langType = langInfo[0] - const fileName = langInfo[1] || '' - const firstLineNumber = parseInt(langInfo[2], 10) +class Markdown { + constructor (options = {}) { + const defaultOptions = { + typographer: true, + linkify: true, + html: true, + xhtmlOut: true, + breaks: true, + highlight: function (str, lang) { + const delimiter = ':' + const langInfo = lang.split(delimiter) + const langType = langInfo[0] + const fileName = langInfo[1] || '' + const firstLineNumber = parseInt(langInfo[2], 10) - if (langType === 'flowchart') { - return `${str}`
- }
- if (langType === 'sequence') {
- return `${str}`
- }
- return '' +
- '' + fileName + '' +
- createGutter(str, firstLineNumber) +
- '' +
- str +
- ''
- }
-})
-// Sanitize use rinput before other plugins
-md.use(sanitize, {
- allowedTags: ['img', 'iframe'],
- allowedAttributes: {
- '*': ['alt', 'style'],
- 'img': ['src', 'width', 'height'],
- 'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen']
- },
- allowedIframeHostnames: ['www.youtube.com']
-})
-md.use(emoji, {
- shortcuts: {}
-})
-md.use(math, {
- inlineOpen: config.preview.latexInlineOpen,
- inlineClose: config.preview.latexInlineClose,
- blockOpen: config.preview.latexBlockOpen,
- blockClose: config.preview.latexBlockClose,
- inlineRenderer: function (str) {
- let output = ''
- try {
- output = katex.renderToString(str.trim())
- } catch (err) {
- output = `${err.message}`
- }
- return output
- },
- blockRenderer: function (str) {
- let output = ''
- try {
- output = katex.renderToString(str.trim(), { displayMode: true })
- } catch (err) {
- output = `${str}`
}
- liToken.attrs.push(['class', 'taskListItem'])
+ if (langType === 'sequence') {
+ return `${str}`
+ }
+ return '' +
+ '' + fileName + '' +
+ createGutter(str, firstLineNumber) +
+ '' +
+ str +
+ ''
}
- content = ``
}
+
+ const updatedOptions = Object.assign(defaultOptions, options)
+ this.md = markdownit(updatedOptions)
+
+ // Sanitize use rinput before other plugins
+ this.md.use(sanitize, {
+ allowedTags: ['img', 'iframe', 'input'],
+ allowedAttributes: {
+ '*': ['alt', 'style'],
+ 'img': ['src', 'width', 'height'],
+ 'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
+ 'input': ['type', 'id', 'checked']
+ },
+ allowedIframeHostnames: ['www.youtube.com']
+ })
+
+ this.md.use(emoji, {
+ shortcuts: {}
+ })
+ this.md.use(math, {
+ inlineOpen: config.preview.latexInlineOpen,
+ inlineClose: config.preview.latexInlineClose,
+ blockOpen: config.preview.latexBlockOpen,
+ blockClose: config.preview.latexBlockClose,
+ inlineRenderer: function (str) {
+ let output = ''
+ try {
+ output = katex.renderToString(str.trim())
+ } catch (err) {
+ output = `${err.message}`
+ }
+ return output
+ },
+ blockRenderer: function (str) {
+ let output = ''
+ try {
+ output = katex.renderToString(str.trim(), { displayMode: true })
+ } catch (err) {
+ output = `