1
0
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:
Simon
2018-03-13 09:24:53 +01:00
committed by GitHub
27 changed files with 435 additions and 285 deletions

View File

@@ -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)}

View File

@@ -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
} }

View File

@@ -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}

View File

@@ -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'
> >

View File

@@ -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

View File

@@ -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'
> >

View File

@@ -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,32 +24,35 @@ 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
}) => ( }) => {
<button styleName={isActive const FolderDragger = SortableHandle(({ className }) => <i className={className} />)
return (
<button styleName={isActive
? 'folderList-item--active' ? 'folderList-item--active'
: 'folderList-item' : 'folderList-item'
} }
onClick={handleButtonClick} onClick={handleButtonClick}
onContextMenu={handleContextMenu} onContextMenu={handleContextMenu}
onDrop={handleDrop} onDrop={handleDrop}
onDragEnter={handleDragEnter} onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave} onDragLeave={handleDragLeave}
> >
<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>
{(!isFolded && _.isNumber(noteCount)) &&
<span styleName='folderList-item-noteCount'>{noteCount}</span>
}
{isFolded &&
<span styleName='folderList-item-tooltip'>
{folderName}
</span> </span>
} {(!isFolded && _.isNumber(noteCount)) &&
</button> <span styleName='folderList-item-noteCount'>{noteCount}</span>
) }
{isFolded &&
<span styleName='folderList-item-tooltip'>
{folderName}
</span>
}
</button>
)
}
StorageItem.propTypes = { StorageItem.propTypes = {
isActive: PropTypes.bool.isRequired, isActive: PropTypes.bool.isRequired,

View File

@@ -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')
} }

View File

@@ -20,181 +20,188 @@ 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 {
typographer: true, constructor (options = {}) {
linkify: true, const defaultOptions = {
html: true, typographer: true,
xhtmlOut: true, linkify: true,
breaks: true, html: true,
highlight: function (str, lang) { xhtmlOut: true,
const delimiter = ':' breaks: true,
const langInfo = lang.split(delimiter) highlight: function (str, lang) {
const langType = langInfo[0] const delimiter = ':'
const fileName = langInfo[1] || '' const langInfo = lang.split(delimiter)
const firstLineNumber = parseInt(langInfo[2], 10) const langType = langInfo[0]
const fileName = langInfo[1] || ''
const firstLineNumber = parseInt(langInfo[2], 10)
if (langType === 'flowchart') { if (langType === 'flowchart') {
return `<pre class="flowchart">${str}</pre>` return `<pre class="flowchart">${str}</pre>`
}
if (langType === 'sequence') {
return `<pre class="sequence">${str}</pre>`
}
return '<pre class="code">' +
'<span class="filename">' + fileName + '</span>' +
createGutter(str, firstLineNumber) +
'<code class="' + langType + '">' +
str +
'</code></pre>'
}
})
// 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 = `<span class="katex-error">${err.message}</span>`
}
return output
},
blockRenderer: function (str) {
let output = ''
try {
output = katex.renderToString(str.trim(), { displayMode: true })
} catch (err) {
output = `<div class="katex-error">${err.message}</div>`
}
return output
}
})
md.use(require('markdown-it-imsize'))
md.use(require('markdown-it-footnote'))
md.use(require('markdown-it-multimd-table'))
md.use(require('markdown-it-named-headers'), {
slugify: (header) => {
return encodeURI(header.trim()
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
.replace(/\s+/g, '-'))
.replace(/\-+$/, '')
}
})
md.use(require('markdown-it-kbd'))
const deflate = require('markdown-it-plantuml/lib/deflate')
md.use(require('markdown-it-plantuml'), '', {
generateSource: function (umlCode) {
const s = unescape(encodeURIComponent(umlCode))
const zippedCode = deflate.encode64(
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
)
return `http://www.plantuml.com/plantuml/svg/${zippedCode}`
}
})
// Override task item
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
let content, terminate, i, l, token
let nextLine = startLine + 1
const terminatorRules = state.md.block.ruler.getRules('paragraph')
const endLine = state.lineMax
// jump line-by-line until empty one or EOF
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
// this would be a code block normally, but after paragraph
// it's considered a lazy continuation regardless of what's there
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
// quirk for blockquotes, this line should already be checked by that rule
if (state.sCount[nextLine] < 0) { continue }
// Some tags can terminate paragraph without empty line.
terminate = false
for (i = 0, l = terminatorRules.length; i < l; i++) {
if (terminatorRules[i](state, nextLine, endLine, true)) {
terminate = true
break
}
}
if (terminate) { break }
}
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
state.line = nextLine
token = state.push('paragraph_open', 'p', 1)
token.map = [startLine, state.line]
if (state.parentType === 'list') {
const match = content.match(/^\[( |x)\] ?(.+)/i)
if (match) {
const liToken = lastFindInArray(state.tokens, token => token.type === 'list_item_open')
if (liToken) {
if (!liToken.attrs) {
liToken.attrs = []
} }
liToken.attrs.push(['class', 'taskListItem']) if (langType === 'sequence') {
return `<pre class="sequence">${str}</pre>`
}
return '<pre class="code CodeMirror">' +
'<span class="filename">' + fileName + '</span>' +
createGutter(str, firstLineNumber) +
'<code class="' + langType + '">' +
str +
'</code></pre>'
} }
content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
} }
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 = `<span class="katex-error">${err.message}</span>`
}
return output
},
blockRenderer: function (str) {
let output = ''
try {
output = katex.renderToString(str.trim(), { displayMode: true })
} catch (err) {
output = `<div class="katex-error">${err.message}</div>`
}
return output
}
})
this.md.use(require('markdown-it-imsize'))
this.md.use(require('markdown-it-footnote'))
this.md.use(require('markdown-it-multimd-table'))
this.md.use(require('markdown-it-named-headers'), {
slugify: (header) => {
return encodeURI(header.trim()
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
.replace(/\s+/g, '-'))
.replace(/\-+$/, '')
}
})
this.md.use(require('markdown-it-kbd'))
const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), '', {
generateSource: function (umlCode) {
const s = unescape(encodeURIComponent(umlCode))
const zippedCode = deflate.encode64(
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
)
return `http://www.plantuml.com/plantuml/svg/${zippedCode}`
}
})
// Override task item
this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
let content, terminate, i, l, token
let nextLine = startLine + 1
const terminatorRules = state.md.block.ruler.getRules('paragraph')
const endLine = state.lineMax
// jump line-by-line until empty one or EOF
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
// this would be a code block normally, but after paragraph
// it's considered a lazy continuation regardless of what's there
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
// quirk for blockquotes, this line should already be checked by that rule
if (state.sCount[nextLine] < 0) { continue }
// Some tags can terminate paragraph without empty line.
terminate = false
for (i = 0, l = terminatorRules.length; i < l; i++) {
if (terminatorRules[i](state, nextLine, endLine, true)) {
terminate = true
break
}
}
if (terminate) { break }
}
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
state.line = nextLine
token = state.push('paragraph_open', 'p', 1)
token.map = [startLine, state.line]
if (state.parentType === 'list') {
const match = content.match(/^\[( |x)\] ?(.+)/i)
if (match) {
const liToken = lastFindInArray(state.tokens, token => token.type === 'list_item_open')
if (liToken) {
if (!liToken.attrs) {
liToken.attrs = []
}
liToken.attrs.push(['class', 'taskListItem'])
}
content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
}
}
token = state.push('inline', '', 0)
token.content = content
token.map = [startLine, state.line]
token.children = []
token = state.push('paragraph_close', 'p', -1)
return true
})
// Add line number attribute for scrolling
const originalRender = this.md.renderer.render
this.md.renderer.render = (tokens, options, env) => {
tokens.forEach((token) => {
switch (token.type) {
case 'heading_open':
case 'paragraph_open':
case 'blockquote_open':
case 'table_open':
token.attrPush(['data-line', token.map[0]])
}
})
const result = originalRender.call(this.md.renderer, tokens, options, env)
return result
}
// FIXME We should not depend on global variable.
window.md = this.md
} }
token = state.push('inline', '', 0) render (content) {
token.content = content
token.map = [startLine, state.line]
token.children = []
token = state.push('paragraph_close', 'p', -1)
return true
})
// Add line number attribute for scrolling
const originalRender = md.renderer.render
md.renderer.render = function render (tokens, options, env) {
tokens.forEach((token) => {
switch (token.type) {
case 'heading_open':
case 'paragraph_open':
case 'blockquote_open':
case 'table_open':
token.attrPush(['data-line', token.map[0]])
}
})
const result = originalRender.call(md.renderer, tokens, options, env)
return result
}
// FIXME We should not depend on global variable.
window.md = md
function normalizeLinkText (linkText) {
return md.normalizeLinkText(linkText)
}
const markdown = {
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 normalizeLinkText (linkText) {
return this.md.normalizeLinkText(linkText)
}
} }
export default markdown export default Markdown

View File

@@ -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}

View File

@@ -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}

View File

@@ -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) {

View File

@@ -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}
}) })
}) })
} }

View File

@@ -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)}
@@ -256,9 +259,9 @@ class StorageItem extends React.Component {
key={storage.key} key={storage.key}
> >
<div styleName={isActive <div styleName={isActive
? 'header--active' ? 'header--active'
: 'header' : 'header'
} }
onContextMenu={(e) => this.handleHeaderContextMenu(e)} onContextMenu={(e) => this.handleHeaderContextMenu(e)}
> >
<button styleName='header-toggleButton' <button styleName='header-toggleButton'
@@ -267,7 +270,7 @@ class StorageItem extends React.Component {
<img src={this.state.isOpen <img src={this.state.isOpen
? '../resources/icon/icon-down.svg' ? '../resources/icon/icon-down.svg'
: '../resources/icon/icon-right.svg' : '../resources/icon/icon-right.svg'
} }
/> />
</button> </button>

View File

@@ -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 = {}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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')
}) })
} }

View File

@@ -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'
/>&nbsp;
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')}

View File

@@ -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
View 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.
![net::ERR_CONNECTION_REFUSED](https://cloud.githubusercontent.com/assets/11307908/24343004/081e66ae-1279-11e7-8d9e-7f478043d835.png)
> ### 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.

View File

@@ -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",

View File

@@ -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/)**.
![Boostnote app screenshot](./resources/repository/top.png) ![Boostnote app screenshot](./resources/repository/top.png)
@@ -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/)

View File

@@ -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({

View File

@@ -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"
@@ -6168,11 +6168,7 @@ speedometer@~0.1.2:
sprintf-js@~1.0.2: 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"