mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 17:56:25 +00:00
Merge branch 'master' into localization
This commit is contained in:
@@ -279,6 +279,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
lineNumber={config.preview.lineNumber}
|
lineNumber={config.preview.lineNumber}
|
||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
scrollPastEnd={config.preview.scrollPastEnd}
|
scrollPastEnd={config.preview.scrollPastEnd}
|
||||||
|
smartQuotes={config.preview.smartQuotes}
|
||||||
ref='preview'
|
ref='preview'
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||||
onDoubleClick={(e) => this.handleDoubleClick(e)}
|
onDoubleClick={(e) => this.handleDoubleClick(e)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import markdown from 'browser/lib/markdown'
|
import Markdown from 'browser/lib/markdown'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
import 'codemirror-mode-elixir'
|
import 'codemirror-mode-elixir'
|
||||||
@@ -130,6 +130,13 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.printHandler = () => this.handlePrint()
|
this.printHandler = () => this.handlePrint()
|
||||||
|
|
||||||
this.linkClickHandler = this.handlelinkClick.bind(this)
|
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) {
|
handlePreviewAnchorClick (e) {
|
||||||
@@ -198,7 +205,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams()
|
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams()
|
||||||
|
|
||||||
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber)
|
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]
|
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||||
|
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
@@ -217,6 +224,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
return `<html>
|
return `<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
|
||||||
<style id="style">${inlineStyles}</style>
|
<style id="style">${inlineStyles}</style>
|
||||||
${styles}
|
${styles}
|
||||||
</head>
|
</head>
|
||||||
@@ -310,6 +318,10 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
||||||
|
if (prevProps.smartQuotes !== this.props.smartQuotes) {
|
||||||
|
this.initMarkdown()
|
||||||
|
this.rewriteIframe()
|
||||||
|
}
|
||||||
if (prevProps.fontFamily !== this.props.fontFamily ||
|
if (prevProps.fontFamily !== this.props.fontFamily ||
|
||||||
prevProps.fontSize !== this.props.fontSize ||
|
prevProps.fontSize !== this.props.fontSize ||
|
||||||
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
|
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
|
||||||
@@ -375,7 +387,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
|
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) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
this.fixDecodedURI(el)
|
this.fixDecodedURI(el)
|
||||||
@@ -391,9 +403,9 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
})
|
})
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('img'), (el) => {
|
_.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
|
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)
|
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
|
||||||
@@ -420,9 +432,9 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
el.innerHTML = ''
|
el.innerHTML = ''
|
||||||
if (codeBlockTheme.indexOf('solarized') === 0) {
|
if (codeBlockTheme.indexOf('solarized') === 0) {
|
||||||
const [refThema, color] = codeBlockTheme.split(' ')
|
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 {
|
} else {
|
||||||
el.parentNode.className += ` cm-s-${codeBlockTheme} CodeMirror`
|
el.parentNode.className += ` cm-s-${codeBlockTheme}`
|
||||||
}
|
}
|
||||||
CodeMirror.runMode(content, syntax.mime, el, {
|
CodeMirror.runMode(content, syntax.mime, el, {
|
||||||
tabSize: indentSize
|
tabSize: indentSize
|
||||||
@@ -505,9 +517,20 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
handlelinkClick (e) {
|
handlelinkClick (e) {
|
||||||
const noteHash = e.target.href.split('/').pop()
|
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)) {
|
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,
|
className: PropTypes.string,
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
showCopyNotification: PropTypes.bool,
|
showCopyNotification: PropTypes.bool,
|
||||||
storagePath: PropTypes.string
|
storagePath: PropTypes.string,
|
||||||
|
smartQuotes: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
codeBlockFontFamily={config.editor.fontFamily}
|
codeBlockFontFamily={config.editor.fontFamily}
|
||||||
lineNumber={config.preview.lineNumber}
|
lineNumber={config.preview.lineNumber}
|
||||||
scrollPastEnd={config.preview.scrollPastEnd}
|
scrollPastEnd={config.preview.scrollPastEnd}
|
||||||
|
smartQuotes={config.preview.smartQuotes}
|
||||||
ref='preview'
|
ref='preview'
|
||||||
tabInde='0'
|
tabInde='0'
|
||||||
value={value}
|
value={value}
|
||||||
|
|||||||
@@ -62,9 +62,9 @@ const NoteItem = ({
|
|||||||
? 'item--active'
|
? 'item--active'
|
||||||
: 'item'
|
: 'item'
|
||||||
}
|
}
|
||||||
key={`${note.storage}-${note.key}`}
|
key={note.key}
|
||||||
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
|
onClick={e => handleNoteClick(e, note.key)}
|
||||||
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
|
onContextMenu={e => handleNoteContextMenu(e, note.key)}
|
||||||
onDragStart={e => handleDragStart(e, note)}
|
onDragStart={e => handleDragStart(e, note)}
|
||||||
draggable='true'
|
draggable='true'
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ $control-height = 30px
|
|||||||
font-size 12px
|
font-size 12px
|
||||||
line-height 20px
|
line-height 20px
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
display flex
|
display block
|
||||||
|
|
||||||
.item-bottom-tagList
|
.item-bottom-tagList
|
||||||
flex 1
|
flex 1
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ const NoteItemSimple = ({
|
|||||||
? 'item-simple--active'
|
? 'item-simple--active'
|
||||||
: 'item-simple'
|
: 'item-simple'
|
||||||
}
|
}
|
||||||
key={`${note.storage}-${note.key}`}
|
key={note.key}
|
||||||
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
|
onClick={e => handleNoteClick(e, note.key)}
|
||||||
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
|
onContextMenu={e => handleNoteContextMenu(e, note.key)}
|
||||||
onDragStart={e => handleDragStart(e, note)}
|
onDragStart={e => handleDragStart(e, note)}
|
||||||
draggable='true'
|
draggable='true'
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import React from 'react'
|
|||||||
import styles from './StorageItem.styl'
|
import styles from './StorageItem.styl'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import { SortableHandle } from 'react-sortable-hoc'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} isActive
|
* @param {boolean} isActive
|
||||||
@@ -23,7 +24,9 @@ import _ from 'lodash'
|
|||||||
const StorageItem = ({
|
const StorageItem = ({
|
||||||
isActive, handleButtonClick, handleContextMenu, folderName,
|
isActive, handleButtonClick, handleContextMenu, folderName,
|
||||||
folderColor, isFolded, noteCount, handleDrop, handleDragEnter, handleDragLeave
|
folderColor, isFolded, noteCount, handleDrop, handleDragEnter, handleDragLeave
|
||||||
}) => (
|
}) => {
|
||||||
|
const FolderDragger = SortableHandle(({ className }) => <i className={className} />)
|
||||||
|
return (
|
||||||
<button styleName={isActive
|
<button styleName={isActive
|
||||||
? 'folderList-item--active'
|
? 'folderList-item--active'
|
||||||
: 'folderList-item'
|
: 'folderList-item'
|
||||||
@@ -37,7 +40,7 @@ const StorageItem = ({
|
|||||||
<span styleName={isFolded
|
<span styleName={isFolded
|
||||||
? 'folderList-item-name--folded' : 'folderList-item-name'
|
? 'folderList-item-name--folded' : 'folderList-item-name'
|
||||||
}>
|
}>
|
||||||
<text style={{color: folderColor, paddingRight: '10px'}}>{isActive ? <i className='fa fa-folder-open-o' /> : <i className='fa fa-folder-o' />}</text>{isFolded ? _.truncate(folderName, {length: 1, omission: ''}) : folderName}
|
<text style={{color: folderColor, paddingRight: '10px'}}>{isActive ? <FolderDragger className='fa fa-folder-open-o' /> : <FolderDragger className='fa fa-folder-o' />}</text>{isFolded ? _.truncate(folderName, {length: 1, omission: ''}) : folderName}
|
||||||
</span>
|
</span>
|
||||||
{(!isFolded && _.isNumber(noteCount)) &&
|
{(!isFolded && _.isNumber(noteCount)) &&
|
||||||
<span styleName='folderList-item-noteCount'>{noteCount}</span>
|
<span styleName='folderList-item-noteCount'>{noteCount}</span>
|
||||||
@@ -49,6 +52,7 @@ const StorageItem = ({
|
|||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
StorageItem.propTypes = {
|
StorageItem.propTypes = {
|
||||||
isActive: PropTypes.bool.isRequired,
|
isActive: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
|
const uuidv4 = require('uuid/v4')
|
||||||
|
|
||||||
module.exports = function (length) {
|
module.exports = function (uuid) {
|
||||||
if (!_.isFinite(length)) length = 10
|
if (typeof uuid === typeof true && uuid) {
|
||||||
|
return uuidv4()
|
||||||
|
}
|
||||||
|
const length = 10
|
||||||
return crypto.randomBytes(length).toString('hex')
|
return crypto.randomBytes(length).toString('hex')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ function createGutter (str, firstLineNumber) {
|
|||||||
return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
|
return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
|
||||||
}
|
}
|
||||||
|
|
||||||
var md = markdownit({
|
class Markdown {
|
||||||
|
constructor (options = {}) {
|
||||||
|
const defaultOptions = {
|
||||||
typographer: true,
|
typographer: true,
|
||||||
linkify: true,
|
linkify: true,
|
||||||
html: true,
|
html: true,
|
||||||
@@ -39,28 +41,34 @@ var md = markdownit({
|
|||||||
if (langType === 'sequence') {
|
if (langType === 'sequence') {
|
||||||
return `<pre class="sequence">${str}</pre>`
|
return `<pre class="sequence">${str}</pre>`
|
||||||
}
|
}
|
||||||
return '<pre class="code">' +
|
return '<pre class="code CodeMirror">' +
|
||||||
'<span class="filename">' + fileName + '</span>' +
|
'<span class="filename">' + fileName + '</span>' +
|
||||||
createGutter(str, firstLineNumber) +
|
createGutter(str, firstLineNumber) +
|
||||||
'<code class="' + langType + '">' +
|
'<code class="' + langType + '">' +
|
||||||
str +
|
str +
|
||||||
'</code></pre>'
|
'</code></pre>'
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const updatedOptions = Object.assign(defaultOptions, options)
|
||||||
|
this.md = markdownit(updatedOptions)
|
||||||
|
|
||||||
// Sanitize use rinput before other plugins
|
// Sanitize use rinput before other plugins
|
||||||
md.use(sanitize, {
|
this.md.use(sanitize, {
|
||||||
allowedTags: ['img', 'iframe'],
|
allowedTags: ['img', 'iframe', 'input'],
|
||||||
allowedAttributes: {
|
allowedAttributes: {
|
||||||
'*': ['alt', 'style'],
|
'*': ['alt', 'style'],
|
||||||
'img': ['src', 'width', 'height'],
|
'img': ['src', 'width', 'height'],
|
||||||
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen']
|
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
||||||
|
'input': ['type', 'id', 'checked']
|
||||||
},
|
},
|
||||||
allowedIframeHostnames: ['www.youtube.com']
|
allowedIframeHostnames: ['www.youtube.com']
|
||||||
})
|
})
|
||||||
md.use(emoji, {
|
|
||||||
|
this.md.use(emoji, {
|
||||||
shortcuts: {}
|
shortcuts: {}
|
||||||
})
|
})
|
||||||
md.use(math, {
|
this.md.use(math, {
|
||||||
inlineOpen: config.preview.latexInlineOpen,
|
inlineOpen: config.preview.latexInlineOpen,
|
||||||
inlineClose: config.preview.latexInlineClose,
|
inlineClose: config.preview.latexInlineClose,
|
||||||
blockOpen: config.preview.latexBlockOpen,
|
blockOpen: config.preview.latexBlockOpen,
|
||||||
@@ -84,10 +92,10 @@ md.use(math, {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
md.use(require('markdown-it-imsize'))
|
this.md.use(require('markdown-it-imsize'))
|
||||||
md.use(require('markdown-it-footnote'))
|
this.md.use(require('markdown-it-footnote'))
|
||||||
md.use(require('markdown-it-multimd-table'))
|
this.md.use(require('markdown-it-multimd-table'))
|
||||||
md.use(require('markdown-it-named-headers'), {
|
this.md.use(require('markdown-it-named-headers'), {
|
||||||
slugify: (header) => {
|
slugify: (header) => {
|
||||||
return encodeURI(header.trim()
|
return encodeURI(header.trim()
|
||||||
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
|
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
|
||||||
@@ -95,10 +103,10 @@ md.use(require('markdown-it-named-headers'), {
|
|||||||
.replace(/\-+$/, '')
|
.replace(/\-+$/, '')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
md.use(require('markdown-it-kbd'))
|
this.md.use(require('markdown-it-kbd'))
|
||||||
|
|
||||||
const deflate = require('markdown-it-plantuml/lib/deflate')
|
const deflate = require('markdown-it-plantuml/lib/deflate')
|
||||||
md.use(require('markdown-it-plantuml'), '', {
|
this.md.use(require('markdown-it-plantuml'), '', {
|
||||||
generateSource: function (umlCode) {
|
generateSource: function (umlCode) {
|
||||||
const s = unescape(encodeURIComponent(umlCode))
|
const s = unescape(encodeURIComponent(umlCode))
|
||||||
const zippedCode = deflate.encode64(
|
const zippedCode = deflate.encode64(
|
||||||
@@ -109,7 +117,7 @@ md.use(require('markdown-it-plantuml'), '', {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Override task item
|
// Override task item
|
||||||
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
||||||
let content, terminate, i, l, token
|
let content, terminate, i, l, token
|
||||||
let nextLine = startLine + 1
|
let nextLine = startLine + 1
|
||||||
const terminatorRules = state.md.block.ruler.getRules('paragraph')
|
const terminatorRules = state.md.block.ruler.getRules('paragraph')
|
||||||
@@ -167,8 +175,8 @@ md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Add line number attribute for scrolling
|
// Add line number attribute for scrolling
|
||||||
const originalRender = md.renderer.render
|
const originalRender = this.md.renderer.render
|
||||||
md.renderer.render = function render (tokens, options, env) {
|
this.md.renderer.render = (tokens, options, env) => {
|
||||||
tokens.forEach((token) => {
|
tokens.forEach((token) => {
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case 'heading_open':
|
case 'heading_open':
|
||||||
@@ -178,23 +186,22 @@ md.renderer.render = function render (tokens, options, env) {
|
|||||||
token.attrPush(['data-line', token.map[0]])
|
token.attrPush(['data-line', token.map[0]])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const result = originalRender.call(md.renderer, tokens, options, env)
|
const result = originalRender.call(this.md.renderer, tokens, options, env)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
// FIXME We should not depend on global variable.
|
// FIXME We should not depend on global variable.
|
||||||
window.md = md
|
window.md = this.md
|
||||||
|
|
||||||
function normalizeLinkText (linkText) {
|
|
||||||
return md.normalizeLinkText(linkText)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const markdown = {
|
render (content) {
|
||||||
render: function markdown (content) {
|
|
||||||
if (!_.isString(content)) content = ''
|
if (!_.isString(content)) content = ''
|
||||||
const renderedContent = md.render(content)
|
return this.md.render(content)
|
||||||
return renderedContent
|
|
||||||
},
|
|
||||||
normalizeLinkText
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default markdown
|
normalizeLinkText (linkText) {
|
||||||
|
return this.md.normalizeLinkText(linkText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Markdown
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
hashHistory.replace({
|
hashHistory.replace({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
query: {
|
||||||
key: newNote.storage + '-' + newNote.key
|
key: newNote.key
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -393,7 +393,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
<InfoPanel
|
<InfoPanel
|
||||||
storageName={currentOption.storage.name}
|
storageName={currentOption.storage.name}
|
||||||
folderName={currentOption.folder.name}
|
folderName={currentOption.folder.name}
|
||||||
noteLink={`[${note.title}](${location.query.key})`}
|
noteLink={`[${note.title}](:note:${location.query.key})`}
|
||||||
updatedAt={formatDate(note.updatedAt)}
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
createdAt={formatDate(note.createdAt)}
|
createdAt={formatDate(note.createdAt)}
|
||||||
exportAsMd={this.exportAsMd}
|
exportAsMd={this.exportAsMd}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
hashHistory.replace({
|
hashHistory.replace({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
query: {
|
||||||
key: newNote.storage + '-' + newNote.key
|
key: newNote.key
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -651,7 +651,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
<InfoPanel
|
<InfoPanel
|
||||||
storageName={currentOption.storage.name}
|
storageName={currentOption.storage.name}
|
||||||
folderName={currentOption.folder.name}
|
folderName={currentOption.folder.name}
|
||||||
noteLink={`[${note.title}](${location.query.key})`}
|
noteLink={`[${note.title}](:note:${location.query.key})`}
|
||||||
updatedAt={formatDate(note.updatedAt)}
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
createdAt={formatDate(note.createdAt)}
|
createdAt={formatDate(note.createdAt)}
|
||||||
exportAsMd={this.showWarning}
|
exportAsMd={this.showWarning}
|
||||||
|
|||||||
@@ -57,11 +57,8 @@ class Detail extends React.Component {
|
|||||||
const { location, data, config } = this.props
|
const { location, data, config } = this.props
|
||||||
let note = null
|
let note = null
|
||||||
if (location.query.key != null) {
|
if (location.query.key != null) {
|
||||||
const splitted = location.query.key.split('-')
|
const noteKey = location.query.key
|
||||||
const storageKey = splitted.shift()
|
note = data.noteMap.get(noteKey)
|
||||||
const noteKey = splitted.shift()
|
|
||||||
|
|
||||||
note = data.noteMap.get(storageKey + '-' + noteKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note == null) {
|
if (note == null) {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ function sortByUpdatedAt (a, b) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findNoteByKey (notes, noteKey) {
|
function findNoteByKey (notes, noteKey) {
|
||||||
return notes.find((note) => `${note.storage}-${note.key}` === noteKey)
|
return notes.find((note) => note.key === noteKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
function findNotesByKeys (notes, noteKeys) {
|
function findNotesByKeys (notes, noteKeys) {
|
||||||
@@ -43,7 +43,7 @@ function findNotesByKeys (notes, noteKeys) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getNoteKey (note) {
|
function getNoteKey (note) {
|
||||||
return `${note.storage}-${note.key}`
|
return note.key
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoteList extends React.Component {
|
class NoteList extends React.Component {
|
||||||
@@ -119,10 +119,10 @@ class NoteList extends React.Component {
|
|||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
const { location } = this.props
|
const { location } = this.props
|
||||||
const { selectedNoteKeys } = this.state
|
const { selectedNoteKeys } = this.state
|
||||||
const visibleNoteKeys = this.notes.map(note => `${note.storage}-${note.key}`)
|
const visibleNoteKeys = this.notes.map(note => note.key)
|
||||||
const note = this.notes[0]
|
const note = this.notes[0]
|
||||||
const prevKey = prevProps.location.query.key
|
const prevKey = prevProps.location.query.key
|
||||||
const noteKey = visibleNoteKeys.includes(prevKey) ? prevKey : note && `${note.storage}-${note.key}`
|
const noteKey = visibleNoteKeys.includes(prevKey) ? prevKey : note && note.key
|
||||||
|
|
||||||
if (note && location.query.key == null) {
|
if (note && location.query.key == null) {
|
||||||
const { router } = this.context
|
const { router } = this.context
|
||||||
@@ -590,11 +590,9 @@ class NoteList extends React.Component {
|
|||||||
})
|
})
|
||||||
if (dialogueButtonIndex === 1) return
|
if (dialogueButtonIndex === 1) return
|
||||||
Promise.all(
|
Promise.all(
|
||||||
selectedNoteKeys.map((uniqueKey) => {
|
selectedNotes.map((note) => {
|
||||||
const storageKey = uniqueKey.split('-')[0]
|
|
||||||
const noteKey = uniqueKey.split('-')[1]
|
|
||||||
return dataApi
|
return dataApi
|
||||||
.deleteNote(storageKey, noteKey)
|
.deleteNote(note.storage, note.key)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@@ -655,19 +653,18 @@ class NoteList extends React.Component {
|
|||||||
content: firstNote.content
|
content: firstNote.content
|
||||||
})
|
})
|
||||||
.then((note) => {
|
.then((note) => {
|
||||||
const uniqueKey = note.storage + '-' + note.key
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
})
|
})
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedNoteKeys: [uniqueKey]
|
selectedNoteKeys: [note.key]
|
||||||
})
|
})
|
||||||
|
|
||||||
hashHistory.push({
|
hashHistory.push({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {key: uniqueKey}
|
query: {key: note.key}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import RenameFolderModal from 'browser/main/modals/RenameFolderModal'
|
|||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import StorageItemChild from 'browser/components/StorageItem'
|
import StorageItemChild from 'browser/components/StorageItem'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import { SortableElement } from 'react-sortable-hoc'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, dialog } = remote
|
const { Menu, dialog } = remote
|
||||||
@@ -219,7 +220,8 @@ class StorageItem extends React.Component {
|
|||||||
render () {
|
render () {
|
||||||
const { storage, location, isFolded, data, dispatch } = this.props
|
const { storage, location, isFolded, data, dispatch } = this.props
|
||||||
const { folderNoteMap, trashedSet } = data
|
const { folderNoteMap, trashedSet } = data
|
||||||
const folderList = storage.folders.map((folder) => {
|
const SortableStorageItemChild = SortableElement(StorageItemChild)
|
||||||
|
const folderList = storage.folders.map((folder, index) => {
|
||||||
const isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key)))
|
const isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key)))
|
||||||
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
||||||
|
|
||||||
@@ -233,8 +235,9 @@ class StorageItem extends React.Component {
|
|||||||
noteCount = noteSet.size - trashedNoteCount
|
noteCount = noteSet.size - trashedNoteCount
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<StorageItemChild
|
<SortableStorageItemChild
|
||||||
key={folder.key}
|
key={folder.key}
|
||||||
|
index={index}
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
handleButtonClick={(e) => this.handleFolderButtonClick(folder.key)(e)}
|
handleButtonClick={(e) => this.handleFolderButtonClick(folder.key)(e)}
|
||||||
handleContextMenu={(e) => this.handleFolderButtonContextMenu(e, folder)}
|
handleContextMenu={(e) => this.handleFolderButtonContextMenu(e, folder)}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import EventEmitter from 'browser/main/lib/eventEmitter'
|
|||||||
import PreferenceButton from './PreferenceButton'
|
import PreferenceButton from './PreferenceButton'
|
||||||
import ListButton from './ListButton'
|
import ListButton from './ListButton'
|
||||||
import TagButton from './TagButton'
|
import TagButton from './TagButton'
|
||||||
|
import {SortableContainer} from 'react-sortable-hoc'
|
||||||
|
|
||||||
class SideNav extends React.Component {
|
class SideNav extends React.Component {
|
||||||
// TODO: should not use electron stuff v0.7
|
// TODO: should not use electron stuff v0.7
|
||||||
@@ -68,6 +69,17 @@ class SideNav extends React.Component {
|
|||||||
router.push('/alltags')
|
router.push('/alltags')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSortEnd (storage) {
|
||||||
|
return ({oldIndex, newIndex}) => {
|
||||||
|
const { dispatch } = this.props
|
||||||
|
dataApi
|
||||||
|
.reorderFolder(storage.key, oldIndex, newIndex)
|
||||||
|
.then((data) => {
|
||||||
|
dispatch({ type: 'REORDER_FOLDER', storage: data.storage })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SideNavComponent (isFolded, storageList) {
|
SideNavComponent (isFolded, storageList) {
|
||||||
const { location, data } = this.props
|
const { location, data } = this.props
|
||||||
|
|
||||||
@@ -117,9 +129,9 @@ class SideNav extends React.Component {
|
|||||||
|
|
||||||
tagListComponent () {
|
tagListComponent () {
|
||||||
const { data, location } = this.props
|
const { data, location } = this.props
|
||||||
const tagList = data.tagNoteMap.map((tag, name) => {
|
const tagList = _.sortBy(data.tagNoteMap.map((tag, name) => {
|
||||||
return { name, size: tag.size }
|
return { name, size: tag.size }
|
||||||
})
|
}), ['name'])
|
||||||
return (
|
return (
|
||||||
tagList.map(tag => {
|
tagList.map(tag => {
|
||||||
return (
|
return (
|
||||||
@@ -148,10 +160,8 @@ class SideNav extends React.Component {
|
|||||||
|
|
||||||
emptyTrash (entries) {
|
emptyTrash (entries) {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
const deletionPromises = entries.map((storageAndNoteKey) => {
|
const deletionPromises = entries.map((note) => {
|
||||||
const storageKey = storageAndNoteKey.split('-')[0]
|
return dataApi.deleteNote(note.storage, note.key)
|
||||||
const noteKey = storageAndNoteKey.split('-')[1]
|
|
||||||
return dataApi.deleteNote(storageKey, noteKey)
|
|
||||||
})
|
})
|
||||||
Promise.all(deletionPromises)
|
Promise.all(deletionPromises)
|
||||||
.then((arrayOfStorageAndNoteKeys) => {
|
.then((arrayOfStorageAndNoteKeys) => {
|
||||||
@@ -167,9 +177,9 @@ class SideNav extends React.Component {
|
|||||||
|
|
||||||
handleFilterButtonContextMenu (event) {
|
handleFilterButtonContextMenu (event) {
|
||||||
const { data } = this.props
|
const { data } = this.props
|
||||||
const entries = data.trashedSet.toJS()
|
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||||
const menu = Menu.buildFromTemplate([
|
const menu = Menu.buildFromTemplate([
|
||||||
{ label: 'Empty Trash', click: () => this.emptyTrash(entries) }
|
{ label: 'Empty Trash', click: () => this.emptyTrash(trashedNotes) }
|
||||||
])
|
])
|
||||||
menu.popup()
|
menu.popup()
|
||||||
}
|
}
|
||||||
@@ -180,13 +190,16 @@ class SideNav extends React.Component {
|
|||||||
const isFolded = config.isSideNavFolded
|
const isFolded = config.isSideNavFolded
|
||||||
|
|
||||||
const storageList = data.storageMap.map((storage, key) => {
|
const storageList = data.storageMap.map((storage, key) => {
|
||||||
return <StorageItem
|
const SortableStorageItem = SortableContainer(StorageItem)
|
||||||
|
return <SortableStorageItem
|
||||||
key={storage.key}
|
key={storage.key}
|
||||||
storage={storage}
|
storage={storage}
|
||||||
data={data}
|
data={data}
|
||||||
location={location}
|
location={location}
|
||||||
isFolded={isFolded}
|
isFolded={isFolded}
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
|
onSortEnd={this.onSortEnd.bind(this)(storage)}
|
||||||
|
useDragHandle
|
||||||
/>
|
/>
|
||||||
})
|
})
|
||||||
const style = {}
|
const style = {}
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ export const DEFAULT_CONFIG = {
|
|||||||
latexInlineClose: '$',
|
latexInlineClose: '$',
|
||||||
latexBlockOpen: '$$',
|
latexBlockOpen: '$$',
|
||||||
latexBlockClose: '$$',
|
latexBlockClose: '$$',
|
||||||
scrollPastEnd: false
|
scrollPastEnd: false,
|
||||||
|
smartQuotes: true
|
||||||
},
|
},
|
||||||
blog: {
|
blog: {
|
||||||
type: 'wordpress', // Available value: wordpress, add more types in the future plz
|
type: 'wordpress', // Available value: wordpress, add more types in the future plz
|
||||||
|
|||||||
@@ -52,12 +52,12 @@ function createNote (storageKey, input) {
|
|||||||
return storage
|
return storage
|
||||||
})
|
})
|
||||||
.then(function saveNote (storage) {
|
.then(function saveNote (storage) {
|
||||||
let key = keygen()
|
let key = keygen(true)
|
||||||
let isUnique = false
|
let isUnique = false
|
||||||
while (!isUnique) {
|
while (!isUnique) {
|
||||||
try {
|
try {
|
||||||
sander.statSync(path.join(storage.path, 'notes', key + '.cson'))
|
sander.statSync(path.join(storage.path, 'notes', key + '.cson'))
|
||||||
key = keygen()
|
key = keygen(true)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
isUnique = true
|
isUnique = true
|
||||||
|
|||||||
@@ -39,12 +39,12 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
|
|||||||
return resolveStorageData(newStorage)
|
return resolveStorageData(newStorage)
|
||||||
.then(function findNewNoteKey (_newStorage) {
|
.then(function findNewNoteKey (_newStorage) {
|
||||||
newStorage = _newStorage
|
newStorage = _newStorage
|
||||||
newNoteKey = keygen()
|
newNoteKey = keygen(true)
|
||||||
let isUnique = false
|
let isUnique = false
|
||||||
while (!isUnique) {
|
while (!isUnique) {
|
||||||
try {
|
try {
|
||||||
sander.statSync(path.join(newStorage.path, 'notes', newNoteKey + '.cson'))
|
sander.statSync(path.join(newStorage.path, 'notes', newNoteKey + '.cson'))
|
||||||
newNoteKey = keygen()
|
newNoteKey = keygen(true)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
isUnique = true
|
isUnique = true
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class NewNoteModal extends React.Component {
|
|||||||
content: ''
|
content: ''
|
||||||
})
|
})
|
||||||
.then((note) => {
|
.then((note) => {
|
||||||
const noteHash = `${note.storage}-${note.key}`
|
const noteHash = note.key
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
@@ -76,7 +76,7 @@ class NewNoteModal extends React.Component {
|
|||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
.then((note) => {
|
.then((note) => {
|
||||||
const noteHash = `${note.storage}-${note.key}`
|
const noteHash = note.key
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class InfoTab extends React.Component {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
amaMessage: i18n.__('Thank\'s for trust us')
|
amaMessage: i18n.__('Thank\'s for trusting us')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ class UiTab extends React.Component {
|
|||||||
latexInlineClose: this.refs.previewLatexInlineClose.value,
|
latexInlineClose: this.refs.previewLatexInlineClose.value,
|
||||||
latexBlockOpen: this.refs.previewLatexBlockOpen.value,
|
latexBlockOpen: this.refs.previewLatexBlockOpen.value,
|
||||||
latexBlockClose: this.refs.previewLatexBlockClose.value,
|
latexBlockClose: this.refs.previewLatexBlockClose.value,
|
||||||
scrollPastEnd: this.refs.previewScrollPastEnd.checked
|
scrollPastEnd: this.refs.previewScrollPastEnd.checked,
|
||||||
|
smartQuotes: this.refs.previewSmartQuotes.checked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,6 +430,16 @@ class UiTab extends React.Component {
|
|||||||
{i18n.__('Show line numbers for preview code blocks')}
|
{i18n.__('Show line numbers for preview code blocks')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.preview.smartQuotes}
|
||||||
|
ref='previewSmartQuotes'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
Enable smart quotes
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div styleName='group-section'>
|
<div styleName='group-section'>
|
||||||
<div styleName='group-section-label'>
|
<div styleName='group-section-label'>
|
||||||
{i18n.__('LaTeX Inline Open Delimiter')}
|
{i18n.__('LaTeX Inline Open Delimiter')}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ function data (state = defaultDataMap(), action) {
|
|||||||
|
|
||||||
action.notes.some((note) => {
|
action.notes.some((note) => {
|
||||||
if (note === undefined) return true
|
if (note === undefined) return true
|
||||||
const uniqueKey = note.storage + '-' + note.key
|
const uniqueKey = note.key
|
||||||
const folderKey = note.storage + '-' + note.folder
|
const folderKey = note.storage + '-' + note.folder
|
||||||
state.noteMap.set(uniqueKey, note)
|
state.noteMap.set(uniqueKey, note)
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ function data (state = defaultDataMap(), action) {
|
|||||||
case 'UPDATE_NOTE':
|
case 'UPDATE_NOTE':
|
||||||
{
|
{
|
||||||
const note = action.note
|
const note = action.note
|
||||||
const uniqueKey = note.storage + '-' + note.key
|
const uniqueKey = note.key
|
||||||
const folderKey = note.storage + '-' + note.folder
|
const folderKey = note.storage + '-' + note.folder
|
||||||
const oldNote = state.noteMap.get(uniqueKey)
|
const oldNote = state.noteMap.get(uniqueKey)
|
||||||
|
|
||||||
@@ -162,9 +162,9 @@ function data (state = defaultDataMap(), action) {
|
|||||||
case 'MOVE_NOTE':
|
case 'MOVE_NOTE':
|
||||||
{
|
{
|
||||||
const originNote = action.originNote
|
const originNote = action.originNote
|
||||||
const originKey = originNote.storage + '-' + originNote.key
|
const originKey = originNote.key
|
||||||
const note = action.note
|
const note = action.note
|
||||||
const uniqueKey = note.storage + '-' + note.key
|
const uniqueKey = note.key
|
||||||
const folderKey = note.storage + '-' + note.folder
|
const folderKey = note.storage + '-' + note.folder
|
||||||
const oldNote = state.noteMap.get(uniqueKey)
|
const oldNote = state.noteMap.get(uniqueKey)
|
||||||
|
|
||||||
@@ -297,7 +297,7 @@ function data (state = defaultDataMap(), action) {
|
|||||||
}
|
}
|
||||||
case 'DELETE_NOTE':
|
case 'DELETE_NOTE':
|
||||||
{
|
{
|
||||||
const uniqueKey = action.storageKey + '-' + action.noteKey
|
const uniqueKey = action.noteKey
|
||||||
const targetNote = state.noteMap.get(uniqueKey)
|
const targetNote = state.noteMap.get(uniqueKey)
|
||||||
|
|
||||||
state = Object.assign({}, state)
|
state = Object.assign({}, state)
|
||||||
@@ -423,7 +423,7 @@ function data (state = defaultDataMap(), action) {
|
|||||||
state.folderNoteMap = new Map(state.folderNoteMap)
|
state.folderNoteMap = new Map(state.folderNoteMap)
|
||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||||
action.notes.forEach((note) => {
|
action.notes.forEach((note) => {
|
||||||
const uniqueKey = note.storage + '-' + note.key
|
const uniqueKey = note.key
|
||||||
const folderKey = note.storage + '-' + note.folder
|
const folderKey = note.storage + '-' + note.folder
|
||||||
state.noteMap.set(uniqueKey, note)
|
state.noteMap.set(uniqueKey, note)
|
||||||
|
|
||||||
@@ -483,7 +483,7 @@ function data (state = defaultDataMap(), action) {
|
|||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||||
state.starredSet = new Set(state.starredSet)
|
state.starredSet = new Set(state.starredSet)
|
||||||
notes.forEach((note) => {
|
notes.forEach((note) => {
|
||||||
const noteKey = storage.key + '-' + note.key
|
const noteKey = note.key
|
||||||
state.noteMap.delete(noteKey)
|
state.noteMap.delete(noteKey)
|
||||||
state.starredSet.delete(noteKey)
|
state.starredSet.delete(noteKey)
|
||||||
note.tags.forEach((tag) => {
|
note.tags.forEach((tag) => {
|
||||||
|
|||||||
86
docs/zh_TW/build.md
Normal file
86
docs/zh_TW/build.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# 編譯
|
||||||
|
此文件還提供下列的語言 [日文](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [韓文](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [俄文](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [簡體中文](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [法文](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) and [德文](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
|
||||||
|
|
||||||
|
## 環境
|
||||||
|
* npm: 4.x
|
||||||
|
* node: 7.x
|
||||||
|
|
||||||
|
`$ grunt pre-build` 在 `npm v5.x` 有問題,所以只能用 `npm v4.x` 。
|
||||||
|
|
||||||
|
## 開發
|
||||||
|
|
||||||
|
我們使用 Webpack HMR 來開發 Boostnote。
|
||||||
|
|
||||||
|
在專案根目錄底下執行下列指令,將會以原始設置啟動 Boostnote。
|
||||||
|
|
||||||
|
**用 yarn 來安裝必要 packages**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ yarn
|
||||||
|
```
|
||||||
|
|
||||||
|
**開始開發**
|
||||||
|
|
||||||
|
```
|
||||||
|
$ yarn run dev-start
|
||||||
|
```
|
||||||
|
|
||||||
|
上述指令同時運行了 `yarn run webpack` 及 `yarn run hot`,相當於將這兩個指令在不同的 terminal 中運行。
|
||||||
|
|
||||||
|
`webpack` 會同時監控修改過的程式碼,並
|
||||||
|
The `webpack` will watch for code changes and then apply them automatically.
|
||||||
|
|
||||||
|
If the following error occurs: `Failed to load resource: net::ERR_CONNECTION_REFUSED`, please reload Boostnote.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> ### Notice
|
||||||
|
> There are some cases where you have to refresh the app manually.
|
||||||
|
> 1. When editing a constructor method of a component
|
||||||
|
> 2. When adding a new css class (similar to 1: the CSS class is re-written by each component. This process occurs at the Constructor method.)
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
We use Grunt to automate deployment.
|
||||||
|
You can build the program by using `grunt`. However, we don't recommend this because the default task includes codesign and authenticode.
|
||||||
|
|
||||||
|
So, we've prepared a separate script which just makes an executable file.
|
||||||
|
|
||||||
|
This build doesn't work on npm v5.3.0. So you need to use v5.2.0 when you build it.
|
||||||
|
|
||||||
|
```
|
||||||
|
grunt pre-build
|
||||||
|
```
|
||||||
|
|
||||||
|
You will find the executable in the `dist` directory. Note, the auto updater won't work because the app isn't signed.
|
||||||
|
|
||||||
|
If you find it necessary, you can use codesign or authenticode with this executable.
|
||||||
|
|
||||||
|
## Make own distribution packages (deb, rpm)
|
||||||
|
|
||||||
|
Distribution packages are created by exec `grunt build` on Linux platform (e.g. Ubuntu, Fedora).
|
||||||
|
|
||||||
|
> Note: You can create both `.deb` and `.rpm` in a single environment.
|
||||||
|
|
||||||
|
After installing the supported version of `node` and `npm`, install build dependency packages.
|
||||||
|
|
||||||
|
|
||||||
|
Ubuntu/Debian:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo apt-get install -y rpm fakeroot
|
||||||
|
```
|
||||||
|
|
||||||
|
Fedora:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo dnf install -y dpkg dpkg-dev rpm-build fakeroot
|
||||||
|
```
|
||||||
|
|
||||||
|
Then execute `grunt build`.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ grunt build
|
||||||
|
```
|
||||||
|
|
||||||
|
You will find `.deb` and `.rpm` in the `dist` directory.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "boost",
|
"name": "boost",
|
||||||
"productName": "Boostnote",
|
"productName": "Boostnote",
|
||||||
"version": "0.10.0",
|
"version": "0.11.1",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"description": "Boostnote",
|
"description": "Boostnote",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
"iconv-lite": "^0.4.19",
|
"iconv-lite": "^0.4.19",
|
||||||
"immutable": "^3.8.1",
|
"immutable": "^3.8.1",
|
||||||
"js-sequence-diagrams": "^1000000.0.6",
|
"js-sequence-diagrams": "^1000000.0.6",
|
||||||
"katex": "^0.8.3",
|
"katex": "^0.9.0",
|
||||||
"lodash": "^4.11.1",
|
"lodash": "^4.11.1",
|
||||||
"lodash-move": "^1.1.1",
|
"lodash-move": "^1.1.1",
|
||||||
"markdown-it": "^6.0.1",
|
"markdown-it": "^6.0.1",
|
||||||
@@ -89,7 +89,8 @@
|
|||||||
"sanitize-html": "^1.18.2",
|
"sanitize-html": "^1.18.2",
|
||||||
"striptags": "^2.2.1",
|
"striptags": "^2.2.1",
|
||||||
"superagent": "^1.2.0",
|
"superagent": "^1.2.0",
|
||||||
"superagent-promise": "^1.0.3"
|
"superagent-promise": "^1.0.3",
|
||||||
|
"uuid": "^3.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"ava": "^0.16.0",
|
"ava": "^0.16.0",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
:mega: Open sourcing our [Android and iOS apps](https://github.com/BoostIO/Boostnote-mobile)!
|
:mega: We've launched a blogging platform with markdown called **[Boostlog](https://boostlog.io/)**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -25,8 +25,8 @@ Boostnote is an open source project. It's an independent project with its ongoin
|
|||||||
## Community
|
## Community
|
||||||
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
|
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
|
||||||
- [Twitter](https://twitter.com/boostnoteapp)
|
- [Twitter](https://twitter.com/boostnoteapp)
|
||||||
- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzAzMjI1MTIyNTQ3LTc2MjNiYWU3NTc1YjZlMTk3NzFmOWE1ZWU1MGRhMzBkMGIwMWFjOWMxMDRiM2I2NzkzYzc4OGZhNmVhZjYzZTM)
|
- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzI3NTIxMTQzMTQzLTUyYWZmZWM1YzcwYzQ5OWQ5YzA3Y2M2NzUzNmIwNzYzMjg5NmQyOGJlNzcyZDJhMGY0ZDc0ZjdlZDFhMDdiMWE)
|
||||||
- [Blog](https://boostlog.io/@junp1234)
|
- [Blog](https://boostlog.io/tags/boostnote)
|
||||||
- [Reddit](https://www.reddit.com/r/Boostnote/)
|
- [Reddit](https://www.reddit.com/r/Boostnote/)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
12
tests/fixtures/TestDummy.js
vendored
12
tests/fixtures/TestDummy.js
vendored
@@ -22,7 +22,7 @@ function dummyBoostnoteJSONData (override = {}, isLegacy = false) {
|
|||||||
if (override.folders == null) {
|
if (override.folders == null) {
|
||||||
data.folders = []
|
data.folders = []
|
||||||
|
|
||||||
var folderCount = Math.floor((Math.random() * 5)) + 1
|
var folderCount = Math.floor((Math.random() * 5)) + 2
|
||||||
for (var i = 0; i < folderCount; i++) {
|
for (var i = 0; i < folderCount; i++) {
|
||||||
var key = keygen()
|
var key = keygen()
|
||||||
while (data.folders.some((folder) => folder.key === key)) {
|
while (data.folders.some((folder) => folder.key === key)) {
|
||||||
@@ -105,11 +105,11 @@ function dummyStorage (storagePath, override = {}) {
|
|||||||
|
|
||||||
sander.writeFileSync(path.join(storagePath, 'boostnote.json'), JSON.stringify(jsonData))
|
sander.writeFileSync(path.join(storagePath, 'boostnote.json'), JSON.stringify(jsonData))
|
||||||
var notesData = []
|
var notesData = []
|
||||||
var noteCount = Math.floor((Math.random() * 15)) + 1
|
var noteCount = Math.floor((Math.random() * 15)) + 2
|
||||||
for (var i = 0; i < noteCount; i++) {
|
for (var i = 0; i < noteCount; i++) {
|
||||||
var key = keygen()
|
var key = keygen(true)
|
||||||
while (notesData.some((note) => note.key === key)) {
|
while (notesData.some((note) => note.key === key)) {
|
||||||
key = keygen()
|
key = keygen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
var noteData = dummyNote({
|
var noteData = dummyNote({
|
||||||
@@ -149,9 +149,9 @@ function dummyLegacyStorage (storagePath, override = {}) {
|
|||||||
var folderNotes = []
|
var folderNotes = []
|
||||||
var noteCount = Math.floor((Math.random() * 5)) + 1
|
var noteCount = Math.floor((Math.random() * 5)) + 1
|
||||||
for (var i = 0; i < noteCount; i++) {
|
for (var i = 0; i < noteCount; i++) {
|
||||||
var key = keygen(6)
|
var key = keygen(true)
|
||||||
while (folderNotes.some((note) => note.key === key)) {
|
while (folderNotes.some((note) => note.key === key)) {
|
||||||
key = keygen(6)
|
key = keygen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
var noteData = dummyNote({
|
var noteData = dummyNote({
|
||||||
|
|||||||
22
yarn.lock
22
yarn.lock
@@ -3986,11 +3986,11 @@ jsx-ast-utils@^2.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
array-includes "^3.0.3"
|
array-includes "^3.0.3"
|
||||||
|
|
||||||
katex@^0.8.3:
|
katex@^0.9.0:
|
||||||
version "0.8.3"
|
version "0.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/katex/-/katex-0.8.3.tgz#909d99864baf964c3ccae39c4a99a8e0fb9a1bd0"
|
resolved "https://registry.yarnpkg.com/katex/-/katex-0.9.0.tgz#26a7d082c21d53725422d2d71da9b2d8455fbd4a"
|
||||||
dependencies:
|
dependencies:
|
||||||
match-at "^0.1.0"
|
match-at "^0.1.1"
|
||||||
|
|
||||||
kind-of@^3.0.2:
|
kind-of@^3.0.2:
|
||||||
version "3.2.2"
|
version "3.2.2"
|
||||||
@@ -4254,9 +4254,9 @@ markdown-it@^6.0.1:
|
|||||||
mdurl "~1.0.1"
|
mdurl "~1.0.1"
|
||||||
uc.micro "^1.0.1"
|
uc.micro "^1.0.1"
|
||||||
|
|
||||||
match-at@^0.1.0:
|
match-at@^0.1.1:
|
||||||
version "0.1.0"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/match-at/-/match-at-0.1.0.tgz#f561e7709ff9a105b85cc62c6b8ee7c15bf24f31"
|
resolved "https://registry.yarnpkg.com/match-at/-/match-at-0.1.1.tgz#25d040d291777704d5e6556bbb79230ec2de0540"
|
||||||
|
|
||||||
matcher@^0.1.1:
|
matcher@^0.1.1:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
@@ -6169,10 +6169,6 @@ sprintf-js@~1.0.2:
|
|||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||||
|
|
||||||
sprintf@^0.1.5:
|
|
||||||
version "0.1.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/sprintf/-/sprintf-0.1.5.tgz#8f83e39a9317c1a502cb7db8050e51c679f6edcf"
|
|
||||||
|
|
||||||
srcset@^1.0.0:
|
srcset@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef"
|
resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef"
|
||||||
@@ -6812,6 +6808,10 @@ uuid@^2.0.1, uuid@^2.0.2:
|
|||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
|
||||||
|
|
||||||
|
uuid@^3.2.1:
|
||||||
|
version "3.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
|
||||||
|
|
||||||
validate-npm-package-license@^3.0.1:
|
validate-npm-package-license@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
|
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
|
||||||
|
|||||||
Reference in New Issue
Block a user