1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 09:46:22 +00:00

Merge branch 'master' into translation-pt

This commit is contained in:
Fábio Rosado
2018-11-06 09:06:01 +00:00
committed by GitHub
69 changed files with 1159 additions and 276 deletions

View File

@@ -14,6 +14,8 @@ import consts from 'browser/lib/consts'
import fs from 'fs' import fs from 'fs'
const { ipcRenderer } = require('electron') const { ipcRenderer } = require('electron')
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
import TurndownService from 'turndown'
import { gfm } from 'turndown-plugin-gfm'
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
@@ -57,6 +59,7 @@ export default class CodeEditor extends React.Component {
} }
this.searchHandler = (e, msg) => this.handleSearch(msg) this.searchHandler = (e, msg) => this.handleSearch(msg)
this.searchState = null this.searchState = null
this.scrollToLineHandeler = this.scrollToLine.bind(this)
this.formatTable = () => this.handleFormatTable() this.formatTable = () => this.handleFormatTable()
this.editorActivityHandler = () => this.handleEditorActivity() this.editorActivityHandler = () => this.handleEditorActivity()
@@ -125,6 +128,7 @@ export default class CodeEditor extends React.Component {
componentDidMount () { componentDidMount () {
const { rulers, enableRulers } = this.props const { rulers, enableRulers } = this.props
const expandSnippet = this.expandSnippet.bind(this) const expandSnippet = this.expandSnippet.bind(this)
eventEmitter.on('line:jump', this.scrollToLineHandeler)
const defaultSnippet = [ const defaultSnippet = [
{ {
@@ -475,7 +479,13 @@ export default class CodeEditor extends React.Component {
moveCursorTo (row, col) {} moveCursorTo (row, col) {}
scrollToLine (num) {} scrollToLine (event, num) {
const cursor = {
line: num,
ch: 1
}
this.editor.setCursor(cursor)
}
focus () { focus () {
this.editor.focus() this.editor.focus()
@@ -538,7 +548,11 @@ export default class CodeEditor extends React.Component {
) )
return prevChar === '](' && nextChar === ')' return prevChar === '](' && nextChar === ')'
} }
if (dataTransferItem.type.match('image')) {
const pastedHtml = clipboardData.getData('text/html')
if (pastedHtml !== '') {
this.handlePasteHtml(e, editor, pastedHtml)
} else if (dataTransferItem.type.match('image')) {
attachmentManagement.handlePastImageEvent( attachmentManagement.handlePastImageEvent(
this, this,
storageKey, storageKey,
@@ -608,6 +622,12 @@ export default class CodeEditor extends React.Component {
}) })
} }
handlePasteHtml (e, editor, pastedHtml) {
e.preventDefault()
const markdown = this.turndownService.turndown(pastedHtml)
editor.replaceSelection(markdown)
}
mapNormalResponse (response, pastedTxt) { mapNormalResponse (response, pastedTxt) {
return this.decodeResponse(response).then(body => { return this.decodeResponse(response).then(body => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@@ -6,6 +6,7 @@ import CodeEditor from 'browser/components/CodeEditor'
import MarkdownPreview from 'browser/components/MarkdownPreview' import MarkdownPreview from 'browser/components/MarkdownPreview'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import { findStorage } from 'browser/lib/findStorage' import { findStorage } from 'browser/lib/findStorage'
import ConfigManager from 'browser/main/lib/ConfigManager'
class MarkdownEditor extends React.Component { class MarkdownEditor extends React.Component {
constructor (props) { constructor (props) {
@@ -18,7 +19,7 @@ class MarkdownEditor extends React.Component {
this.supportMdSelectionBold = [16, 17, 186] this.supportMdSelectionBold = [16, 17, 186]
this.state = { this.state = {
status: 'PREVIEW', status: props.config.editor.switchPreview === 'RIGHTCLICK' ? props.config.editor.delfaultStatus : 'PREVIEW',
renderValue: props.value, renderValue: props.value,
keyPressed: new Set(), keyPressed: new Set(),
isLocked: false isLocked: false
@@ -64,6 +65,10 @@ class MarkdownEditor extends React.Component {
}) })
} }
setValue (value) {
this.refs.code.setValue(value)
}
handleChange (e) { handleChange (e) {
this.value = this.refs.code.value this.value = this.refs.code.value
this.props.onChange(e) this.props.onChange(e)
@@ -72,9 +77,7 @@ class MarkdownEditor extends React.Component {
handleContextMenu (e) { handleContextMenu (e) {
const { config } = this.props const { config } = this.props
if (config.editor.switchPreview === 'RIGHTCLICK') { if (config.editor.switchPreview === 'RIGHTCLICK') {
const newStatus = this.state.status === 'PREVIEW' const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW'
? 'CODE'
: 'PREVIEW'
this.setState({ this.setState({
status: newStatus status: newStatus
}, () => { }, () => {
@@ -84,6 +87,10 @@ class MarkdownEditor extends React.Component {
this.refs.preview.focus() this.refs.preview.focus()
} }
eventEmitter.emit('topbar:togglelockbutton', this.state.status) eventEmitter.emit('topbar:togglelockbutton', this.state.status)
const newConfig = Object.assign({}, config)
newConfig.editor.delfaultStatus = newStatus
ConfigManager.set(newConfig)
}) })
} }
} }
@@ -300,6 +307,7 @@ class MarkdownEditor extends React.Component {
noteKey={noteKey} noteKey={noteKey}
customCSS={config.preview.customCSS} customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS} allowCustomCSS={config.preview.allowCustomCSS}
lineThroughCheckbox={config.preview.lineThroughCheckbox}
/> />
</div> </div>
) )

View File

@@ -16,7 +16,6 @@ import convertModeName from 'browser/lib/convertModeName'
import copy from 'copy-to-clipboard' import copy from 'copy-to-clipboard'
import mdurl from 'mdurl' import mdurl from 'mdurl'
import exportNote from 'browser/main/lib/dataApi/exportNote' import exportNote from 'browser/main/lib/dataApi/exportNote'
import { escapeHtmlCharacters } from 'browser/lib/utils'
import context from 'browser/lib/context' import context from 'browser/lib/context'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import fs from 'fs' import fs from 'fs'
@@ -80,7 +79,6 @@ function buildStyle (
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'), url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype'); url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
} }
${allowCustomCSS ? customCSS : ''}
${markdownStyle} ${markdownStyle}
body { body {
@@ -88,6 +86,11 @@ body {
font-size: ${fontSize}px; font-size: ${fontSize}px;
${scrollPastEnd && 'padding-bottom: 90vh;'} ${scrollPastEnd && 'padding-bottom: 90vh;'}
} }
@media print {
body {
padding-bottom: initial;
}
}
code { code {
font-family: '${codeBlockFontFamily.join("','")}'; font-family: '${codeBlockFontFamily.join("','")}';
background-color: rgba(0,0,0,0.04); background-color: rgba(0,0,0,0.04);
@@ -144,6 +147,8 @@ body p {
display: none display: none
} }
} }
${allowCustomCSS ? customCSS : ''}
` `
} }
@@ -325,9 +330,7 @@ export default class MarkdownPreview extends React.Component {
allowCustomCSS, allowCustomCSS,
customCSS customCSS
) )
let body = this.markdown.render( let body = this.markdown.render(noteContent)
escapeHtmlCharacters(noteContent, { detectCodeBlock: true })
)
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent( const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
noteContent, noteContent,
@@ -484,10 +487,6 @@ export default class MarkdownPreview extends React.Component {
eventEmitter.on('export:save-md', this.saveAsMdHandler) eventEmitter.on('export:save-md', this.saveAsMdHandler)
eventEmitter.on('export:save-html', this.saveAsHtmlHandler) eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
eventEmitter.on('print', this.printHandler) eventEmitter.on('print', this.printHandler)
eventEmitter.on('config-renew', () => {
this.markdown.updateConfig()
this.rewriteIframe()
})
} }
componentWillUnmount () { componentWillUnmount () {
@@ -531,7 +530,8 @@ export default class MarkdownPreview extends React.Component {
prevProps.smartQuotes !== this.props.smartQuotes || prevProps.smartQuotes !== this.props.smartQuotes ||
prevProps.sanitize !== this.props.sanitize || prevProps.sanitize !== this.props.sanitize ||
prevProps.smartArrows !== this.props.smartArrows || prevProps.smartArrows !== this.props.smartArrows ||
prevProps.breaks !== this.props.breaks prevProps.breaks !== this.props.breaks ||
prevProps.lineThroughCheckbox !== this.props.lineThroughCheckbox
) { ) {
this.initMarkdown() this.initMarkdown()
this.rewriteIframe() this.rewriteIframe()
@@ -737,7 +737,6 @@ export default class MarkdownPreview extends React.Component {
el.addEventListener('click', this.linkClickHandler) el.addEventListener('click', this.linkClickHandler)
}) })
} catch (e) { } catch (e) {
console.error(e)
el.className = 'flowchart-error' el.className = 'flowchart-error'
el.innerHTML = 'Flowchart parse error: ' + e.message el.innerHTML = 'Flowchart parse error: ' + e.message
} }
@@ -758,7 +757,6 @@ export default class MarkdownPreview extends React.Component {
el.addEventListener('click', this.linkClickHandler) el.addEventListener('click', this.linkClickHandler)
}) })
} catch (e) { } catch (e) {
console.error(e)
el.className = 'sequence-error' el.className = 'sequence-error'
el.innerHTML = 'Sequence diagram parse error: ' + e.message el.innerHTML = 'Sequence diagram parse error: ' + e.message
} }
@@ -771,12 +769,18 @@ export default class MarkdownPreview extends React.Component {
try { try {
const chartConfig = JSON.parse(el.innerHTML) const chartConfig = JSON.parse(el.innerHTML)
el.innerHTML = '' el.innerHTML = ''
var canvas = document.createElement('canvas')
const canvas = document.createElement('canvas')
el.appendChild(canvas) el.appendChild(canvas)
/* eslint-disable no-new */
new Chart(canvas, chartConfig) const height = el.attributes.getNamedItem('data-height')
if (height && height.value !== 'undefined') {
el.style.height = height.value + 'vh'
canvas.height = height.value + 'vh'
}
const chart = new Chart(canvas, chartConfig)
} catch (e) { } catch (e) {
console.error(e)
el.className = 'chart-error' el.className = 'chart-error'
el.innerHTML = 'chartjs diagram parse error: ' + e.message el.innerHTML = 'chartjs diagram parse error: ' + e.message
} }
@@ -860,6 +864,15 @@ export default class MarkdownPreview extends React.Component {
return return
} }
const regexIsLine = /^:line:[0-9]/
if (regexIsLine.test(linkHash)) {
const numberPattern = /\d+/g
const lineNumber = parseInt(linkHash.match(numberPattern)[0])
eventEmitter.emit('line:jump', lineNumber)
return
}
// this will match the old link format storage.key-note.key // this will match the old link format storage.key-note.key
// e.g. // e.g.
// 877f99c3268608328037-1c211eb7dcb463de6490 // 877f99c3268608328037-1c211eb7dcb463de6490

View File

@@ -20,12 +20,18 @@ class MarkdownSplitEditor extends React.Component {
} }
} }
setValue (value) {
this.refs.code.setValue(value)
}
handleOnChange () { handleOnChange () {
this.value = this.refs.code.value this.value = this.refs.code.value
this.props.onChange() this.props.onChange()
} }
handleScroll (e) { handleScroll (e) {
if (!this.props.config.preview.scrollSync) return
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document') const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
const codeDoc = _.get(this, 'refs.code.editor.doc') const codeDoc = _.get(this, 'refs.code.editor.doc')
let srcTop, srcHeight, targetTop, targetHeight let srcTop, srcHeight, targetTop, targetHeight
@@ -192,6 +198,7 @@ class MarkdownSplitEditor extends React.Component {
noteKey={noteKey} noteKey={noteKey}
customCSS={config.preview.customCSS} customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS} allowCustomCSS={config.preview.allowCustomCSS}
lineThroughCheckbox={config.preview.lineThroughCheckbox}
/> />
</div> </div>
) )

View File

@@ -24,16 +24,19 @@ const TagElement = ({ tagName }) => (
/** /**
* @description Tag element list component. * @description Tag element list component.
* @param {Array|null} tags * @param {Array|null} tags
* @param {boolean} showTagsAlphabetically
* @return {React.Component} * @return {React.Component}
*/ */
const TagElementList = tags => { const TagElementList = (tags, showTagsAlphabetically) => {
if (!isArray(tags)) { if (!isArray(tags)) {
return [] return []
} }
const tagElements = tags.map(tag => TagElement({ tagName: tag })) if (showTagsAlphabetically) {
return _.sortBy(tags).map(tag => TagElement({ tagName: tag }))
return tagElements } else {
return tags.map(tag => TagElement({ tagName: tag }))
}
} }
/** /**
@@ -55,7 +58,8 @@ const NoteItem = ({
pathname, pathname,
storageName, storageName,
folderName, folderName,
viewType viewType,
showTagsAlphabetically
}) => ( }) => (
<div <div
styleName={isActive ? 'item--active' : 'item'} styleName={isActive ? 'item--active' : 'item'}
@@ -93,7 +97,7 @@ const NoteItem = ({
<div styleName='item-bottom'> <div styleName='item-bottom'>
<div styleName='item-bottom-tagList'> <div styleName='item-bottom-tagList'>
{note.tags.length > 0 {note.tags.length > 0
? TagElementList(note.tags) ? TagElementList(note.tags, showTagsAlphabetically)
: <span : <span
style={{ fontStyle: 'italic', opacity: 0.5 }} style={{ fontStyle: 'italic', opacity: 0.5 }}
styleName='item-bottom-tagList-empty' styleName='item-bottom-tagList-empty'

View File

@@ -7,18 +7,18 @@ import styles from './StorageList.styl'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
/** /**
* @param {Array} storgaeList * @param {Array} storageList
*/ */
const StorageList = ({storageList, isFolded}) => ( const StorageList = ({storageList, isFolded}) => (
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}> <div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
{storageList.length > 0 ? storageList : ( {storageList.length > 0 ? storageList : (
<div styleName='storgaeList-empty'>No storage mount.</div> <div styleName='storageList-empty'>No storage mount.</div>
)} )}
</div> </div>
) )
StorageList.propTypes = { StorageList.propTypes = {
storgaeList: PropTypes.arrayOf(PropTypes.element).isRequired storageList: PropTypes.arrayOf(PropTypes.element).isRequired
} }
export default CSSModules(StorageList, styles) export default CSSModules(StorageList, styles)

View File

@@ -14,8 +14,8 @@ import CSSModules from 'browser/lib/CSSModules'
* @param {bool} isRelated * @param {bool} isRelated
*/ */
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isActive, isRelated, count}) => ( const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count}) => (
<div styleName='tagList-itemContainer'> <div styleName='tagList-itemContainer' onContextMenu={e => handleContextMenu(e, name)}>
{isRelated {isRelated
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}> ? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
<i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} /> <i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
@@ -25,7 +25,7 @@ const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isAc
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}> <button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
<span styleName='tagList-item-name'> <span styleName='tagList-item-name'>
{`# ${name}`} {`# ${name}`}
<span styleName='tagList-item-count'>{count}</span> <span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span>
</span> </span>
</button> </button>
</div> </div>

View File

@@ -12,7 +12,7 @@ import styles from './TodoListPercentage.styl'
*/ */
const TodoListPercentage = ({ const TodoListPercentage = ({
percentageOfTodo percentageOfTodo, onClearCheckboxClick
}) => ( }) => (
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}> <div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
<div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}> <div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}>
@@ -20,11 +20,15 @@ const TodoListPercentage = ({
<p styleName='percentageText'>{percentageOfTodo}%</p> <p styleName='percentageText'>{percentageOfTodo}%</p>
</div> </div>
</div> </div>
<div styleName='todoClear'>
<p styleName='todoClearText' onClick={(e) => onClearCheckboxClick(e)}>clear</p>
</div>
</div> </div>
) )
TodoListPercentage.propTypes = { TodoListPercentage.propTypes = {
percentageOfTodo: PropTypes.number.isRequired percentageOfTodo: PropTypes.number.isRequired,
onClearCheckboxClick: PropTypes.func.isRequired
} }
export default CSSModules(TodoListPercentage, styles) export default CSSModules(TodoListPercentage, styles)

View File

@@ -1,4 +1,5 @@
.percentageBar .percentageBar
display: flex
position absolute position absolute
top 72px top 72px
right 0px right 0px
@@ -30,6 +31,20 @@
color #f4f4f4 color #f4f4f4
font-weight 600 font-weight 600
.todoClear
display flex
justify-content: flex-end
position absolute
z-index 120
width 100%
height 100%
padding 2px 10px
.todoClearText
color #f4f4f4
cursor pointer
font-weight 500
body[data-theme="dark"] body[data-theme="dark"]
.percentageBar .percentageBar
background-color #444444 background-color #444444
@@ -39,6 +54,9 @@ body[data-theme="dark"]
.percentageText .percentageText
color $ui-dark-text-color color $ui-dark-text-color
.todoClearText
color $ui-dark-text-color
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.percentageBar .percentageBar
@@ -50,6 +68,9 @@ body[data-theme="solarized-dark"]
.percentageText .percentageText
color #fdf6e3 color #fdf6e3
.todoClearText
color #fdf6e3
body[data-theme="monokai"] body[data-theme="monokai"]
.percentageBar .percentageBar
background-color: $ui-monokai-borderColor background-color: $ui-monokai-borderColor
@@ -69,3 +90,6 @@ body[data-theme="dracula"]
.percentageText .percentageText
color $ui-dracula-text-color color $ui-dracula-text-color
.percentageText
color $ui-dracula-text-color

View File

@@ -209,41 +209,39 @@ code
text-decoration none text-decoration none
margin-right 2px margin-right 2px
pre pre
padding 0.5em !important padding 0.5rem !important
border solid 1px #D1D1D1 border solid 1px #D1D1D1
border-radius 5px border-radius 5px
overflow-x auto overflow-x auto
margin 0 0 1em margin 0 0 1rem
display flex display flex
line-height 1.4em line-height 1.4em
&.flowchart, &.sequence, &.chart
display flex
justify-content center
background-color white
&.CodeMirror
height initial
flex-wrap wrap
&>code
flex 1
overflow-x auto
code code
background-color inherit background-color inherit
margin 0 margin 0
padding 0 padding 0
border none border none
border-radius 0 border-radius 0
&.CodeMirror
height initial
flex-wrap wrap
&>code
flex 1
overflow-x auto
&.mermaid svg
max-width 100% !important
&>span.filename &>span.filename
width 100% margin -0.5rem 100% 0.5rem -0.5rem
border-radius: 5px 0px 0px 0px padding 0.125rem 0.375rem
margin -8px 100% 8px -8px
padding 0px 6px
background-color #777; background-color #777;
color white color white
&:empty
display none
&>span.lineNumber &>span.lineNumber
display none display none
font-size 1em font-size 1em
padding 0.5em 0 padding 0.5rem 0
margin -0.5em 0.5em -0.5em -0.5em margin -0.5rem 0.5rem -0.5rem -0.5rem
border-right 1px solid border-right 1px solid
text-align right text-align right
border-top-left-radius 4px border-top-left-radius 4px
@@ -375,6 +373,19 @@ for name, val in admonition_types
color: val[color] color: val[color]
content: val[icon] content: val[icon]
pre.fence
flex-wrap wrap
.chart, .flowchart, .mermaid, .sequence
display flex
justify-content center
background-color white
max-width 100%
flex-grow 1
canvas, svg
max-width 100% !important
themeDarkBackground = darken(#21252B, 10%) themeDarkBackground = darken(#21252B, 10%)
themeDarkText = #f9f9f9 themeDarkText = #f9f9f9
themeDarkBorder = lighten(themeDarkBackground, 20%) themeDarkBorder = lighten(themeDarkBackground, 20%)

View File

@@ -11,9 +11,9 @@ function getRandomInt (min, max) {
} }
function getId () { function getId () {
var pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' const pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
var id = 'm-' let id = 'm-'
for (var i = 0; i < 7; i++) { for (let i = 0; i < 7; i++) {
id += pool[getRandomInt(0, 16)] id += pool[getRandomInt(0, 16)]
} }
return id return id
@@ -21,16 +21,20 @@ function getId () {
function render (element, content, theme) { function render (element, content, theme) {
try { try {
const height = element.attributes.getNamedItem('data-height')
if (height && height.value !== 'undefined') {
element.style.height = height.value + 'vh'
}
let isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai' || theme === 'dracula' let isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai' || theme === 'dracula'
mermaidAPI.initialize({ mermaidAPI.initialize({
theme: isDarkTheme ? 'dark' : 'default', theme: isDarkTheme ? 'dark' : 'default',
themeCSS: isDarkTheme ? darkThemeStyling : '' themeCSS: isDarkTheme ? darkThemeStyling : '',
useMaxWidth: false
}) })
mermaidAPI.render(getId(), content, (svgGraph) => { mermaidAPI.render(getId(), content, (svgGraph) => {
element.innerHTML = svgGraph element.innerHTML = svgGraph
}) })
} catch (e) { } catch (e) {
console.error(e)
element.className = 'mermaid-error' element.className = 'mermaid-error'
element.innerHTML = 'mermaid diagram parse error: ' + e.message element.innerHTML = 'mermaid diagram parse error: ' + e.message
} }

View File

@@ -49,7 +49,7 @@ const languages = [
}, },
{ {
name: 'Portuguese', name: 'Portuguese',
locale: 'pt' locale: 'pt-BR'
}, },
{ {
name: 'Russian', name: 'Russian',
@@ -61,6 +61,9 @@ const languages = [
}, { }, {
name: 'Turkish', name: 'Turkish',
locale: 'tr' locale: 'tr'
}, {
name: 'Thai',
locale: 'th'
} }
] ]

View File

@@ -1,4 +1,4 @@
export function findNoteTitle (value) { export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleField = 'title') {
const splitted = value.split('\n') const splitted = value.split('\n')
let title = null let title = null
let isInsideCodeBlock = false let isInsideCodeBlock = false
@@ -6,6 +6,11 @@ export function findNoteTitle (value) {
if (splitted[0] === '---') { if (splitted[0] === '---') {
let line = 0 let line = 0
while (++line < splitted.length) { while (++line < splitted.length) {
if (enableFrontMatterTitle && splitted[line].startsWith(frontMatterTitleField + ':')) {
title = splitted[line].substring(frontMatterTitleField.length + 1).trim()
break
}
if (splitted[line] === '---') { if (splitted[line] === '---') {
splitted.splice(0, line + 1) splitted.splice(0, line + 1)
@@ -14,17 +19,19 @@ export function findNoteTitle (value) {
} }
} }
splitted.some((line, index) => { if (title === null) {
const trimmedLine = line.trim() splitted.some((line, index) => {
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim() const trimmedLine = line.trim()
if (trimmedLine.match('```')) { const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
isInsideCodeBlock = !isInsideCodeBlock if (trimmedLine.match('```')) {
} isInsideCodeBlock = !isInsideCodeBlock
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) { }
title = trimmedLine if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
return true title = trimmedLine
} return true
}) }
})
}
if (title === null) { if (title === null) {
title = '' title = ''

View File

@@ -0,0 +1,130 @@
'use strict'
module.exports = function (md, renderers, defaultRenderer) {
function fence (state, startLine, endLine) {
let pos = state.bMarks[startLine] + state.tShift[startLine]
let max = state.eMarks[startLine]
if (state.sCount[startLine] - state.blkIndent >= 4 || pos + 3 > max) {
return false
}
const marker = state.src.charCodeAt(pos)
if (!(marker === 96 || marker === 126)) {
return false
}
let mem = pos
pos = state.skipChars(pos, marker)
let len = pos - mem
if (len < 3) {
return false
}
const markup = state.src.slice(mem, pos)
const params = state.src.slice(pos, max)
let nextLine = startLine
let haveEndMarker = false
while (true) {
nextLine++
if (nextLine >= endLine) {
break
}
pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
max = state.eMarks[nextLine]
if (pos < max && state.sCount[nextLine] < state.blkIndent) {
break
}
if (state.src.charCodeAt(pos) !== marker || state.sCount[nextLine] - state.blkIndent >= 4) {
continue
}
pos = state.skipChars(pos, marker)
if (pos - mem < len) {
continue
}
pos = state.skipSpaces(pos)
if (pos >= max) {
haveEndMarker = true
break
}
}
len = state.sCount[startLine]
state.line = nextLine + (haveEndMarker ? 1 : 0)
const parameters = {}
let langType = ''
let fileName = ''
let firstLineNumber = 1
let match = /^(\w[-\w]*)?(?:\(((?:\s*\w[-\w]*(?:=(?:'(?:.*?[^\\])?'|"(?:.*?[^\\])?"|(?:[^'"][^\s]*)))?)*)\))?(?::([^:]*)(?::(\d+))?)?\s*$/.exec(params)
if (match) {
if (match[1]) {
langType = match[1]
}
if (match[3]) {
fileName = match[3]
}
if (match[4]) {
firstLineNumber = parseInt(match[4], 10)
}
if (match[2]) {
const params = match[2]
const regex = /(\w[-\w]*)(?:=(?:'(.*?[^\\])?'|"(.*?[^\\])?"|([^'"][^\s]*)))?/g
let name, value
while ((match = regex.exec(params))) {
name = match[1]
value = match[2] || match[3] || match[4] || null
const height = /^(\d+)h$/.exec(name)
if (height && !value) {
parameters.height = height[1]
} else {
parameters[name] = value
}
}
}
}
let token
if (renderers[langType]) {
token = state.push(`${langType}_fence`, 'div', 0)
} else {
token = state.push('_fence', 'code', 0)
}
token.langType = langType
token.fileName = fileName
token.firstLineNumber = firstLineNumber
token.parameters = parameters
token.content = state.getLines(startLine + 1, nextLine, len, true)
token.markup = markup
token.map = [startLine, state.line]
return true
}
md.block.ruler.before('fence', '_fence', fence, {
alt: ['paragraph', 'reference', 'blockquote', 'list']
})
for (const name in renderers) {
md.renderer.rules[`${name}_fence`] = (tokens, index) => renderers[name](tokens[index])
}
if (defaultRenderer) {
md.renderer.rules['_fence'] = (tokens, index) => defaultRenderer(tokens[index])
}
}

View File

@@ -2,6 +2,7 @@
import sanitizeHtml from 'sanitize-html' import sanitizeHtml from 'sanitize-html'
import { escapeHtmlCharacters } from './utils' import { escapeHtmlCharacters } from './utils'
import url from 'url'
module.exports = function sanitizePlugin (md, options) { module.exports = function sanitizePlugin (md, options) {
options = options || {} options = options || {}
@@ -14,7 +15,7 @@ module.exports = function sanitizePlugin (md, options) {
options options
) )
} }
if (state.tokens[tokenIdx].type === 'fence') { if (state.tokens[tokenIdx].type === '_fence') {
// escapeHtmlCharacters has better performance // escapeHtmlCharacters has better performance
state.tokens[tokenIdx].content = escapeHtmlCharacters( state.tokens[tokenIdx].content = escapeHtmlCharacters(
state.tokens[tokenIdx].content, state.tokens[tokenIdx].content,
@@ -25,7 +26,7 @@ module.exports = function sanitizePlugin (md, options) {
const inlineTokens = state.tokens[tokenIdx].children const inlineTokens = state.tokens[tokenIdx].children
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) { for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
if (inlineTokens[childIdx].type === 'html_inline') { if (inlineTokens[childIdx].type === 'html_inline') {
inlineTokens[childIdx].content = sanitizeHtml( inlineTokens[childIdx].content = sanitizeInline(
inlineTokens[childIdx].content, inlineTokens[childIdx].content,
options options
) )
@@ -35,3 +36,89 @@ module.exports = function sanitizePlugin (md, options) {
} }
}) })
} }
const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:=("|')(?:[^\3]+?)\3)?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i
const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/ig
function sanitizeInline (html, options) {
let match = tagRegex.exec(html)
if (!match) {
return ''
}
const { allowedTags, allowedAttributes, selfClosing, allowedSchemesAppliedToAttributes } = options
if (match[1] !== undefined) {
// opening tag
const tag = match[1].toLowerCase()
if (allowedTags.indexOf(tag) === -1) {
return ''
}
const attributes = match[2]
let attrs = ''
let name
let value
while ((match = attributesRegex.exec(attributes))) {
name = match[1].toLowerCase()
value = match[3]
if (allowedAttributes['*'].indexOf(name) !== -1 || (allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)) {
if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) {
if (naughtyHRef(value, options) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value, options))) {
continue
}
}
attrs += ` ${name}`
if (match[2]) {
attrs += `="${value}"`
}
}
}
if (selfClosing.indexOf(tag) === -1) {
return '<' + tag + attrs + '>'
} else {
return '<' + tag + attrs + ' />'
}
} else {
// closing tag
if (allowedTags.indexOf(match[4].toLowerCase()) !== -1) {
return html
} else {
return ''
}
}
}
function naughtyHRef (href, options) {
// href = href.replace(/[\x00-\x20]+/g, '')
href = href.replace(/<\!\-\-.*?\-\-\>/g, '')
const matches = href.match(/^([a-zA-Z]+)\:/)
if (!matches) {
if (href.match(/^[\/\\]{2}/)) {
return !options.allowProtocolRelative
}
// No scheme
return false
}
const scheme = matches[1].toLowerCase()
return options.allowedSchemes.indexOf(scheme) === -1
}
function naughtyIFrame (src, options) {
try {
const parsed = url.parse(src, false, true)
return options.allowedIframeHostnames.index(parsed.hostname) === -1
} catch (e) {
return true
}
}

View File

@@ -21,39 +21,13 @@ function createGutter (str, firstLineNumber) {
class Markdown { class Markdown {
constructor (options = {}) { constructor (options = {}) {
let config = ConfigManager.get() const config = ConfigManager.get()
const defaultOptions = { const defaultOptions = {
typographer: config.preview.smartQuotes, typographer: config.preview.smartQuotes,
linkify: true, linkify: true,
html: true, html: true,
xhtmlOut: true, xhtmlOut: true,
breaks: config.preview.breaks, breaks: config.preview.breaks,
highlight: function (str, lang) {
const delimiter = ':'
const langInfo = lang.split(delimiter)
const langType = langInfo[0]
const fileName = langInfo[1] || ''
const firstLineNumber = parseInt(langInfo[2], 10)
if (langType === 'flowchart') {
return `<pre class="flowchart">${str}</pre>`
}
if (langType === 'sequence') {
return `<pre class="sequence">${str}</pre>`
}
if (langType === 'chart') {
return `<pre class="chart">${str}</pre>`
}
if (langType === 'mermaid') {
return `<pre class="mermaid">${str}</pre>`
}
return '<pre class="code CodeMirror">' +
'<span class="filename">' + fileName + '</span>' +
createGutter(str, firstLineNumber) +
'<code class="' + langType + '">' +
str +
'</code></pre>'
},
sanitize: 'STRICT' sanitize: 'STRICT'
} }
@@ -106,7 +80,11 @@ class Markdown {
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'], 'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
'input': ['type', 'id', 'checked'] 'input': ['type', 'id', 'checked']
}, },
allowedIframeHostnames: ['www.youtube.com'] allowedIframeHostnames: ['www.youtube.com'],
selfClosing: [ 'img', 'br', 'hr', 'input' ],
allowedSchemes: [ 'http', 'https', 'ftp', 'mailto' ],
allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ],
allowProtocolRelative: true
}) })
} }
@@ -153,6 +131,39 @@ class Markdown {
this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error']}) this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error']})
this.md.use(require('./markdown-it-frontmatter')) this.md.use(require('./markdown-it-frontmatter'))
this.md.use(require('./markdown-it-fence'), {
chart: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="chart" data-height="${token.parameters.height}">${token.content}</div>
</pre>`
},
flowchart: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="flowchart" data-height="${token.parameters.height}">${token.content}</div>
</pre>`
},
mermaid: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="mermaid" data-height="${token.parameters.height}">${token.content}</div>
</pre>`
},
sequence: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="sequence" data-height="${token.parameters.height}">${token.content}</div>
</pre>`
}
}, token => {
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
${createGutter(token.content, token.firstLineNumber)}
<code class="${token.langType}">${token.content}</code>
</pre>`
})
const deflate = require('markdown-it-plantuml/lib/deflate') const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), '', { this.md.use(require('markdown-it-plantuml'), '', {
generateSource: function (umlCode) { generateSource: function (umlCode) {
@@ -265,9 +276,6 @@ class Markdown {
} }
// FIXME We should not depend on global variable. // FIXME We should not depend on global variable.
window.md = this.md window.md = this.md
this.updateConfig = () => {
config = ConfigManager.get()
}
} }
render (content) { render (content) {

View File

@@ -61,11 +61,14 @@ class MarkdownNoteDetail extends React.Component {
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT' const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
this.handleSwitchMode(reversedType) this.handleSwitchMode(reversedType)
}) })
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
ee.on('code:generate-toc', this.generateToc) ee.on('code:generate-toc', this.generateToc)
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) { const isNewNote = nextProps.note.key !== this.props.note.key
const hasDeletedTags = nextProps.note.tags.length < this.props.note.tags.length
if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) {
if (this.saveQueue != null) this.saveNow() if (this.saveQueue != null) this.saveNow()
this.setState({ this.setState({
note: Object.assign({}, nextProps.note) note: Object.assign({}, nextProps.note)
@@ -91,7 +94,7 @@ class MarkdownNoteDetail extends React.Component {
handleUpdateContent () { handleUpdateContent () {
const { note } = this.state const { note } = this.state
note.content = this.refs.content.value note.content = this.refs.content.value
note.title = markdown.strip(striptags(findNoteTitle(note.content))) note.title = markdown.strip(striptags(findNoteTitle(note.content, this.props.config.editor.enableFrontMatterTitle, this.props.config.editor.frontMatterTitleField)))
this.updateNote(note) this.updateNote(note)
} }
@@ -293,9 +296,33 @@ class MarkdownNoteDetail extends React.Component {
}) })
} }
handleDeleteNote () {
this.handleTrashButtonClick()
}
handleClearTodo () {
const { note } = this.state
const splitted = note.content.split('\n')
const clearTodoContent = splitted.map((line) => {
const trimmedLine = line.trim()
if (trimmedLine.match(/\[x\]/i)) {
return line.replace(/\[x\]/i, '[ ]')
} else {
return line
}
}).join('\n')
note.content = clearTodoContent
this.refs.content.setValue(note.content)
this.updateNote(note)
}
renderEditor () { renderEditor () {
const { config, ignorePreviewPointerEvents } = this.props const { config, ignorePreviewPointerEvents } = this.props
const { note } = this.state const { note } = this.state
if (this.state.editorType === 'EDITOR_PREVIEW') { if (this.state.editorType === 'EDITOR_PREVIEW') {
return <MarkdownEditor return <MarkdownEditor
ref='content' ref='content'
@@ -321,7 +348,7 @@ class MarkdownNoteDetail extends React.Component {
} }
render () { render () {
const { data, location } = this.props const { data, location, config } = this.props
const { note, editorType } = this.state const { note, editorType } = this.state
const storageKey = note.storage const storageKey = note.storage
const folderKey = note.folder const folderKey = note.folder
@@ -372,10 +399,12 @@ class MarkdownNoteDetail extends React.Component {
<TagSelect <TagSelect
ref='tags' ref='tags'
value={this.state.note.tags} value={this.state.note.tags}
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
showTagsAlphabetically={config.ui.showTagsAlphabetically}
data={data} data={data}
onChange={this.handleUpdateTag.bind(this)} onChange={this.handleUpdateTag.bind(this)}
/> />
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} /> <TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} /> <ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />

View File

@@ -112,7 +112,7 @@ class SnippetNoteDetail extends React.Component {
if (this.refs.tags) note.tags = this.refs.tags.value if (this.refs.tags) note.tags = this.refs.tags.value
note.description = this.refs.description.value note.description = this.refs.description.value
note.updatedAt = new Date() note.updatedAt = new Date()
note.title = findNoteTitle(note.description) note.title = findNoteTitle(note.description, false)
this.setState({ this.setState({
note note
@@ -354,12 +354,10 @@ class SnippetNoteDetail extends React.Component {
this.refs['code-' + this.state.snippetIndex].reload() this.refs['code-' + this.state.snippetIndex].reload()
if (this.visibleTabs.offsetWidth > this.allTabs.scrollWidth) { if (this.visibleTabs.offsetWidth > this.allTabs.scrollWidth) {
console.log('no need for arrows')
this.moveTabBarBy(0) this.moveTabBarBy(0)
} else { } else {
const lastTab = this.allTabs.lastChild const lastTab = this.allTabs.lastChild
if (lastTab.offsetLeft + lastTab.offsetWidth < this.visibleTabs.offsetWidth) { if (lastTab.offsetLeft + lastTab.offsetWidth < this.visibleTabs.offsetWidth) {
console.log('need to scroll')
const width = this.visibleTabs.offsetWidth const width = this.visibleTabs.offsetWidth
const newLeft = lastTab.offsetLeft + lastTab.offsetWidth - width const newLeft = lastTab.offsetLeft + lastTab.offsetWidth - width
this.moveTabBarBy(newLeft > 0 ? -newLeft : 0) this.moveTabBarBy(newLeft > 0 ? -newLeft : 0)
@@ -627,7 +625,6 @@ class SnippetNoteDetail extends React.Component {
} }
focusEditor () { focusEditor () {
console.log('code-' + this.state.snippetIndex)
this.refs['code-' + this.state.snippetIndex].focus() this.refs['code-' + this.state.snippetIndex].focus()
} }
@@ -759,6 +756,8 @@ class SnippetNoteDetail extends React.Component {
<TagSelect <TagSelect
ref='tags' ref='tags'
value={this.state.note.tags} value={this.state.note.tags}
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
showTagsAlphabetically={config.ui.showTagsAlphabetically}
data={data} data={data}
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
/> />

View File

@@ -179,10 +179,10 @@ class TagSelect extends React.Component {
} }
render () { render () {
const { value, className } = this.props const { value, className, showTagsAlphabetically } = this.props
const tagList = _.isArray(value) const tagList = _.isArray(value)
? value.map((tag) => { ? (showTagsAlphabetically ? _.sortBy(value) : value).map((tag) => {
return ( return (
<span styleName='tag' <span styleName='tag'
key={tag} key={tag}

View File

@@ -1,26 +1,26 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './ToggleModeButton.styl' import styles from './ToggleModeButton.styl'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
const ToggleModeButton = ({ const ToggleModeButton = ({
onClick, editorType onClick, editorType
}) => ( }) => (
<div styleName='control-toggleModeButton'> <div styleName='control-toggleModeButton'>
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => onClick('SPLIT')}> <div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => onClick('SPLIT')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} /> <img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} />
</div> </div>
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}> <div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} /> <img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
</div> </div>
<span styleName='tooltip'>{i18n.__('Toggle Mode')}</span> <span styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
</div> </div>
) )
ToggleModeButton.propTypes = { ToggleModeButton.propTypes = {
onClick: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,
editorType: PropTypes.string.Required editorType: PropTypes.string.Required
} }
export default CSSModules(ToggleModeButton, styles) export default CSSModules(ToggleModeButton, styles)

View File

@@ -80,7 +80,6 @@ class Main extends React.Component {
} }
}) })
.then(data => { .then(data => {
console.log(data)
store.dispatch({ store.dispatch({
type: 'ADD_STORAGE', type: 'ADD_STORAGE',
storage: data.storage, storage: data.storage,
@@ -297,7 +296,7 @@ class Main extends React.Component {
onMouseUp={e => this.handleMouseUp(e)} onMouseUp={e => this.handleMouseUp(e)}
> >
<SideNav <SideNav
{..._.pick(this.props, ['dispatch', 'data', 'config', 'location'])} {..._.pick(this.props, ['dispatch', 'data', 'config', 'params', 'location'])}
width={this.state.navWidth} width={this.state.navWidth}
/> />
{!config.isSideNavFolded && {!config.isSideNavFolded &&

View File

@@ -56,7 +56,6 @@ class NoteList extends React.Component {
super(props) super(props)
this.selectNextNoteHandler = () => { this.selectNextNoteHandler = () => {
console.log('fired next')
this.selectNextNote() this.selectNextNote()
} }
this.selectPriorNoteHandler = () => { this.selectPriorNoteHandler = () => {
@@ -616,7 +615,6 @@ class NoteList extends React.Component {
.catch((err) => { .catch((err) => {
console.error('Cannot Delete note: ' + err) console.error('Cannot Delete note: ' + err)
}) })
console.log('Notes were all deleted')
} else { } else {
if (!confirmDeleteNote(confirmDeletion, false)) return if (!confirmDeleteNote(confirmDeletion, false)) return
@@ -636,7 +634,6 @@ class NoteList extends React.Component {
}) })
}) })
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE') AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
console.log('Notes went to trash')
}) })
.catch((err) => { .catch((err) => {
console.error('Notes could not go to trash: ' + err) console.error('Notes could not go to trash: ' + err)
@@ -996,6 +993,7 @@ class NoteList extends React.Component {
folderName={this.getNoteFolder(note).name} folderName={this.getNoteFolder(note).name}
storageName={this.getNoteStorage(note).name} storageName={this.getNoteStorage(note).name}
viewType={viewType} viewType={viewType}
showTagsAlphabetically={config.ui.showTagsAlphabetically}
/> />
) )
} }

View File

@@ -18,6 +18,11 @@ import TagButton from './TagButton'
import {SortableContainer} from 'react-sortable-hoc' import {SortableContainer} from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import context from 'browser/lib/context' import context from 'browser/lib/context'
import { remote } from 'electron'
function matchActiveTags (tags, activeTags) {
return _.every(activeTags, v => tags.indexOf(v) >= 0)
}
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
@@ -30,6 +35,52 @@ class SideNav extends React.Component {
EventEmitter.off('side:preferences', this.handleMenuButtonClick) EventEmitter.off('side:preferences', this.handleMenuButtonClick)
} }
deleteTag (tag) {
const selectedButton = remote.dialog.showMessageBox(remote.getCurrentWindow(), {
ype: 'warning',
message: i18n.__('Confirm tag deletion'),
detail: i18n.__('This will permanently remove this tag.'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
})
if (selectedButton === 0) {
const { data, dispatch, location, params } = this.props
const notes = data.noteMap
.map(note => note)
.filter(note => note.tags.indexOf(tag) !== -1)
.map(note => {
note = Object.assign({}, note)
note.tags = note.tags.slice()
note.tags.splice(note.tags.indexOf(tag), 1)
return note
})
Promise
.all(notes.map(note => dataApi.updateNote(note.storage, note.key, note)))
.then(updatedNotes => {
updatedNotes.forEach(note => {
dispatch({
type: 'UPDATE_NOTE',
note
})
})
if (location.pathname.match('/tags')) {
const tags = params.tagname.split(' ')
const index = tags.indexOf(tag)
if (index !== -1) {
tags.splice(index, 1)
this.context.router.push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`)
}
}
})
}
}
handleMenuButtonClick (e) { handleMenuButtonClick (e) {
openModal(PreferencesModal) openModal(PreferencesModal)
} }
@@ -44,6 +95,17 @@ class SideNav extends React.Component {
router.push('/starred') router.push('/starred')
} }
handleTagContextMenu (e, tag) {
const menu = []
menu.push({
label: i18n.__('Delete Tag'),
click: this.deleteTag.bind(this, tag)
})
context.popup(menu)
}
handleToggleButtonClick (e) { handleToggleButtonClick (e) {
const { dispatch, config } = this.props const { dispatch, config } = this.props
@@ -144,12 +206,20 @@ class SideNav extends React.Component {
tagListComponent () { tagListComponent () {
const { data, location, config } = this.props const { data, location, config } = this.props
const relatedTags = this.getRelatedTags(this.getActiveTags(location.pathname), data.noteMap) const activeTags = this.getActiveTags(location.pathname)
const relatedTags = this.getRelatedTags(activeTags, data.noteMap)
let tagList = _.sortBy(data.tagNoteMap.map( let tagList = _.sortBy(data.tagNoteMap.map(
(tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) }) (tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
), ['name']).filter( ).filter(
tag => tag.size > 0 tag => tag.size > 0
) ), ['name'])
if (config.ui.enableLiveNoteCounts && activeTags.length !== 0) {
const notesTags = data.noteMap.map(note => note.tags)
tagList = tagList.map(tag => {
tag.size = notesTags.filter(tags => tags.includes(tag.name) && matchActiveTags(tags, activeTags)).length
return tag
})
}
if (config.sortTagsBy === 'COUNTER') { if (config.sortTagsBy === 'COUNTER') {
tagList = _.sortBy(tagList, item => (0 - item.size)) tagList = _.sortBy(tagList, item => (0 - item.size))
} }
@@ -165,6 +235,7 @@ class SideNav extends React.Component {
name={tag.name} name={tag.name}
handleClickTagListItem={this.handleClickTagListItem.bind(this)} handleClickTagListItem={this.handleClickTagListItem.bind(this)}
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)} handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
handleContextMenu={this.handleTagContextMenu.bind(this)}
isActive={this.getTagActive(location.pathname, tag.name)} isActive={this.getTagActive(location.pathname, tag.name)}
isRelated={tag.related} isRelated={tag.related}
key={tag.name} key={tag.name}
@@ -247,7 +318,6 @@ class SideNav extends React.Component {
.catch((err) => { .catch((err) => {
console.error('Cannot Delete note: ' + err) console.error('Cannot Delete note: ' + err)
}) })
console.log('Trash emptied')
} }
handleFilterButtonContextMenu (event) { handleFilterButtonContextMenu (event) {

View File

@@ -45,7 +45,6 @@ function initAwsMobileAnalytics () {
if (getSendEventCond()) return if (getSendEventCond()) return
AWS.config.credentials.get((err) => { AWS.config.credentials.get((err) => {
if (!err) { if (!err) {
console.log('Cognito Identity ID: ' + AWS.config.credentials.identityId)
recordDynamicCustomEvent('APP_STARTED') recordDynamicCustomEvent('APP_STARTED')
recordStaticCustomEvent() recordStaticCustomEvent()
} }
@@ -58,7 +57,7 @@ function recordDynamicCustomEvent (type, options = {}) {
mobileAnalyticsClient.recordEvent(type, options) mobileAnalyticsClient.recordEvent(type, options)
} catch (analyticsError) { } catch (analyticsError) {
if (analyticsError instanceof ReferenceError) { if (analyticsError instanceof ReferenceError) {
console.log(analyticsError.name + ': ' + analyticsError.message) console.error(analyticsError.name + ': ' + analyticsError.message)
} }
} }
} }
@@ -71,7 +70,7 @@ function recordStaticCustomEvent () {
}) })
} catch (analyticsError) { } catch (analyticsError) {
if (analyticsError instanceof ReferenceError) { if (analyticsError instanceof ReferenceError) {
console.log(analyticsError.name + ': ' + analyticsError.message) console.error(analyticsError.name + ': ' + analyticsError.message)
} }
} }
} }

View File

@@ -24,7 +24,8 @@ export const DEFAULT_CONFIG = {
amaEnabled: true, amaEnabled: true,
hotkey: { hotkey: {
toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E', toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
toggleMode: OSX ? 'Command + Option + M' : 'Ctrl + M' toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M',
deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace'
}, },
ui: { ui: {
language: 'en', language: 'en',
@@ -43,11 +44,14 @@ export const DEFAULT_CONFIG = {
enableRulers: false, enableRulers: false,
rulers: [80, 120], rulers: [80, 120],
displayLineNumbers: true, displayLineNumbers: true,
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK'
delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE'
scrollPastEnd: false, scrollPastEnd: false,
type: 'SPLIT', type: 'SPLIT', // 'SPLIT', 'EDITOR_PREVIEW'
fetchUrlTitle: true, fetchUrlTitle: true,
enableTableEditor: false enableTableEditor: false,
enableFrontMatterTitle: true,
frontMatterTitleField: 'title'
}, },
preview: { preview: {
fontSize: '14', fontSize: '14',
@@ -60,6 +64,7 @@ export const DEFAULT_CONFIG = {
latexBlockClose: '$$', latexBlockClose: '$$',
plantUMLServerAddress: 'http://www.plantuml.com/plantuml', plantUMLServerAddress: 'http://www.plantuml.com/plantuml',
scrollPastEnd: false, scrollPastEnd: false,
scrollSync: true,
smartQuotes: true, smartQuotes: true,
breaks: true, breaks: true,
smartArrows: false, smartArrows: false,

View File

@@ -529,7 +529,6 @@ function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
return modifiedLinkText return modifiedLinkText
}) })
} else { } else {
console.log('One if the parameters was null -> Do nothing..')
return Promise.resolve(linkText) return Promise.resolve(linkText)
} }
} }

View File

@@ -14,7 +14,6 @@ function renameStorage (key, name) {
cachedStorageList = JSON.parse(localStorage.getItem('storages')) cachedStorageList = JSON.parse(localStorage.getItem('storages'))
if (!_.isArray(cachedStorageList)) throw new Error('invalid storages') if (!_.isArray(cachedStorageList)) throw new Error('invalid storages')
} catch (err) { } catch (err) {
console.log('error got')
console.error(err) console.error(err)
return Promise.reject(err) return Promise.reject(err)
} }

View File

@@ -31,13 +31,9 @@ function resolveStorageData (storageCache) {
const version = parseInt(storage.version, 10) const version = parseInt(storage.version, 10)
if (version >= 1) { if (version >= 1) {
if (version > 1) {
console.log('The repository version is newer than one of current app.')
}
return Promise.resolve(storage) return Promise.resolve(storage)
} }
console.log('Transform Legacy storage', storage.path)
return migrateFromV6Storage(storage.path) return migrateFromV6Storage(storage.path)
.then(() => storage) .then(() => storage)
} }

View File

@@ -9,7 +9,7 @@ function resolveStorageNotes (storage) {
notePathList = sander.readdirSync(notesDirPath) notePathList = sander.readdirSync(notesDirPath)
} catch (err) { } catch (err) {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
console.log(notesDirPath, ' doesn\'t exist.') console.error(notesDirPath, ' doesn\'t exist.')
sander.mkdirSync(notesDirPath) sander.mkdirSync(notesDirPath)
} else { } else {
console.warn('Failed to find note dir', notesDirPath, err) console.warn('Failed to find note dir', notesDirPath, err)

View File

@@ -12,7 +12,6 @@ function toggleStorage (key, isOpen) {
cachedStorageList = JSON.parse(localStorage.getItem('storages')) cachedStorageList = JSON.parse(localStorage.getItem('storages'))
if (!_.isArray(cachedStorageList)) throw new Error('invalid storages') if (!_.isArray(cachedStorageList)) throw new Error('invalid storages')
} catch (err) { } catch (err) {
console.log('error got')
console.error(err) console.error(err)
return Promise.reject(err) return Promise.reject(err)
} }

View File

@@ -14,7 +14,6 @@ function once (name, listener) {
} }
function emit (name, ...args) { function emit (name, ...args) {
console.log(name)
remote.getCurrentWindow().webContents.send(name, ...args) remote.getCurrentWindow().webContents.send(name, ...args)
} }

View File

@@ -14,14 +14,13 @@ nodeIpc.connectTo(
path.join(app.getPath('userData'), 'boostnote.service'), path.join(app.getPath('userData'), 'boostnote.service'),
function () { function () {
nodeIpc.of.node.on('error', function (err) { nodeIpc.of.node.on('error', function (err) {
console.log(err) console.error(err)
}) })
nodeIpc.of.node.on('connect', function () { nodeIpc.of.node.on('connect', function () {
console.log('Connected successfully')
ipcRenderer.send('config-renew', {config: ConfigManager.get()}) ipcRenderer.send('config-renew', {config: ConfigManager.get()})
}) })
nodeIpc.of.node.on('disconnect', function () { nodeIpc.of.node.on('disconnect', function () {
console.log('disconnected') return
}) })
} }
) )

View File

@@ -3,5 +3,8 @@ import ee from 'browser/main/lib/eventEmitter'
module.exports = { module.exports = {
'toggleMode': () => { 'toggleMode': () => {
ee.emit('topbar:togglemodebutton') ee.emit('topbar:togglemodebutton')
},
'deleteNote': () => {
ee.emit('hotkey:deletenote')
} }
} }

View File

@@ -23,21 +23,29 @@ class Crowdfunding extends React.Component {
return ( return (
<div styleName='root'> <div styleName='root'>
<div styleName='header'>{i18n.__('Crowdfunding')}</div> <div styleName='header'>{i18n.__('Crowdfunding')}</div>
<p>{i18n.__('Dear Boostnote users,')}</p>
<br />
<p>{i18n.__('Thank you for using Boostnote!')}</p> <p>{i18n.__('Thank you for using Boostnote!')}</p>
<p>{i18n.__('Boostnote is used in about 200 different countries and regions by an awesome community of developers.')}</p>
<br /> <br />
<p>{i18n.__('To support our growing userbase, and satisfy community expectations,')}</p> <p>{i18n.__('We launched IssueHunt which is an issue-based crowdfunding / sourcing platform for open source projects.')}</p>
<p>{i18n.__('we would like to invest more time and resources in this project.')}</p> <p>{i18n.__('Anyone can put a bounty on not only a bug but also on OSS feature requests listed on IssueHunt. Collected funds will be distributed to project owners and contributors.')}</p>
<br /> <br />
<p>{i18n.__('If you use Boostnote and see its potential, help us out by supporting the project on OpenCollective!')}</p> <p>{i18n.__('### Sustainable Open Source Ecosystem')}</p>
<p>{i18n.__('We discussed about open-source ecosystem and IssueHunt concept with the Boostnote team repeatedly. We actually also discussed with Matz who father of Ruby.')}</p>
<p>{i18n.__('The original reason why we made IssueHunt was to reward our contributors of Boostnote project. Weve got tons of Github stars and hundred of contributors in two years.')}</p>
<p>{i18n.__('We thought that it will be nice if we can pay reward for our contributors.')}</p>
<br /> <br />
<p>{i18n.__('Thanks,')}</p> <p>{i18n.__('### We believe Meritocracy')}</p>
<p>{i18n.__('We think developers who has skill and did great things must be rewarded properly.')}</p>
<p>{i18n.__('OSS projects are used in everywhere on the internet, but no matter how they great, most of owners of those projects need to have another job to sustain their living.')}</p>
<p>{i18n.__('It sometimes looks like exploitation.')}</p>
<p>{i18n.__('Weve realized IssueHunt could enhance sustainability of open-source ecosystem.')}</p>
<br />
<p>{i18n.__('As same as issues of Boostnote are already funded on IssueHunt, your open-source projects can be also started funding from now.')}</p>
<br />
<p>{i18n.__('Thank you,')}</p>
<p>{i18n.__('The Boostnote Team')}</p> <p>{i18n.__('The Boostnote Team')}</p>
<br /> <br />
<button styleName='cf-link'> <button styleName='cf-link'>
<a href='https://opencollective.com/boostnoteio' onClick={(e) => this.handleLinkClick(e)}>{i18n.__('Support via OpenCollective')}</a> <a href='http://bit.ly/issuehunt-from-boostnote-app' onClick={(e) => this.handleLinkClick(e)}>{i18n.__('See IssueHunt')}</a>
</button> </button>
</div> </div>
) )

View File

@@ -28,10 +28,20 @@ class HotkeyTab extends React.Component {
}}) }})
} }
this.handleSettingError = (err) => { this.handleSettingError = (err) => {
this.setState({keymapAlert: { if (
type: 'error', this.state.config.hotkey.toggleMain === '' ||
message: err.message != null ? err.message : i18n.__('An error occurred!') this.state.config.hotkey.toggleMode === ''
}}) ) {
this.setState({keymapAlert: {
type: 'success',
message: i18n.__('Successfully applied!')
}})
} else {
this.setState({keymapAlert: {
type: 'error',
message: err.message != null ? err.message : i18n.__('An error occurred!')
}})
}
} }
this.oldHotkey = this.state.config.hotkey this.oldHotkey = this.state.config.hotkey
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone) ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
@@ -68,7 +78,8 @@ class HotkeyTab extends React.Component {
const { config } = this.state const { config } = this.state
config.hotkey = { config.hotkey = {
toggleMain: this.refs.toggleMain.value, toggleMain: this.refs.toggleMain.value,
toggleMode: this.refs.toggleMode.value toggleMode: this.refs.toggleMode.value,
deleteNote: this.refs.deleteNote.value
} }
this.setState({ this.setState({
config config
@@ -127,6 +138,17 @@ class HotkeyTab extends React.Component {
/> />
</div> </div>
</div> </div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Delete Note')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleHotkeyChange(e)}
ref='deleteNote'
value={config.hotkey.deleteNote}
type='text'
/>
</div>
</div>
<div styleName='group-control'> <div styleName='group-control'>
<button styleName='group-control-leftButton' <button styleName='group-control-leftButton'
onClick={(e) => this.handleHintToggleButtonClick(e)} onClick={(e) => this.handleHintToggleButtonClick(e)}

View File

@@ -84,7 +84,7 @@ class InfoTab extends React.Component {
>{i18n.__('GitHub')}</a> >{i18n.__('GitHub')}</a>
</li> </li>
<li> <li>
<a href='https://boostlog.io/@junp1234' <a href='https://medium.com/boostnote'
onClick={(e) => this.handleLinkClick(e)} onClick={(e) => this.handleLinkClick(e)}
>{i18n.__('Blog')}</a> >{i18n.__('Blog')}</a>
</li> </li>

View File

@@ -71,6 +71,9 @@ class UiTab extends React.Component {
showCopyNotification: this.refs.showCopyNotification.checked, showCopyNotification: this.refs.showCopyNotification.checked,
confirmDeletion: this.refs.confirmDeletion.checked, confirmDeletion: this.refs.confirmDeletion.checked,
showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked, showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked,
showTagsAlphabetically: this.refs.showTagsAlphabetically.checked,
saveTagsAlphabetically: this.refs.saveTagsAlphabetically.checked,
enableLiveNoteCounts: this.refs.enableLiveNoteCounts.checked,
disableDirectWrite: this.refs.uiD2w != null disableDirectWrite: this.refs.uiD2w != null
? this.refs.uiD2w.checked ? this.refs.uiD2w.checked
: false : false
@@ -89,7 +92,9 @@ class UiTab extends React.Component {
snippetDefaultLanguage: this.refs.editorSnippetDefaultLanguage.value, snippetDefaultLanguage: this.refs.editorSnippetDefaultLanguage.value,
scrollPastEnd: this.refs.scrollPastEnd.checked, scrollPastEnd: this.refs.scrollPastEnd.checked,
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked, fetchUrlTitle: this.refs.editorFetchUrlTitle.checked,
enableTableEditor: this.refs.enableTableEditor.checked enableTableEditor: this.refs.enableTableEditor.checked,
enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked,
frontMatterTitleField: this.refs.frontMatterTitleField.value
}, },
preview: { preview: {
fontSize: this.refs.previewFontSize.value, fontSize: this.refs.previewFontSize.value,
@@ -102,6 +107,7 @@ class UiTab extends React.Component {
latexBlockClose: this.refs.previewLatexBlockClose.value, latexBlockClose: this.refs.previewLatexBlockClose.value,
plantUMLServerAddress: this.refs.previewPlantUMLServerAddress.value, plantUMLServerAddress: this.refs.previewPlantUMLServerAddress.value,
scrollPastEnd: this.refs.previewScrollPastEnd.checked, scrollPastEnd: this.refs.previewScrollPastEnd.checked,
scrollSync: this.refs.previewScrollSync.checked,
smartQuotes: this.refs.previewSmartQuotes.checked, smartQuotes: this.refs.previewSmartQuotes.checked,
breaks: this.refs.previewBreaks.checked, breaks: this.refs.previewBreaks.checked,
smartArrows: this.refs.previewSmartArrows.checked, smartArrows: this.refs.previewSmartArrows.checked,
@@ -246,16 +252,6 @@ class UiTab extends React.Component {
{i18n.__('Show a confirmation dialog when deleting notes')} {i18n.__('Show a confirmation dialog when deleting notes')}
</label> </label>
</div> </div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.ui.showOnlyRelatedTags}
ref='showOnlyRelatedTags'
type='checkbox'
/>&nbsp;
{i18n.__('Show only related tags')}
</label>
</div>
{ {
global.process.platform === 'win32' global.process.platform === 'win32'
? <div styleName='group-checkBoxSection'> ? <div styleName='group-checkBoxSection'>
@@ -271,6 +267,52 @@ class UiTab extends React.Component {
</div> </div>
: null : null
} }
<div styleName='group-header2'>Tags</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.ui.saveTagsAlphabetically}
ref='saveTagsAlphabetically'
type='checkbox'
/>&nbsp;
{i18n.__('Save tags of a note in alphabetical order')}
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.ui.showTagsAlphabetically}
ref='showTagsAlphabetically'
type='checkbox'
/>&nbsp;
{i18n.__('Show tags of a note in alphabetical order')}
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.ui.showOnlyRelatedTags}
ref='showOnlyRelatedTags'
type='checkbox'
/>&nbsp;
{i18n.__('Show only related tags')}
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.ui.enableLiveNoteCounts}
ref='enableLiveNoteCounts'
type='checkbox'
/>&nbsp;
{i18n.__('Enable live count of notes')}
</label>
</div>
<div styleName='group-header2'>Editor</div> <div styleName='group-header2'>Editor</div>
<div styleName='group-section'> <div styleName='group-section'>
@@ -428,6 +470,31 @@ class UiTab extends React.Component {
</div> </div>
</div> </div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Front matter title field')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
ref='frontMatterTitleField'
value={config.editor.frontMatterTitleField}
onChange={(e) => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.enableFrontMatterTitle}
ref='enableFrontMatterTitle'
type='checkbox'
/>&nbsp;
{i18n.__('Extract title from front matter')}
</label>
</div>
<div styleName='group-checkBoxSection'> <div styleName='group-checkBoxSection'>
<label> <label>
<input onChange={(e) => this.handleUIChange(e)} <input onChange={(e) => this.handleUIChange(e)}
@@ -534,6 +601,16 @@ class UiTab extends React.Component {
{i18n.__('Allow preview to scroll past the last line')} {i18n.__('Allow preview to scroll past the last line')}
</label> </label>
</div> </div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.preview.scrollSync}
ref='previewScrollSync'
type='checkbox'
/>&nbsp;
{i18n.__('When scrolling, synchronize preview with editor')}
</label>
</div>
<div styleName='group-checkBoxSection'> <div styleName='group-checkBoxSection'>
<label> <label>
<input onChange={(e) => this.handleUIChange(e)} <input onChange={(e) => this.handleUIChange(e)}

View File

@@ -113,7 +113,6 @@ function data (state = defaultDataMap(), action) {
// If storage chanced, origin key must be discarded // If storage chanced, origin key must be discarded
if (originKey !== uniqueKey) { if (originKey !== uniqueKey) {
console.log('diffrent storage')
// From isStarred // From isStarred
if (originNote.isStarred) { if (originNote.isStarred) {
state.starredSet = new Set(state.starredSet) state.starredSet = new Set(state.starredSet)

View File

@@ -49,7 +49,7 @@ function startServer () {
} }
function startElectron () { function startElectron () {
spawn(electron, ['--hot', './index.js']) spawn(electron, ['--hot', './index.js'], { stdio: 'inherit' })
.on('close', () => { .on('close', () => {
server.close() server.close()
}) })

View File

@@ -52,7 +52,10 @@
if (className.indexOf('cm-url') !== -1) { if (className.indexOf('cm-url') !== -1) {
const match = /^\((.*)\)|\[(.*)\]|(.*)$/.exec(el.textContent) const match = /^\((.*)\)|\[(.*)\]|(.*)$/.exec(el.textContent)
return match[1] || match[2] || match[3] const url = match[1] || match[2] || match[3]
// `:storage` is the value of the variable `STORAGE_FOLDER_PLACEHOLDER` defined in `browser/main/lib/dataApi/attachmentManagement`
return /^:storage(?:\/|%5C)/.test(url) ? null : url
} }
return null return null

View File

@@ -34,11 +34,12 @@
.cm-s-rubyblue.CodeMirror .cm-table-row-even { background-color: rgb(41, 58, 73); } .cm-s-rubyblue.CodeMirror .cm-table-row-even { background-color: rgb(41, 58, 73); }
.cm-s-seti.CodeMirror .cm-table-row-even { background-color: rgb(40, 42, 43); } .cm-s-seti.CodeMirror .cm-table-row-even { background-color: rgb(40, 42, 43); }
.cm-s-shadowfox.CodeMirror .cm-table-row-even { background-color: rgb(56, 56, 59); } .cm-s-shadowfox.CodeMirror .cm-table-row-even { background-color: rgb(56, 56, 59); }
.cm-s-solarized.CodeMirror .cm-table-row-even { background-color: rgb(242, 242, 242); }
.cm-s-the-matrix.CodeMirror .cm-table-row-even { background-color: rgb(0, 26, 0); } .cm-s-the-matrix.CodeMirror .cm-table-row-even { background-color: rgb(0, 26, 0); }
.cm-s-tomorrow-night-bright.CodeMirror .cm-table-row-even { background-color: rgb(23, 23, 23); } .cm-s-tomorrow-night-bright.CodeMirror .cm-table-row-even { background-color: rgb(23, 23, 23); }
.cm-s-tomorrow-night-eighties.CodeMirror .cm-table-row-even { background-color: rgb(20, 20, 20); } .cm-s-tomorrow-night-eighties.CodeMirror .cm-table-row-even { background-color: rgb(20, 20, 20); }
.cm-s-twilight.CodeMirror .cm-table-row-even { background-color: rgb(43, 43, 43); } .cm-s-twilight.CodeMirror .cm-table-row-even { background-color: rgb(43, 43, 43); }
.cm-s-vibrant-ink.CodeMirror .cm-table-row-even { background-color: rgb(26, 26, 26); } .cm-s-vibrant-ink.CodeMirror .cm-table-row-even { background-color: rgb(26, 26, 26); }
.cm-s-xq-dark.CodeMirror .cm-table-row-even { background-color: rgb(34, 25, 53); } .cm-s-xq-dark.CodeMirror .cm-table-row-even { background-color: rgb(34, 25, 53); }
.cm-s-yeti.CodeMirror .cm-table-row-even { background-color: rgb(235, 232, 230); } .cm-s-yeti.CodeMirror .cm-table-row-even { background-color: rgb(235, 232, 230); }
.cm-s-solarized.cm-s-dark .cm-table-row-even { background-color: rgb(13, 54, 64); }
.cm-s-solarized.cm-s-light .cm-table-row-even { background-color: rgb(245, 240, 222); }

View File

@@ -54,6 +54,9 @@
stream.skipToEnd() stream.skipToEnd()
return null return null
},
blankLine(state) {
state.inTable = false
} }
} }

View File

@@ -289,9 +289,23 @@ module.exports = function (grunt) {
const Color = require('color') const Color = require('color')
const parseCSS = require('css').parse const parseCSS = require('css').parse
function generateRule (selector, bgColor, fgColor) {
if (bgColor.isLight()) {
bgColor = bgColor.mix(fgColor, 0.05)
} else {
bgColor = bgColor.mix(fgColor, 0.1)
}
if (selector && selector.length > 0) {
return `${selector} .cm-table-row-even { background-color: ${bgColor.rgb().string()}; }`
} else {
return `.cm-table-row-even { background-color: ${bgColor.rgb().string()}; }`
}
}
const root = path.join(__dirname, 'node_modules/codemirror/theme/') const root = path.join(__dirname, 'node_modules/codemirror/theme/')
const colors = fs.readdirSync(root).map(file => { const colors = fs.readdirSync(root).filter(file => file !== 'solarized.css').map(file => {
const css = parseCSS(fs.readFileSync(path.join(root, file), 'utf8')) const css = parseCSS(fs.readFileSync(path.join(root, file), 'utf8'))
const rules = css.stylesheet.rules.filter(rule => rule.selectors && /\b\.CodeMirror$/.test(rule.selectors[0])) const rules = css.stylesheet.rules.filter(rule => rule.selectors && /\b\.CodeMirror$/.test(rule.selectors[0]))
@@ -304,7 +318,7 @@ module.exports = function (grunt) {
bgColor = Color(declaration.value.split(' ')[0]) bgColor = Color(declaration.value.split(' ')[0])
} else if (declaration.property === 'color') { } else if (declaration.property === 'color') {
const value = /^(.*?)(?:\s*!important)?$/.exec(declaration.value)[1] const value = /^(.*?)(?:\s*!important)?$/.exec(declaration.value)[1]
let match = /^rgba\((.*?),\s*1\)$/.exec(value) const match = /^rgba\((.*?),\s*1\)$/.exec(value)
if (match) { if (match) {
fgColor = Color(`rgb(${match[1]})`) fgColor = Color(`rgb(${match[1]})`)
} else { } else {
@@ -313,19 +327,18 @@ module.exports = function (grunt) {
} }
}) })
if (bgColor.isLight()) { return generateRule(rules[0].selectors[0], bgColor, fgColor)
bgColor = bgColor.mix(fgColor, 0.05)
} else {
bgColor = bgColor.mix(fgColor, 0.1)
}
return `${rules[0].selectors[0]} .cm-table-row-even { background-color: ${bgColor.rgb().string()}; }`
} }
}).filter(value => !!value) }).filter(value => !!value)
const defaultBgColor = Color('white').mix(Color('black'), 0.05) // default
colors.unshift(generateRule(null, Color('white'), Color('black')))
// solarized dark
colors.push(generateRule('.cm-s-solarized.cm-s-dark', Color('#002b36'), Color('#839496')))
// solarized light
colors.push(generateRule('.cm-s-solarized.cm-s-light', Color('#fdf6e3'), Color('#657b83')))
fs.writeFileSync(path.join(__dirname, 'extra_scripts/codemirror/mode/bfm/bfm.css'), [`.cm-table-row-even { background-color: ${defaultBgColor.rgb().string()}; }`, ...colors].join('\n'), 'utf8') fs.writeFileSync(path.join(__dirname, 'extra_scripts/codemirror/mode/bfm/bfm.css'), colors.join('\n'), 'utf8')
}) })
grunt.registerTask('default', ['build']) grunt.registerTask('default', ['build'])

View File

@@ -59,7 +59,7 @@ updater.on('update-downloaded', (info) => {
}) })
updater.autoUpdater.on('error', (err) => { updater.autoUpdater.on('error', (err) => {
console.log(err) console.error(err)
}) })
ipc.on('update-app-confirm', function (event, msg) { ipc.on('update-app-confirm', function (event, msg) {

View File

@@ -7,13 +7,19 @@ const config = new Config()
const _ = require('lodash') const _ = require('lodash')
var showMenu = process.platform !== 'win32' var showMenu = process.platform !== 'win32'
const windowSize = config.get('windowsize') || { x: null, y: null, width: 1080, height: 720 } const windowSize = config.get('windowsize') || {
x: null,
y: null,
width: 1080,
height: 720
}
const mainWindow = new BrowserWindow({ const mainWindow = new BrowserWindow({
x: windowSize.x, x: windowSize.x,
y: windowSize.y, y: windowSize.y,
width: windowSize.width, width: windowSize.width,
height: windowSize.height, height: windowSize.height,
useContentSize: true,
minWidth: 500, minWidth: 500,
minHeight: 320, minHeight: 320,
autoHideMenuBar: showMenu, autoHideMenuBar: showMenu,

View File

@@ -145,6 +145,7 @@
"UserName": "Benutzername", "UserName": "Benutzername",
"Password": "Passwort", "Password": "Passwort",
"Russian": "Russisch", "Russian": "Russisch",
"Thai": "Thai (ภาษาไทย)",
"Command(⌘)": "Befehlstaste(⌘)", "Command(⌘)": "Befehlstaste(⌘)",
"Editor Rulers": "Editor Trennline", "Editor Rulers": "Editor Trennline",
"Enable": "Aktiviert", "Enable": "Aktiviert",

View File

@@ -135,6 +135,7 @@
"Hotkeys": "Hotkeys", "Hotkeys": "Hotkeys",
"Show/Hide Boostnote": "Show/Hide Boostnote", "Show/Hide Boostnote": "Show/Hide Boostnote",
"Toggle Editor Mode": "Toggle Editor Mode", "Toggle Editor Mode": "Toggle Editor Mode",
"Delete Note": "Delete Note",
"Restore": "Restore", "Restore": "Restore",
"Permanent Delete": "Permanent Delete", "Permanent Delete": "Permanent Delete",
"Confirm note deletion": "Confirm note deletion", "Confirm note deletion": "Confirm note deletion",
@@ -155,6 +156,7 @@
"Password": "Password", "Password": "Password",
"Russian": "Russian", "Russian": "Russian",
"Hungarian": "Hungarian", "Hungarian": "Hungarian",
"Thai": "Thai (ภาษาไทย)",
"Command(⌘)": "Command(⌘)", "Command(⌘)": "Command(⌘)",
"Add Storage": "Add Storage", "Add Storage": "Add Storage",
"Name": "Name", "Name": "Name",
@@ -176,6 +178,9 @@
"Allow dangerous html tags": "Allow dangerous html tags", "Allow dangerous html tags": "Allow dangerous html tags",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠", "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Save tags of a note in alphabetical order": "Save tags of a note in alphabetical order",
"Show tags of a note in alphabetical order": "Show tags of a note in alphabetical order",
"Enable live count of notes": "Enable live count of notes",
"Enable smart table editor": "Enable smart table editor", "Enable smart table editor": "Enable smart table editor",
"Snippet Default Language": "Snippet Default Language" "Snippet Default Language": "Snippet Default Language"
} }

View File

@@ -143,6 +143,7 @@
"Spanish": "Español", "Spanish": "Español",
"Unsaved Changes!": "¡Tienes que guardar!", "Unsaved Changes!": "¡Tienes que guardar!",
"Russian": "Ruso", "Russian": "Ruso",
"Thai": "Thai (ภาษาไทย)",
"Command(⌘)": "Comando(⌘)", "Command(⌘)": "Comando(⌘)",
"Editor Rulers": "Reglas del editor", "Editor Rulers": "Reglas del editor",
"Enable": "Activar", "Enable": "Activar",

View File

@@ -146,6 +146,7 @@
"UserName": "نام کاربری", "UserName": "نام کاربری",
"Password": "رمز عبور", "Password": "رمز عبور",
"Russian": "روسی", "Russian": "روسی",
"Thai": "Thai (ภาษาไทย)",
"Command(⌘)": "Command(⌘)", "Command(⌘)": "Command(⌘)",
"Editor Rulers": "Editor Rulers", "Editor Rulers": "Editor Rulers",
"Enable": "فعال", "Enable": "فعال",

View File

@@ -142,6 +142,7 @@
"Spanish": "Espagnol", "Spanish": "Espagnol",
"Unsaved Changes!": "Il faut sauvegarder !", "Unsaved Changes!": "Il faut sauvegarder !",
"Russian": "Russe", "Russian": "Russe",
"Thai": "Thai (ภาษาไทย)",
"Command(⌘)": "Command(⌘)", "Command(⌘)": "Command(⌘)",
"Editor Rulers": "Règles dans l'éditeur", "Editor Rulers": "Règles dans l'éditeur",
"Enable": "Activer", "Enable": "Activer",
@@ -153,6 +154,10 @@
"Allow dangerous html tags": "Accepter les tags html dangereux", "Allow dangerous html tags": "Accepter les tags html dangereux",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convertir des flèches textuelles en jolis signes. ⚠ Cela va interferérer avec les éventuels commentaires HTML dans votre Markdown.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convertir des flèches textuelles en jolis signes. ⚠ Cela va interferérer avec les éventuels commentaires HTML dans votre Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Vous avez collé un lien qui référence une pièce-jointe qui n'a pas pu être récupéré dans le dossier de stockage de la note. Coller des liens qui font référence à des pièces-jointes ne fonctionne que si la source et la destination et la même. Veuillez plutôt utiliser du Drag & Drop ! ⚠", "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Vous avez collé un lien qui référence une pièce-jointe qui n'a pas pu être récupéré dans le dossier de stockage de la note. Coller des liens qui font référence à des pièces-jointes ne fonctionne que si la source et la destination et la même. Veuillez plutôt utiliser du Drag & Drop ! ⚠",
"Save tags of a note in alphabetical order": "Sauvegarder les tags d'une note en ordre alphabétique",
"Show tags of a note in alphabetical order": "Afficher les tags d'une note par ordre alphabétique",
"Enable live count of notes": "Activer le comptage live des notes",
"Enable smart table editor": "Activer l'intelligent éditeur de tableaux", "Enable smart table editor": "Activer l'intelligent éditeur de tableaux",
"Snippet Default Language": "Langage par défaut d'un snippet" "Snippet Default Language": "Langage par défaut d'un snippet",
"Delete Note": "Supprimer la note"
} }

View File

@@ -155,6 +155,7 @@
"Password": "Jelszo", "Password": "Jelszo",
"Russian": "Russian", "Russian": "Russian",
"Hungarian": "Hungarian", "Hungarian": "Hungarian",
"Thai": "Thai (ภาษาไทย)",
"Command(⌘)": "Command(⌘)", "Command(⌘)": "Command(⌘)",
"Add Storage": "Tároló hozzáadása", "Add Storage": "Tároló hozzáadása",
"Name": "Név", "Name": "Név",

View File

@@ -146,6 +146,7 @@
"UserName": "UserName", "UserName": "UserName",
"Password": "Password", "Password": "Password",
"Russian": "Russo", "Russian": "Russo",
"Thai": "Thai (ภาษาไทย)",
"Command(⌘)": "Comando(⌘)", "Command(⌘)": "Comando(⌘)",
"Editor Rulers": "Regole dell'editor", "Editor Rulers": "Regole dell'editor",
"Enable": "Abilita", "Enable": "Abilita",

View File

@@ -155,6 +155,7 @@
"Password": "パスワード", "Password": "パスワード",
"Russian": "ロシア語", "Russian": "ロシア語",
"Hungarian": "ハンガリー語", "Hungarian": "ハンガリー語",
"Thai": "Thai (ภาษาไทย)",
"Command(⌘)": "コマンド(⌘)", "Command(⌘)": "コマンド(⌘)",
"Add Storage": "ストレージを追加", "Add Storage": "ストレージを追加",
"Name": "名前", "Name": "名前",

View File

@@ -143,6 +143,7 @@
"Spanish": "Spanish", "Spanish": "Spanish",
"Unsaved Changes!": "저장해주세요!", "Unsaved Changes!": "저장해주세요!",
"Russian": "Russian", "Russian": "Russian",
"Thai": "Thai (ภาษาไทย)",
"Command(⌘)": "Command(⌘)", "Command(⌘)": "Command(⌘)",
"Delete Folder": "폴더 삭제", "Delete Folder": "폴더 삭제",
"This will delete all notes in the folder and can not be undone.": "폴더의 모든 노트를 지우게 되고, 되돌릴 수 없습니다.", "This will delete all notes in the folder and can not be undone.": "폴더의 모든 노트를 지우게 되고, 되돌릴 수 없습니다.",

View File

@@ -143,6 +143,7 @@
"Spanish": "Spanish", "Spanish": "Spanish",
"Unsaved Changes!": "Unsaved Changes!", "Unsaved Changes!": "Unsaved Changes!",
"Russian": "Russian", "Russian": "Russian",
"Thai": "Thai (ภาษาไทย)",
"Editor Rulers": "Editor Rulers", "Editor Rulers": "Editor Rulers",
"Enable": "Enable", "Enable": "Enable",
"Disable": "Disable", "Disable": "Disable",

View File

@@ -149,6 +149,7 @@
"Spanish": "Hiszpański", "Spanish": "Hiszpański",
"Unsaved Changes!": "Musisz zapisać!", "Unsaved Changes!": "Musisz zapisać!",
"Russian": "Rosyjski", "Russian": "Rosyjski",
"Thai": "Thai (ภาษาไทย)",
"Editor Rulers": "Margines", "Editor Rulers": "Margines",
"Enable": "Włącz", "Enable": "Włącz",
"Disable": "Wyłącz", "Disable": "Wyłącz",

View File

@@ -143,6 +143,7 @@
"Spanish": "Espanhol", "Spanish": "Espanhol",
"Unsaved Changes!": "Você precisa salvar!", "Unsaved Changes!": "Você precisa salvar!",
"Russian": "Russo", "Russian": "Russo",
"Thai": "Thai (ภาษาไทย)",
"Editor Rulers": "Réguas do Editor", "Editor Rulers": "Réguas do Editor",
"Enable": "Habilitado", "Enable": "Habilitado",
"Disable": "Desabilitado", "Disable": "Desabilitado",

View File

@@ -144,6 +144,7 @@
"UserName": "Имя пользователя", "UserName": "Имя пользователя",
"Password": "Пароль", "Password": "Пароль",
"Russian": "Русский", "Russian": "Русский",
"Thai": "Thai (ภาษาไทย)",
"Editor Rulers": "Editor Rulers", "Editor Rulers": "Editor Rulers",
"Enable": "Enable", "Enable": "Enable",
"Disable": "Disable", "Disable": "Disable",

View File

@@ -142,6 +142,7 @@
"Spanish": "Spanish", "Spanish": "Spanish",
"Unsaved Changes!": "Unsaved Changes!", "Unsaved Changes!": "Unsaved Changes!",
"Russian": "Russian", "Russian": "Russian",
"Thai": "Thai (ภาษาไทย)",
"Editor Rulers": "Editor Rulers", "Editor Rulers": "Editor Rulers",
"Enable": "Enable", "Enable": "Enable",
"Disable": "Disable", "Disable": "Disable",

182
locales/th.json Normal file
View File

@@ -0,0 +1,182 @@
{
"Notes": "โน๊ต",
"Tags": "แท็ก",
"Preferences": "ตั้งค่า",
"Make a note": "สร้างโน๊ต",
"Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl(^)",
"to create a new note": "เพื่อสร้างโน๊ต",
"Toggle Mode": "Toggle Mode",
"Add tag...": "เพิ่มแท็ก...",
"Trash": "ถังขยะ",
"MODIFICATION DATE": "แก้ไขเมื่อ",
"Words": "คำ",
"Letters": "ตัวอักษร",
"STORAGE": "แหล่งจัดเก็บ",
"FOLDER": "โฟลเดอร์",
"CREATION DATE": "สร้างเมื่อ",
"NOTE LINK": "NOTE LINK",
".md": ".md",
".txt": ".txt",
".html": ".html",
"Print": "พิมพ์",
"Your preferences for Boostnote": "การตั้งค่าของคุณสำหรับ Boostnote",
"Help": "ช่วยเหลือ",
"Hide Help": "ซ่อนการช่วยเหลือ",
"Storages": "แหล่งจัดเก็บ",
"Add Storage Location": "เพิ่มแหล่งจัดเก็บ",
"Add Folder": "เพิ่มโฟลเดอร์",
"Select Folder": "เลือกโฟลเดอร์",
"Open Storage folder": "เปิดโฟลเดอร์แหล่งจัดเก็บ",
"Unlink": "ยกเลิกการลิงค์",
"Edit": "แก้ไข",
"Delete": "ลบ",
"Interface": "หน้าตาโปรแกรม",
"Interface Theme": "ธีมของโปรแกรม",
"Default": "ค่าเริ่มต้น",
"White": "โทนสว่าง",
"Solarized Dark": "Solarized Dark",
"Dark": "โทนมืด",
"Show a confirmation dialog when deleting notes": "แสดงหน้าต่างยืนยันเมื่อทำการลบโน๊ต",
"Disable Direct Write (It will be applied after restarting)": "ปิด Direct Write (It will be applied after restarting)",
"Show only related tags": "แสดงเฉพาะแท็กที่เกี่ยวข้อง",
"Editor Theme": "ธีมของ Editor",
"Editor Font Size": "ขนาดอักษรของ Editor",
"Editor Font Family": "แบบอักษรของ Editor",
"Editor Indent Style": "รูปแบบการย่อหน้าของ Editor",
"Spaces": "ช่องว่าง",
"Tabs": "แท็บ",
"Switch to Preview": "Switch to Preview",
"When Editor Blurred": "When Editor Blurred",
"When Editor Blurred, Edit On Double Click": "When Editor Blurred, Edit On Double Click",
"On Right Click": "On Right Click",
"Editor Keymap": "รูปแบบคีย์ลัดของ Editor",
"default": "ค่าเริ่มต้น",
"vim": "vim",
"emacs": "emacs",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ กรุณาปิดและเปิดโปรแกรมใหม่ หลังจากคุณเปลี่ยนคีย์ลัด",
"Show line numbers in the editor": "แสดงหมายเลขบรรทัด",
"Allow editor to scroll past the last line": "อนุญาตให้เลื่อน Scroll เลยบรรทัดสุดท้ายได้",
"Enable smart quotes": "เปิด Smart quotes",
"Bring in web page title when pasting URL on editor": "แสดงชื่อ Title ของเว็บไซต์เมื่อวางลิงค์ใน Editor",
"Preview": "พรีวิว",
"Preview Font Size": "ขนาดอักษร",
"Preview Font Family": "แบบอักษร",
"Code block Theme": "ธีมของ Code block",
"Allow preview to scroll past the last line": "อนุญาตให้เลื่อน Scroll เลยบรรทัดสุดท้ายได้",
"Show line numbers for preview code blocks": "แสดงหมายเลขบรรทัดใน Code block",
"LaTeX Inline Open Delimiter": "LaTeX Inline Open Delimiter",
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"PlantUML Server": "เซิฟเวอร์ของ PlantUML",
"Community": "ชุมชนผู้ใช้",
"Subscribe to Newsletter": "สมัครรับข่าวสาร",
"GitHub": "GitHub",
"Blog": "บล็อก",
"Facebook Group": "กลุ่ม Facebook",
"Twitter": "Twitter",
"About": "เกี่ยวกับ",
"Boostnote": "Boostnote",
"An open source note-taking app made for programmers just like you.": "เป็นแอพพลิเคชันจดบันทึก ที่ออกแบบมาเพื่อโปรแกรมเมอร์อย่างคุณ.",
"Website": "เว็บไซต์",
"Development": "การพัฒนา",
" : Development configurations for Boostnote.": " : การตั้งค่าต่างๆสำหรับการพัฒนา Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "สงวนลิขสิทธิ์ (C) 2017 - 2018 BoostIO",
"License: GPL v3": "License: GPL v3",
"Analytics": "การวิเคราะห์",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote จะเก็บข้อมูลแบบไม่ระบุตัวตนเพื่อนำไปใช้ในการปรับปรุงแอพพลิเคชันเท่านั้น, และจะไม่มีการเก็บข้อมูลส่วนตัวใดๆของคุณ เช่น ข้อมูลในโน๊ตของคุณอย่างเด็ดขาด.",
"You can see how it works on ": "คุณสามารถดูรายละเอียดเพิ่มเติมได้ที่ ",
"You can choose to enable or disable this option.": "คุณสามารถเลือกที่จะเปิดหรือปิดตัวเลือกนี้ได้.",
"Enable analytics to help improve Boostnote": "เปิดการวิเคราะห์ สำหรับการนำไปปรับปรุงพัฒนา Boostnote",
"Crowdfunding": "การระดมทุนสาธารณะ",
"Dear everyone,": "สวัสดีทุกคน,",
"Thank you for using Boostnote!": "ขอขอบคุณที่เลือกใช้ Boostnote!",
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "มีการใช้งาน Boostnote จากสังคมผู้ใช้ที่เป็น Developer มากกว่า 200 ประเทศทั่วโลกจากหลากหลายภูมิภาค.",
"To continue supporting this growth, and to satisfy community expectations,": "เพื่อให้เกิดการสนับสนุนให้เกิดการเติบโตอย่างต่อเนื่อง, และเพื่อพัฒนาให้ตรงตามความต้องการของชุมชนผู้ใช้,",
"we would like to invest more time and resources in this project.": "เราต้องใช้เวลา และการลงทุนด้านทรัพยากรสำหรับโครงการนี้.",
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "ถ้าคุณชอบและมองเห็นความเป็นไปได้ในอนาคต, คุณสามารถช่วยเหลือด้วยการสนับสนุนเราผ่าน OpenCollective!",
"Thanks,": "ขอขอบคุณ,",
"Boostnote maintainers": "กลุ่มผู้พัฒนา Boostnote",
"Support via OpenCollective": "สนับสนุนผ่าน OpenCollective",
"Language": "ภาษา",
"English": "English",
"German": "German",
"French": "French",
"Show \"Saved to Clipboard\" notification when copying": "แสดงการแจ้งเตือน \"บันทึกไปยังคลิปบอร์ด\" เมื่อทำการคัดลอก",
"All Notes": "โน๊ตทั้งหมด",
"Starred": "รายการโปรด",
"Are you sure to ": "คุณแน่ใจหรือไม่ที่จะ ",
" delete": " ลบ",
"this folder?": "โฟลเดอร์นี้?",
"Confirm": "ยืนยัน",
"Cancel": "ยกเลิก",
"Markdown Note": "โน๊ต Markdown",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "รูปแบบนี้ใช้สำหรับสร้างเอกสารทั่วไป. รองรับการเขียนเช็คลิสต์, แทรกโค้ด และการเขียนโดยใช้ Latex.",
"Snippet Note": "โน๊ต Snippet",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "รูปแบบนี้ใช้สำหรับสร้าง Code snippets. สามารถรวมหลาย Snippets เป็นโน๊ตเดียวกันได้.",
"Tab to switch format": "กด Tab เพื่อเปลี่ยนรูปแบบที่เลือก",
"Updated": "เรียงตามอัพเดท",
"Created": "เรียงตามเวลาที่สร้างโน๊ต",
"Alphabetically": "เรียงตามอักษร",
"Counter": "Counter",
"Default View": "มุมมองปกติ",
"Compressed View": "มุมมองหนาแน่น",
"Search": "ค้นหา",
"Blog Type": "ประเภทของบล็อก",
"Blog Address": "ที่อยู่ของบล็อก",
"Save": "บันทึก",
"Auth": "การยืนยันตัวตน",
"Authentication Method": "รูปแบบการยืนยันตัวตน",
"JWT": "JWT",
"USER": "USER",
"Token": "Token",
"Storage": "แหล่งจัดเก็บ",
"Hotkeys": "คีย์ลัด",
"Show/Hide Boostnote": "แสดง/ซ่อน Boostnote",
"Toggle editor mode": "เปิด/ปิด Editor mode",
"Restore": "กู้คืน",
"Permanent Delete": "ลบถาวร",
"Confirm note deletion": "ยืนยันการลบโน๊ต",
"This will permanently remove this note.": "โน๊ตของคุณจะถูกลบอย่างถาวร.",
"Successfully applied!": "สำเร็จ!",
"Albanian": "Albanian",
"Chinese (zh-CN)": "Chinese (zh-CN)",
"Chinese (zh-TW)": "Chinese (zh-TW)",
"Danish": "Danish",
"Japanese": "Japanese",
"Korean": "Korean",
"Norwegian": "Norwegian",
"Polish": "Polish",
"Portuguese": "Portuguese",
"Spanish": "Spanish",
"You have to save!": "คุณจำเป็นต้องบันทึก!",
"UserName": "UserName",
"Password": "Password",
"Russian": "Russian",
"Hungarian": "Hungarian",
"Thai": "Thai (ภาษาไทย)",
"Command(⌘)": "Command(⌘)",
"Add Storage": "เพิ่มแหล่งจัดเก็บ",
"Name": "ชื่อ",
"Type": "ชนิด",
"File System": "ระบบไฟล์",
"Setting up 3rd-party cloud storage integration:": "ดูวิธีการตั้งค่า หากต้องการใช้งานแบบลิงค์ไฟล์ร่วมกับผู้ให้บริการเก็บข้อมูลบนคลาวด์",
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
"Location": "ที่อยู่",
"Add": "เพิ่ม",
"Unlink Storage": "ยกเลิกการลิงค์ Storage",
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "การยกเลิกการลิงค์ จะเป็นการลบการลิงค์แหล่งจัดเก็บออกไปจาก Boostnote. แต่ไฟล์ข้อมูลจะไม่ถูกลบ, หากต้องการลบข้อมูล กรุณาลบโพลเดอร์ของข้อมูลในเครื่องของท่านด้วยตัวเอง.",
"Editor Rulers": "ไม้บรรทัด Editor",
"Enable": "เปิด",
"Disable": "ปิด",
"Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "อนุญาตเฉพาะ HTML tag ที่มีความปลอดภัย (แนะนำ)",
"Render newlines in Markdown paragraphs as <br>": "ใช้ <br> แทนอักขระขึ้นบรรทัดใหม่ในข้อความ Markdown",
"Allow styles": "อนุญาตการใช้ styles",
"Allow dangerous html tags": "อนุญาตให้ใช้ html tags ที่ไม่ปลอดภัย",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "แปลงลูกศรจากรูปแบบข้อความให้เป็นสัญลักษณ์. ⚠ สิ่งนี้จะเป็นการแทรกโดยใช้ HTML comment ลงไปใน Markdown ที่คุณเขียน.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ ไม่พบไฟล์แนบในโน๊ตนี้ จากลิงค์ที่คุณได้วาง. คุณสามารถวางลิงค์ที่อ้างอิงไปยังไฟล์แนบ เฉพาะกรณีที่ต้นทาง และปลายทางที่อ้างถึงนั้นอยู่ใน 'แหล่งจัดเก็บ เดียวกัน. กรุณาใช้การลากและวางเพื่อใส่ไฟล์แนบแทน! ⚠",
"Enable smart table editor": "เปิดการใช้ Smart table editor",
"Snippet Default Language": "ทำการ Snippet ภาษาที่เป็นค่าเริ่มต้น"
}

View File

@@ -1,7 +1,7 @@
{ {
"name": "boost", "name": "boost",
"productName": "Boostnote", "productName": "Boostnote",
"version": "0.11.9", "version": "0.11.10",
"main": "index.js", "main": "index.js",
"description": "Boostnote", "description": "Boostnote",
"license": "GPL-3.0", "license": "GPL-3.0",
@@ -15,7 +15,7 @@
"dev": "node dev-scripts/dev.js" "dev": "node dev-scripts/dev.js"
}, },
"config": { "config": {
"electron-version": "2.0.7" "electron-version": "3.0.3"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -56,7 +56,7 @@
"codemirror": "^5.40.2", "codemirror": "^5.40.2",
"codemirror-mode-elixir": "^1.1.1", "codemirror-mode-elixir": "^1.1.1",
"electron-config": "^1.0.0", "electron-config": "^1.0.0",
"electron-gh-releases": "^2.0.2", "electron-gh-releases": "^2.0.4",
"escape-string-regexp": "^1.0.5", "escape-string-regexp": "^1.0.5",
"file-uri-to-path": "^1.0.0", "file-uri-to-path": "^1.0.0",
"file-url": "^2.0.2", "file-url": "^2.0.2",
@@ -101,7 +101,9 @@
"sanitize-html": "^1.18.2", "sanitize-html": "^1.18.2",
"striptags": "^2.2.1", "striptags": "^2.2.1",
"unique-slug": "2.0.0", "unique-slug": "2.0.0",
"uuid": "^3.2.1" "uuid": "^3.2.1",
"turndown":"^4.0.2",
"turndown-plugin-gfm":"^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"ava": "^0.25.0", "ava": "^0.25.0",
@@ -123,7 +125,7 @@
"css-loader": "^0.19.0", "css-loader": "^0.19.0",
"devtron": "^1.1.0", "devtron": "^1.1.0",
"dom-storage": "^2.0.2", "dom-storage": "^2.0.2",
"electron": "2.0.7", "electron": "3.0.3",
"electron-packager": "^12.0.0", "electron-packager": "^12.0.0",
"eslint": "^3.13.1", "eslint": "^3.13.1",
"eslint-config-standard": "^6.2.1", "eslint-config-standard": "^6.2.1",

View File

@@ -30,7 +30,7 @@ Issues on Boostnote can be funded by anyone and the money will be distributed to
- [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/enQtMzkxOTk4ODkyNzc0LThkNmMzY2VlZjVhYTNiYjE5YjQyZGVjNTJlYTY1OGMyZTFjNGU5YTUyYjUzOWZhYTU4OTVlNDYyNDFjYWMzNDM) - [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzkxOTk4ODkyNzc0LThkNmMzY2VlZjVhYTNiYjE5YjQyZGVjNTJlYTY1OGMyZTFjNGU5YTUyYjUzOWZhYTU4OTVlNDYyNDFjYWMzNDM)
- [Blog](https://boostlog.io/tags/boostnote) - [Blog](https://medium.com/boostnote)
- [Reddit](https://www.reddit.com/r/Boostnote/) - [Reddit](https://www.reddit.com/r/Boostnote/)

View File

@@ -3,6 +3,7 @@
exports[`TagListItem renders correctly 1`] = ` exports[`TagListItem renders correctly 1`] = `
<div <div
className="tagList-itemContainer" className="tagList-itemContainer"
onContextMenu={[Function]}
> >
<div <div
className="tagList-itemNarrow" className="tagList-itemNarrow"

View File

@@ -50,11 +50,14 @@ const smartQuotes = 'This is a "QUOTE".'
const breaks = 'This is the first line.\nThis is the second line.' const breaks = 'This is the first line.\nThis is the second line.'
const shortcuts = '<kbd>Ctrl</kbd>\n\n[[Ctrl]]'
export default { export default {
basic, basic,
codeblock, codeblock,
katex, katex,
checkboxes, checkboxes,
smartQuotes, smartQuotes,
breaks breaks,
shortcuts
} }

View File

@@ -20,7 +20,47 @@ test('findNoteTitle#find should return a correct title (string)', t => {
testCases.forEach(testCase => { testCases.forEach(testCase => {
const [input, expected] = testCase const [input, expected] = testCase
t.is(findNoteTitle(input), expected, `Test for find() input: ${input} expected: ${expected}`) t.is(findNoteTitle(input, false), expected, `Test for find() input: ${input} expected: ${expected}`)
}) })
}) })
test('findNoteTitle#find should ignore front matter when enableFrontMatterTitle=false', t => {
// [input, expected]
const testCases = [
['---\nlayout: test\ntitle: hoge hoge hoge \n---\n# fuga', '# fuga'],
['---\ntitle:hoge\n---\n# fuga', '# fuga'],
['title: fuga\n# hoge', '# hoge']
]
testCases.forEach(testCase => {
const [input, expected] = testCase
t.is(findNoteTitle(input, false), expected, `Test for find() input: ${input} expected: ${expected}`)
})
})
test('findNoteTitle#find should respect front matter when enableFrontMatterTitle=true', t => {
// [input, expected]
const testCases = [
['---\nlayout: test\ntitle: hoge hoge hoge \n---\n# fuga', 'hoge hoge hoge'],
['---\ntitle:hoge\n---\n# fuga', 'hoge'],
['title: fuga\n# hoge', '# hoge']
]
testCases.forEach(testCase => {
const [input, expected] = testCase
t.is(findNoteTitle(input, true), expected, `Test for find() input: ${input} expected: ${expected}`)
})
})
test('findNoteTitle#find should respect frontMatterTitleField when provided', t => {
// [input, expected]
const testCases = [
['---\ntitle: hoge\n---\n# fuga', '# fuga'],
['---\ncustom: hoge\n---\n# fuga', 'hoge']
]
testCases.forEach(testCase => {
const [input, expected] = testCase
t.is(findNoteTitle(input, true, 'custom'), expected, `Test for find() input: ${input} expected: ${expected}`)
})
})

View File

@@ -43,3 +43,8 @@ test('Markdown.render() should render line breaks correctly', t => {
const renderedNonBreaks = newmd.render(markdownFixtures.breaks) const renderedNonBreaks = newmd.render(markdownFixtures.breaks)
t.snapshot(renderedNonBreaks) t.snapshot(renderedNonBreaks)
}) })
test('Markdown.render() should render shortcuts correctly', t => {
const rendered = md.render(markdownFixtures.shortcuts)
t.snapshot(rendered)
})

View File

@@ -18,6 +18,14 @@ Generated by [AVA](https://ava.li).
This is the second line.</p>␊ This is the second line.</p>␊
` `
## Markdown.render() should render shortcuts correctly
> Snapshot 1
`<p data-line="0"><kbd>Ctrl</kbd></p>␊
<p data-line="2"><kbd>Ctrl</kbd></p>␊
`
## Markdown.render() should renders KaTeX correctly ## Markdown.render() should renders KaTeX correctly
> Snapshot 1 > Snapshot 1
@@ -39,9 +47,12 @@ Generated by [AVA](https://ava.li).
> Snapshot 1 > Snapshot 1
`<pre class="code CodeMirror"><span class="filename">filename.js</span><span class="lineNumber CodeMirror-gutters"><span class="CodeMirror-linenumber">2</span></span><code class="js">var project = 'boostnote'; `<pre class="code CodeMirror" data-line="1">
</code></pre>␊ <span class="filename">filename.js</span>␊
` <span class="lineNumber CodeMirror-gutters"><span class="CodeMirror-linenumber">2</span></span>␊
<code class="js">var project = 'boostnote';␊
</code>␊
</pre>`
## Markdown.render() should renders markdown correctly ## Markdown.render() should renders markdown correctly

166
yarn.lock
View File

@@ -93,6 +93,10 @@ abab@^1.0.3, abab@^1.0.4:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
abab@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"
abbrev@1: abbrev@1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
@@ -138,6 +142,10 @@ acorn@^5.0.0, acorn@^5.3.0, acorn@^5.5.0:
version "5.5.3" version "5.5.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9"
acorn@^5.5.3:
version "5.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8"
ajv-keywords@^1.0.0: ajv-keywords@^1.0.0:
version "1.5.1" version "1.5.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
@@ -2242,6 +2250,12 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
dependencies: dependencies:
cssom "0.3.x" cssom "0.3.x"
cssstyle@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.0.0.tgz#79b16d51ec5591faec60e688891f15d2a5705129"
dependencies:
cssom "0.3.x"
ctype@0.5.3: ctype@0.5.3:
version "0.5.3" version "0.5.3"
resolved "https://registry.yarnpkg.com/ctype/-/ctype-0.5.3.tgz#82c18c2461f74114ef16c135224ad0b9144ca12f" resolved "https://registry.yarnpkg.com/ctype/-/ctype-0.5.3.tgz#82c18c2461f74114ef16c135224ad0b9144ca12f"
@@ -2757,7 +2771,7 @@ domelementtype@~1.1.1:
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
domexception@^1.0.0: domexception@^1.0.0, domexception@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
dependencies: dependencies:
@@ -2812,20 +2826,6 @@ electron-config@^1.0.0:
dependencies: dependencies:
conf "^1.0.0" conf "^1.0.0"
electron-download@^3.0.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-3.3.0.tgz#2cfd54d6966c019c4d49ad65fbe65cc9cdef68c8"
dependencies:
debug "^2.2.0"
fs-extra "^0.30.0"
home-path "^1.0.1"
minimist "^1.2.0"
nugget "^2.0.0"
path-exists "^2.1.0"
rc "^1.1.2"
semver "^5.3.0"
sumchecker "^1.2.0"
electron-download@^4.0.0: electron-download@^4.0.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.0.tgz#bf932c746f2f87ffcc09d1dd472f2ff6b9187845" resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.0.tgz#bf932c746f2f87ffcc09d1dd472f2ff6b9187845"
@@ -2840,7 +2840,21 @@ electron-download@^4.0.0:
semver "^5.3.0" semver "^5.3.0"
sumchecker "^2.0.1" sumchecker "^2.0.1"
electron-gh-releases@^2.0.2: electron-download@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8"
dependencies:
debug "^3.0.0"
env-paths "^1.0.0"
fs-extra "^4.0.1"
minimist "^1.2.0"
nugget "^2.0.1"
path-exists "^3.0.0"
rc "^1.2.1"
semver "^5.4.1"
sumchecker "^2.0.2"
electron-gh-releases@^2.0.4:
version "2.0.4" version "2.0.4"
resolved "https://registry.yarnpkg.com/electron-gh-releases/-/electron-gh-releases-2.0.4.tgz#198c07a0970fb8e80fcc67bd0b4198a010923dc3" resolved "https://registry.yarnpkg.com/electron-gh-releases/-/electron-gh-releases-2.0.4.tgz#198c07a0970fb8e80fcc67bd0b4198a010923dc3"
dependencies: dependencies:
@@ -2924,12 +2938,12 @@ electron-winstaller@^2.2.0:
lodash.template "^4.2.2" lodash.template "^4.2.2"
temp "^0.8.3" temp "^0.8.3"
electron@2.0.7: electron@3.0.3:
version "2.0.7" version "3.0.3"
resolved "https://registry.yarnpkg.com/electron/-/electron-2.0.7.tgz#f7ce410433298e319032ce31f0e6ffd709ff052c" resolved "https://registry.yarnpkg.com/electron/-/electron-3.0.3.tgz#2857ed8d37c1b46e0a75a72684800252255f3243"
dependencies: dependencies:
"@types/node" "^8.0.24" "@types/node" "^8.0.24"
electron-download "^3.0.1" electron-download "^4.1.0"
extract-zip "^1.0.3" extract-zip "^1.0.3"
emojis-list@^2.0.0: emojis-list@^2.0.0:
@@ -3044,10 +3058,6 @@ es6-promise@^3.1.2:
version "3.3.1" version "3.3.1"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613"
es6-promise@^4.0.5:
version "4.2.4"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29"
es6-set@~0.1.5: es6-set@~0.1.5:
version "0.1.5" version "0.1.5"
resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1"
@@ -3093,6 +3103,17 @@ escodegen@^1.6.1, escodegen@^1.9.0:
optionalDependencies: optionalDependencies:
source-map "~0.6.1" source-map "~0.6.1"
escodegen@^1.9.1:
version "1.11.0"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589"
dependencies:
esprima "^3.1.3"
estraverse "^4.2.0"
esutils "^2.0.2"
optionator "^0.8.1"
optionalDependencies:
source-map "~0.6.1"
escope@^3.6.0: escope@^3.6.0:
version "3.6.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3"
@@ -3757,16 +3778,6 @@ fs-extra@0.26.7, fs-extra@^0.26.0, fs-extra@^0.26.7:
path-is-absolute "^1.0.0" path-is-absolute "^1.0.0"
rimraf "^2.2.8" rimraf "^2.2.8"
fs-extra@^0.30.0:
version "0.30.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0"
dependencies:
graceful-fs "^4.1.2"
jsonfile "^2.1.0"
klaw "^1.0.0"
path-is-absolute "^1.0.0"
rimraf "^2.2.8"
fs-extra@^1.0.0: fs-extra@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950"
@@ -3782,7 +3793,7 @@ fs-extra@^2.0.0:
graceful-fs "^4.1.2" graceful-fs "^4.1.2"
jsonfile "^2.1.0" jsonfile "^2.1.0"
fs-extra@^4.0.0: fs-extra@^4.0.0, fs-extra@^4.0.1:
version "4.0.3" version "4.0.3"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
dependencies: dependencies:
@@ -4325,10 +4336,6 @@ home-or-tmp@^2.0.0:
os-homedir "^1.0.0" os-homedir "^1.0.0"
os-tmpdir "^1.0.1" os-tmpdir "^1.0.1"
home-path@^1.0.1:
version "1.0.6"
resolved "https://registry.yarnpkg.com/home-path/-/home-path-1.0.6.tgz#d549dc2465388a7f8667242c5b31588d29af29fc"
hooker@~0.2.3: hooker@~0.2.3:
version "0.2.3" version "0.2.3"
resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959" resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959"
@@ -5406,6 +5413,37 @@ jsdom@^11.5.1:
ws "^4.0.0" ws "^4.0.0"
xml-name-validator "^3.0.0" xml-name-validator "^3.0.0"
jsdom@^11.9.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8"
dependencies:
abab "^2.0.0"
acorn "^5.5.3"
acorn-globals "^4.1.0"
array-equal "^1.0.0"
cssom ">= 0.3.2 < 0.4.0"
cssstyle "^1.0.0"
data-urls "^1.0.0"
domexception "^1.0.1"
escodegen "^1.9.1"
html-encoding-sniffer "^1.0.2"
left-pad "^1.3.0"
nwsapi "^2.0.7"
parse5 "4.0.0"
pn "^1.1.0"
request "^2.87.0"
request-promise-native "^1.0.5"
sax "^1.2.4"
symbol-tree "^3.2.2"
tough-cookie "^2.3.4"
w3c-hr-time "^1.0.1"
webidl-conversions "^4.0.2"
whatwg-encoding "^1.0.3"
whatwg-mimetype "^2.1.0"
whatwg-url "^6.4.1"
ws "^5.2.0"
xml-name-validator "^3.0.0"
jsdom@^9.4.2: jsdom@^9.4.2:
version "9.12.0" version "9.12.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4"
@@ -5571,7 +5609,7 @@ lcid@^1.0.0:
dependencies: dependencies:
invert-kv "^1.0.0" invert-kv "^1.0.0"
left-pad@^1.2.0: left-pad@^1.2.0, left-pad@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
@@ -6424,7 +6462,7 @@ npmlog@^4.0.2:
gauge "~2.7.3" gauge "~2.7.3"
set-blocking "~2.0.0" set-blocking "~2.0.0"
nugget@^2.0.0: nugget@^2.0.0, nugget@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0" resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0"
dependencies: dependencies:
@@ -6452,6 +6490,10 @@ nwsapi@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.0.tgz#7c8faf4ad501e1d17a651ebc5547f966b547c5c7" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.0.tgz#7c8faf4ad501e1d17a651ebc5547f966b547c5c7"
nwsapi@^2.0.7:
version "2.0.8"
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.8.tgz#e3603579b7e162b3dbedae4fb24e46f771d8fa24"
oauth-sign@~0.6.0: oauth-sign@~0.6.0:
version "0.6.0" version "0.6.0"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.6.0.tgz#7dbeae44f6ca454e1f168451d630746735813ce3" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.6.0.tgz#7dbeae44f6ca454e1f168451d630746735813ce3"
@@ -6722,7 +6764,7 @@ path-browserify@0.0.0:
version "0.0.0" version "0.0.0"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
path-exists@^2.0.0, path-exists@^2.1.0: path-exists@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
dependencies: dependencies:
@@ -7203,6 +7245,10 @@ pseudomap@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
psl@^1.1.24:
version "1.1.29"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67"
punycode@1.3.2: punycode@1.3.2:
version "1.3.2" version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
@@ -7289,7 +7335,7 @@ raw-body@2.3.2:
iconv-lite "0.4.19" iconv-lite "0.4.19"
unpipe "1.0.0" unpipe "1.0.0"
rc@^1.0.1, rc@^1.1.2, rc@^1.1.6, rc@^1.1.7: rc@^1.0.1, rc@^1.1.2, rc@^1.1.6, rc@^1.1.7, rc@^1.2.1:
version "1.2.8" version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
dependencies: dependencies:
@@ -7714,7 +7760,7 @@ request@2.55.0:
tough-cookie ">=0.12.0" tough-cookie ">=0.12.0"
tunnel-agent "~0.4.0" tunnel-agent "~0.4.0"
request@^2.45.0, request@^2.79.0, request@^2.83.0: request@^2.45.0, request@^2.79.0, request@^2.83.0, request@^2.87.0:
version "2.87.0" version "2.87.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e"
dependencies: dependencies:
@@ -8520,14 +8566,7 @@ stylus@^0.52.4:
sax "0.5.x" sax "0.5.x"
source-map "0.1.x" source-map "0.1.x"
sumchecker@^1.2.0: sumchecker@^2.0.1, sumchecker@^2.0.2:
version "1.3.1"
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-1.3.1.tgz#79bb3b4456dd04f18ebdbc0d703a1d1daec5105d"
dependencies:
debug "^2.2.0"
es6-promise "^4.0.5"
sumchecker@^2.0.1:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e"
dependencies: dependencies:
@@ -8753,6 +8792,13 @@ tough-cookie@>=0.12.0, tough-cookie@>=2.3.3, tough-cookie@^2.3.2, tough-cookie@^
dependencies: dependencies:
punycode "^1.4.1" punycode "^1.4.1"
tough-cookie@^2.3.4:
version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
dependencies:
psl "^1.1.24"
punycode "^1.4.1"
tr46@^1.0.1: tr46@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
@@ -8809,6 +8855,16 @@ tunnel-agent@~0.4.0:
version "0.4.3" version "0.4.3"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
turndown-plugin-gfm@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/turndown-plugin-gfm/-/turndown-plugin-gfm-1.0.2.tgz#6f8678a361f35220b2bdf5619e6049add75bf1c7"
turndown@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/turndown/-/turndown-4.0.2.tgz#c3ddb8ba32a3665723599be2f4e7860adb6042ae"
dependencies:
jsdom "^11.9.0"
tweetnacl@^0.14.3, tweetnacl@~0.14.0: tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5" version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
@@ -9346,6 +9402,12 @@ ws@^4.0.0:
async-limiter "~1.0.0" async-limiter "~1.0.0"
safe-buffer "~5.1.0" safe-buffer "~5.1.0"
ws@^5.2.0:
version "5.2.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f"
dependencies:
async-limiter "~1.0.0"
xdg-basedir@^3.0.0: xdg-basedir@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"