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

Merged Master into feature branch and fixed conflicts

This commit is contained in:
nathan-castlehow
2019-08-01 20:12:58 +08:00
75 changed files with 477 additions and 224 deletions

View File

@@ -54,6 +54,7 @@ export default class CodeEditor extends React.Component {
this.focusHandler = () => { this.focusHandler = () => {
ipcRenderer.send('editor:focused', true) ipcRenderer.send('editor:focused', true)
} }
const debouncedDeletionOfAttachments = _.debounce(attachmentManagement.deleteAttachmentsNotPresentInNote, 30000)
this.blurHandler = (editor, e) => { this.blurHandler = (editor, e) => {
ipcRenderer.send('editor:focused', false) ipcRenderer.send('editor:focused', false)
if (e == null) return null if (e == null) return null
@@ -65,16 +66,11 @@ export default class CodeEditor extends React.Component {
el = el.parentNode el = el.parentNode
} }
this.props.onBlur != null && this.props.onBlur(e) this.props.onBlur != null && this.props.onBlur(e)
const { const {
storageKey, storageKey,
noteKey noteKey
} = this.props } = this.props
attachmentManagement.deleteAttachmentsNotPresentInNote( debouncedDeletionOfAttachments(this.editor.getValue(), storageKey, noteKey)
this.editor.getValue(),
storageKey,
noteKey
)
} }
this.pasteHandler = (editor, e) => { this.pasteHandler = (editor, e) => {
e.preventDefault() e.preventDefault()
@@ -206,23 +202,11 @@ export default class CodeEditor extends React.Component {
'Cmd-T': function (cm) { 'Cmd-T': function (cm) {
// Do nothing // Do nothing
}, },
'Ctrl-/': function (cm) { [translateHotkey(hotkey.insertDate)]: function (cm) {
if (global.process.platform === 'darwin') { return }
const dateNow = new Date() const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleDateString()) cm.replaceSelection(dateNow.toLocaleDateString())
}, },
'Cmd-/': function (cm) { [translateHotkey(hotkey.insertDateTime)]: function (cm) {
if (global.process.platform !== 'darwin') { return }
const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleDateString())
},
'Shift-Ctrl-/': function (cm) {
if (global.process.platform === 'darwin') { return }
const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleString())
},
'Shift-Cmd-/': function (cm) {
if (global.process.platform !== 'darwin') { return }
const dateNow = new Date() const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleString()) cm.replaceSelection(dateNow.toLocaleString())
}, },
@@ -290,7 +274,7 @@ export default class CodeEditor extends React.Component {
value: this.props.value, value: this.props.value,
linesHighlighted: this.props.linesHighlighted, linesHighlighted: this.props.linesHighlighted,
lineNumbers: this.props.displayLineNumbers, lineNumbers: this.props.displayLineNumbers,
lineWrapping: true, lineWrapping: this.props.lineWrapping,
theme: this.props.theme, theme: this.props.theme,
indentUnit: this.props.indentSize, indentUnit: this.props.indentSize,
tabSize: this.props.indentSize, tabSize: this.props.indentSize,
@@ -590,6 +574,10 @@ export default class CodeEditor extends React.Component {
this.editor.setOption('lineNumbers', this.props.displayLineNumbers) this.editor.setOption('lineNumbers', this.props.displayLineNumbers)
} }
if (prevProps.lineWrapping !== this.props.lineWrapping) {
this.editor.setOption('lineWrapping', this.props.lineWrapping)
}
if (prevProps.scrollPastEnd !== this.props.scrollPastEnd) { if (prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd) this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
} }

View File

@@ -304,6 +304,7 @@ class MarkdownEditor extends React.Component {
enableRulers={config.editor.enableRulers} enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers} rulers={config.editor.rulers}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
lineWrapping
matchingPairs={config.editor.matchingPairs} matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples} matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs} explodingPairs={config.editor.explodingPairs}
@@ -341,6 +342,7 @@ class MarkdownEditor extends React.Component {
smartArrows={config.preview.smartArrows} smartArrows={config.preview.smartArrows}
breaks={config.preview.breaks} breaks={config.preview.breaks}
sanitize={config.preview.sanitize} sanitize={config.preview.sanitize}
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
ref='preview' ref='preview'
onContextMenu={(e) => this.handleContextMenu(e)} onContextMenu={(e) => this.handleContextMenu(e)}
onDoubleClick={(e) => this.handleDoubleClick(e)} onDoubleClick={(e) => this.handleDoubleClick(e)}

View File

@@ -41,6 +41,7 @@ const CSS_FILES = [
`${appPath}/node_modules/codemirror/lib/codemirror.css`, `${appPath}/node_modules/codemirror/lib/codemirror.css`,
`${appPath}/node_modules/react-image-carousel/lib/css/main.min.css` `${appPath}/node_modules/react-image-carousel/lib/css/main.min.css`
] ]
const win = global.process.platform === 'win32'
function buildStyle ( function buildStyle (
fontFamily, fontFamily,
@@ -247,7 +248,7 @@ export default class MarkdownPreview extends React.Component {
handleContextMenu (event) { handleContextMenu (event) {
const menu = buildMarkdownPreviewContextMenu(this, event) const menu = buildMarkdownPreviewContextMenu(this, event)
if (menu != null) { if (menu != null) {
setTimeout(() => menu.popup(remote.getCurrentWindow()), 30) menu.popup(remote.getCurrentWindow())
} }
} }
@@ -320,7 +321,11 @@ export default class MarkdownPreview extends React.Component {
customCSS customCSS
) )
let body = this.markdown.render(noteContent) let body = this.markdown.render(noteContent)
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] body = attachmentManagement.fixLocalURLS(
body,
this.props.storagePath
)
const files = [this.getCodeThemeLink(codeBlockTheme), ...CSS_FILES]
files.forEach(file => { files.forEach(file => {
if (global.process.platform === 'win32') { if (global.process.platform === 'win32') {
file = file.replace('file:///', '') file = file.replace('file:///', '')
@@ -555,6 +560,7 @@ export default class MarkdownPreview extends React.Component {
if ( if (
prevProps.smartQuotes !== this.props.smartQuotes || prevProps.smartQuotes !== this.props.smartQuotes ||
prevProps.sanitize !== this.props.sanitize || prevProps.sanitize !== this.props.sanitize ||
prevProps.mermaidHTMLLabel !== this.props.mermaidHTMLLabel ||
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 prevProps.lineThroughCheckbox !== this.props.lineThroughCheckbox
@@ -632,7 +638,7 @@ export default class MarkdownPreview extends React.Component {
this.getWindow().document.getElementById( this.getWindow().document.getElementById(
'codeTheme' 'codeTheme'
).href = this.GetCodeThemeLink(codeBlockTheme) ).href = this.getCodeThemeLink(codeBlockTheme)
this.getWindow().document.getElementById('style').innerHTML = buildStyle( this.getWindow().document.getElementById('style').innerHTML = buildStyle(
fontFamily, fontFamily,
fontSize, fontSize,
@@ -645,14 +651,12 @@ export default class MarkdownPreview extends React.Component {
) )
} }
GetCodeThemeLink (name) { getCodeThemeLink (name) {
const theme = consts.THEMES.find(theme => theme.name === name) const theme = consts.THEMES.find(theme => theme.name === name)
if (theme) { return theme != null
return `${appPath}/${theme.path}` ? theme.path
} else { : `${appPath}/node_modules/codemirror/theme/elegant.css`
return `${appPath}/node_modules/codemirror/theme/elegant.css`
}
} }
rewriteIframe () { rewriteIframe () {
@@ -678,7 +682,8 @@ export default class MarkdownPreview extends React.Component {
showCopyNotification, showCopyNotification,
storagePath, storagePath,
noteKey, noteKey,
sanitize sanitize,
mermaidHTMLLabel
} = this.props } = this.props
let { value, codeBlockTheme } = this.props let { value, codeBlockTheme } = this.props
@@ -820,7 +825,7 @@ export default class MarkdownPreview extends React.Component {
_.forEach( _.forEach(
this.refs.root.contentWindow.document.querySelectorAll('.mermaid'), this.refs.root.contentWindow.document.querySelectorAll('.mermaid'),
el => { el => {
mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme) mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme, mermaidHTMLLabel)
} }
) )
@@ -870,6 +875,12 @@ export default class MarkdownPreview extends React.Component {
this.setImgOnClickEventHelper(img, rect) this.setImgOnClickEventHelper(img, rect)
imgObserver.observe(parentEl, config) imgObserver.observe(parentEl, config)
} }
const aList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('a')
for (const a of aList) {
a.removeEventListener('click', this.linkClickHandler)
a.addEventListener('click', this.linkClickHandler)
}
} }
setImgOnClickEventHelper (img, rect) { setImgOnClickEventHelper (img, rect) {
@@ -998,11 +1009,11 @@ export default class MarkdownPreview extends React.Component {
if (!rawHref) return // not checked href because parser will create file://... string for [empty link]() if (!rawHref) return // not checked href because parser will create file://... string for [empty link]()
const regexNoteInternalLink = /.*[main.\w]*.html#/ const extractId = /(main.html)?#/
const regexNoteInternalLink = new RegExp(`${extractId.source}(.+)`)
if (regexNoteInternalLink.test(href)) { if (regexNoteInternalLink.test(linkHash)) {
const targetId = mdurl.encode(linkHash) const targetId = mdurl.encode(linkHash.replace(extractId, ''))
const targetElement = this.refs.root.contentWindow.document.querySelector( const targetElement = this.refs.root.contentWindow.document.getElementById(
targetId targetId
) )

View File

@@ -150,7 +150,6 @@ class MarkdownSplitEditor extends React.Component {
onMouseMove={e => this.handleMouseMove(e)} onMouseMove={e => this.handleMouseMove(e)}
onMouseUp={e => this.handleMouseUp(e)}> onMouseUp={e => this.handleMouseUp(e)}>
<CodeEditor <CodeEditor
styleName='codeEditor'
ref='code' ref='code'
width={this.state.codeEditorWidthInPercent + '%'} width={this.state.codeEditorWidthInPercent + '%'}
mode='Boost Flavored Markdown' mode='Boost Flavored Markdown'
@@ -160,6 +159,7 @@ class MarkdownSplitEditor extends React.Component {
fontFamily={config.editor.fontFamily} fontFamily={config.editor.fontFamily}
fontSize={editorFontSize} fontSize={editorFontSize}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
lineWrapping
matchingPairs={config.editor.matchingPairs} matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples} matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs} explodingPairs={config.editor.explodingPairs}
@@ -187,7 +187,6 @@ class MarkdownSplitEditor extends React.Component {
</div> </div>
<MarkdownPreview <MarkdownPreview
style={previewStyle} style={previewStyle}
styleName='preview'
theme={config.ui.theme} theme={config.ui.theme}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
fontSize={config.preview.fontSize} fontSize={config.preview.fontSize}
@@ -200,6 +199,7 @@ class MarkdownSplitEditor extends React.Component {
smartArrows={config.preview.smartArrows} smartArrows={config.preview.smartArrows}
breaks={config.preview.breaks} breaks={config.preview.breaks}
sanitize={config.preview.sanitize} sanitize={config.preview.sanitize}
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
ref='preview' ref='preview'
tabInde='0' tabInde='0'
value={value} value={value}

View File

@@ -8,9 +8,30 @@
top -2px top -2px
width 0 width 0
z-index 0 z-index 0
border-left 1px solid $ui-borderColor
.slider-hitbox .slider-hitbox
absolute top bottom left right absolute top bottom left right
width 7px width 7px
left -3px left -3px
z-index 10 z-index 10
cursor col-resize cursor col-resize
body[data-theme="dark"]
.root
.slider
border-left 1px solid $ui-dark-borderColor
body[data-theme="solarized-dark"]
.root
.slider
border-left 1px solid $ui-solarized-dark-borderColor
body[data-theme="monokai"]
.root
.slider
border-left 1px solid $ui-monokai-borderColor
body[data-theme="dracula"]
.root
.slider
border-left 1px solid $ui-dracula-borderColor

View File

@@ -8,7 +8,7 @@ const ModalEscButton = ({
}) => ( }) => (
<button styleName='escButton' onClick={handleEscButtonClick}> <button styleName='escButton' onClick={handleEscButtonClick}>
<div styleName='esc-mark'>×</div> <div styleName='esc-mark'>×</div>
<div styleName='esc-text'>esc</div> <div>esc</div>
</button> </button>
) )

View File

@@ -148,15 +148,14 @@ NoteItem.propTypes = {
tags: PropTypes.array, tags: PropTypes.array,
isStarred: PropTypes.bool.isRequired, isStarred: PropTypes.bool.isRequired,
isTrashed: PropTypes.bool.isRequired, isTrashed: PropTypes.bool.isRequired,
blog: { blog: PropTypes.shape({
blogLink: PropTypes.string, blogLink: PropTypes.string,
blogId: PropTypes.number blogId: PropTypes.number
} })
}), }),
handleNoteClick: PropTypes.func.isRequired, handleNoteClick: PropTypes.func.isRequired,
handleNoteContextMenu: PropTypes.func.isRequired, handleNoteContextMenu: PropTypes.func.isRequired,
handleDragStart: PropTypes.func.isRequired, handleDragStart: PropTypes.func.isRequired
handleDragEnd: PropTypes.func.isRequired
} }
export default CSSModules(NoteItem, styles) export default CSSModules(NoteItem, styles)

View File

@@ -74,7 +74,7 @@ SideNavFilter.propTypes = {
isStarredActive: PropTypes.bool.isRequired, isStarredActive: PropTypes.bool.isRequired,
isTrashedActive: PropTypes.bool.isRequired, isTrashedActive: PropTypes.bool.isRequired,
handleStarredButtonClick: PropTypes.func.isRequired, handleStarredButtonClick: PropTypes.func.isRequired,
handleTrashdButtonClick: PropTypes.func.isRequired handleTrashedButtonClick: PropTypes.func.isRequired
} }
export default CSSModules(SideNavFilter, styles) export default CSSModules(SideNavFilter, styles)

View File

@@ -114,7 +114,7 @@ class SnippetTab extends React.Component {
> >
{snippet.name.trim().length > 0 {snippet.name.trim().length > 0
? snippet.name ? snippet.name
: <span styleName='button-unnamed'> : <span>
{i18n.__('Unnamed')} {i18n.__('Unnamed')}
</span> </span>
} }

View File

@@ -25,10 +25,10 @@ const TodoProcess = ({
) )
TodoProcess.propTypes = { TodoProcess.propTypes = {
todoStatus: { todoStatus: PropTypes.exact({
total: PropTypes.number.isRequired, total: PropTypes.number.isRequired,
completed: PropTypes.number.isRequired completed: PropTypes.number.isRequired
} })
} }
export default CSSModules(TodoProcess, styles) export default CSSModules(TodoProcess, styles)

View File

@@ -19,7 +19,7 @@ function getId () {
return id return id
} }
function render (element, content, theme) { function render (element, content, theme, enableHTMLLabel) {
try { try {
const height = element.attributes.getNamedItem('data-height') const height = element.attributes.getNamedItem('data-height')
if (height && height.value !== 'undefined') { if (height && height.value !== 'undefined') {
@@ -29,7 +29,8 @@ function render (element, content, theme) {
mermaidAPI.initialize({ mermaidAPI.initialize({
theme: isDarkTheme ? 'dark' : 'default', theme: isDarkTheme ? 'dark' : 'default',
themeCSS: isDarkTheme ? darkThemeStyling : '', themeCSS: isDarkTheme ? darkThemeStyling : '',
useMaxWidth: false useMaxWidth: false,
flowchart: { htmlLabels: enableHTMLLabel }
}) })
mermaidAPI.render(getId(), content, (svgGraph) => { mermaidAPI.render(getId(), content, (svgGraph) => {
element.innerHTML = svgGraph element.innerHTML = svgGraph

View File

@@ -7,6 +7,7 @@ const CODEMIRROR_THEME_PATH = 'node_modules/codemirror/theme'
const CODEMIRROR_EXTRA_THEME_PATH = 'extra_scripts/codemirror/theme' const CODEMIRROR_EXTRA_THEME_PATH = 'extra_scripts/codemirror/theme'
const isProduction = process.env.NODE_ENV === 'production' const isProduction = process.env.NODE_ENV === 'production'
const paths = [ const paths = [
isProduction ? path.join(app.getAppPath(), CODEMIRROR_THEME_PATH) : path.resolve(CODEMIRROR_THEME_PATH), isProduction ? path.join(app.getAppPath(), CODEMIRROR_THEME_PATH) : path.resolve(CODEMIRROR_THEME_PATH),
isProduction ? path.join(app.getAppPath(), CODEMIRROR_EXTRA_THEME_PATH) : path.resolve(CODEMIRROR_EXTRA_THEME_PATH) isProduction ? path.join(app.getAppPath(), CODEMIRROR_EXTRA_THEME_PATH) : path.resolve(CODEMIRROR_EXTRA_THEME_PATH)
@@ -18,7 +19,7 @@ const themes = paths
return { return {
name, name,
path: path.join(directory.split(/\//g).slice(-3).join('/'), file), path: path.join(directory, file),
className: `cm-s-${name}` className: `cm-s-${name}`
} }
})) }))
@@ -27,17 +28,16 @@ const themes = paths
themes.splice(themes.findIndex(({ name }) => name === 'solarized'), 1, { themes.splice(themes.findIndex(({ name }) => name === 'solarized'), 1, {
name: 'solarized dark', name: 'solarized dark',
path: `${CODEMIRROR_THEME_PATH}/solarized.css`, path: path.join(paths[0], 'solarized.css'),
className: `cm-s-solarized cm-s-dark` className: `cm-s-solarized cm-s-dark`
}, { }, {
name: 'solarized light', name: 'solarized light',
path: `${CODEMIRROR_THEME_PATH}/solarized.css`, path: path.join(paths[0], 'solarized.css'),
className: `cm-s-solarized cm-s-light` className: `cm-s-solarized cm-s-light`
}) })
themes.splice(0, 0, { themes.splice(0, 0, {
name: 'default', name: 'default',
path: `${CODEMIRROR_THEME_PATH}/elegant.css`, path: path.join(paths[0], 'elegant.css'),
className: `cm-s-default` className: `cm-s-default`
}) })

View File

@@ -81,11 +81,7 @@ const buildMarkdownPreviewContextMenu = function (markdownPreview, event) {
// Default context menu inclusions // Default context menu inclusions
const template = [{ const template = [{
role: 'cut'
}, {
role: 'copy' role: 'copy'
}, {
role: 'paste'
}, { }, {
role: 'selectall' role: 'selectall'
}] }]

View File

@@ -1,5 +1,10 @@
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir' import 'codemirror-mode-elixir'
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']}) const stylusCodeInfo = CodeMirror.modeInfo.find(info => info.name === 'Stylus')
if (stylusCodeInfo == null) {
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
} else {
stylusCodeInfo.alias = ['styl']
}
CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']}) CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']})

View File

@@ -15,7 +15,7 @@ module.exports = function sanitizePlugin (md, options) {
options options
) )
} }
if (state.tokens[tokenIdx].type === '_fence') { if (state.tokens[tokenIdx].type.match(/.*_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,

View File

@@ -21,7 +21,7 @@ function uniqueSlug (slug, slugs, opts) {
} }
function linkify (token) { function linkify (token) {
token.content = mdlink(token.content, '#' + token.slug) token.content = mdlink(token.content, `#${decodeURI(token.slug)}`)
return token return token
} }

View File

@@ -2,6 +2,7 @@ import markdownit from 'markdown-it'
import sanitize from './markdown-it-sanitize-html' import sanitize from './markdown-it-sanitize-html'
import emoji from 'markdown-it-emoji' import emoji from 'markdown-it-emoji'
import math from '@rokt33r/markdown-it-math' import math from '@rokt33r/markdown-it-math'
import mdurl from 'mdurl'
import smartArrows from 'markdown-it-smartarrows' import smartArrows from 'markdown-it-smartarrows'
import _ from 'lodash' import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
@@ -150,9 +151,9 @@ class Markdown {
const content = token.content.split('\n').slice(0, -1).map(line => { const content = token.content.split('\n').slice(0, -1).map(line => {
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line) const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
if (match) { if (match) {
return match[1] return mdurl.encode(match[1])
} else { } else {
return line return mdurl.encode(line)
} }
}).join('\n') }).join('\n')
@@ -288,7 +289,9 @@ class Markdown {
case 'list_item_open': case 'list_item_open':
case 'paragraph_open': case 'paragraph_open':
case 'table_open': case 'table_open':
token.attrPush(['data-line', token.map[0]]) if (token.map) {
token.attrPush(['data-line', token.map[0]])
}
} }
}) })
const result = originalRender.call(this.md.renderer, tokens, options, env) const result = originalRender.call(this.md.renderer, tokens, options, env)

View File

@@ -1,17 +1,11 @@
import diacritics from 'diacritics-map'
function replaceDiacritics (str) {
return str.replace(/[À-ž]/g, function (ch) {
return diacritics[ch] || ch
})
}
module.exports = function slugify (title) { module.exports = function slugify (title) {
let slug = title.trim() const slug = encodeURI(
title.trim()
.replace(/^\s+/, '')
.replace(/\s+$/, '')
.replace(/\s+/g, '-')
.replace(/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`]/g, '')
)
slug = replaceDiacritics(slug) return slug
slug = slug.replace(/[^\w\s-]/g, '').replace(/\s+/g, '-')
return encodeURI(slug).replace(/\-+$/, '')
} }

View File

@@ -11,7 +11,7 @@ const FullscreenButton = ({
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B' const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
return ( return (
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}> <button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' /> <img src='../resources/icon/icon-full.svg' />
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span> <span lang={i18n.locale} styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
</button> </button>
) )

View File

@@ -60,7 +60,7 @@ class InfoPanel extends React.Component {
</div> </div>
<div> <div>
<input styleName='infoPanel-noteLink' ref='noteLink' value={noteLink} onClick={(e) => { e.target.select() }} /> <input styleName='infoPanel-noteLink' ref='noteLink' defaultValue={noteLink} onClick={(e) => { e.target.select() }} />
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'> <button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
<i className='fa fa-clipboard' /> <i className='fa fa-clipboard' />
</button> </button>

View File

@@ -67,9 +67,6 @@ class MarkdownNoteDetail extends React.Component {
}) })
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this)) ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
ee.on('code:generate-toc', this.generateToc) ee.on('code:generate-toc', this.generateToc)
// Focus content if using blur or double click
if (this.state.switchPreview === 'BLUR' || this.state.switchPreview === 'DBL_CLICK') this.focus()
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
@@ -84,6 +81,20 @@ class MarkdownNoteDetail extends React.Component {
if (this.refs.tags) this.refs.tags.reset() if (this.refs.tags) this.refs.tags.reset()
}) })
} }
// Focus content if using blur or double click
// --> Moved here from componentDidMount so a re-render during search won't set focus to the editor
const {switchPreview} = nextProps.config.editor
if (this.state.switchPreview !== switchPreview) {
this.setState({
switchPreview
})
if (switchPreview === 'BLUR' || switchPreview === 'DBL_CLICK') {
console.log('setting focus', switchPreview)
this.focus()
}
}
} }
componentWillUnmount () { componentWillUnmount () {
@@ -300,7 +311,7 @@ class MarkdownNoteDetail extends React.Component {
} }
getToggleLockButton () { getToggleLockButton () {
return this.state.isLocked ? '../resources/icon/icon-previewoff-on.svg' : '../resources/icon/icon-previewoff-off.svg' return this.state.isLocked ? '../resources/icon/icon-lock.svg' : '../resources/icon/icon-unlock.svg'
} }
handleDeleteKeyDown (e) { handleDeleteKeyDown (e) {
@@ -439,7 +450,7 @@ class MarkdownNoteDetail extends React.Component {
const detailTopBar = <div styleName='info'> const detailTopBar = <div styleName='info'>
<div styleName='info-left'> <div styleName='info-left'>
<div styleName='info-left-top'> <div>
<FolderSelect styleName='info-left-top-folderSelect' <FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder} value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder' ref='folder'
@@ -473,7 +484,7 @@ class MarkdownNoteDetail extends React.Component {
onFocus={(e) => this.handleFocus(e)} onFocus={(e) => this.handleFocus(e)}
onMouseDown={(e) => this.handleLockButtonMouseDown(e)} onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
> >
<img styleName='iconInfo' src={imgSrc} /> <img src={imgSrc} />
{this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>} {this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
</button> </button>

View File

@@ -10,7 +10,7 @@ const PermanentDeleteButton = ({
<button styleName='control-trashButton--in-trash' <button styleName='control-trashButton--in-trash'
onClick={(e) => onClick(e)} onClick={(e) => onClick(e)}
> >
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' /> <img src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>{i18n.__('Permanent Delete')}</span> <span styleName='tooltip'>{i18n.__('Permanent Delete')}</span>
</button> </button>
) )

View File

@@ -518,6 +518,19 @@ class SnippetNoteDetail extends React.Component {
]) ])
} }
handleWrapLineButtonClick (e) {
context.popup([
{
label: 'on',
click: (e) => this.handleWrapLineItemClick(e, true)
},
{
label: 'off',
click: (e) => this.handleWrapLineItemClick(e, false)
}
])
}
handleIndentSizeItemClick (e, indentSize) { handleIndentSizeItemClick (e, indentSize) {
const { config, dispatch } = this.props const { config, dispatch } = this.props
const editor = Object.assign({}, config.editor, { const editor = Object.assign({}, config.editor, {
@@ -550,6 +563,22 @@ class SnippetNoteDetail extends React.Component {
}) })
} }
handleWrapLineItemClick (e, lineWrapping) {
const { config, dispatch } = this.props
const editor = Object.assign({}, config.editor, {
lineWrapping
})
ConfigManager.set({
editor
})
dispatch({
type: 'SET_CONFIG',
config: {
editor
}
})
}
focus () { focus () {
this.refs.description.focus() this.refs.description.focus()
} }
@@ -720,6 +749,7 @@ class SnippetNoteDetail extends React.Component {
mode={snippet.mode || (autoDetect ? null : config.editor.snippetDefaultLanguage)} mode={snippet.mode || (autoDetect ? null : config.editor.snippetDefaultLanguage)}
value={snippet.content} value={snippet.content}
linesHighlighted={snippet.linesHighlighted} linesHighlighted={snippet.linesHighlighted}
lineWrapping={config.editor.lineWrapping}
theme={config.editor.theme} theme={config.editor.theme}
fontFamily={config.editor.fontFamily} fontFamily={config.editor.fontFamily}
fontSize={editorFontSize} fontSize={editorFontSize}
@@ -778,7 +808,7 @@ class SnippetNoteDetail extends React.Component {
const detailTopBar = <div styleName='info'> const detailTopBar = <div styleName='info'>
<div styleName='info-left'> <div styleName='info-left'>
<div styleName='info-left-top'> <div>
<FolderSelect styleName='info-left-top-folderSelect' <FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder} value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder' ref='folder'
@@ -899,6 +929,12 @@ class SnippetNoteDetail extends React.Component {
size: {config.editor.indentSize}&nbsp; size: {config.editor.indentSize}&nbsp;
<i className='fa fa-caret-down' /> <i className='fa fa-caret-down' />
</button> </button>
<button
onClick={(e) => this.handleWrapLineButtonClick(e)}
>
Wrap Line: {config.editor.lineWrapping ? 'on' : 'off'}&nbsp;
<i className='fa fa-caret-down' />
</button>
</div> </div>
<StatusBar <StatusBar

View File

@@ -8,11 +8,11 @@ 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' : undefined} onClick={() => onClick('SPLIT')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} /> <img 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' : undefined} onClick={() => onClick('EDITOR_PREVIEW')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} /> <img src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
</div> </div>
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Toggle Mode')}</span> <span lang={i18n.locale} styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
</div> </div>
@@ -20,7 +20,7 @@ const ToggleModeButton = ({
ToggleModeButton.propTypes = { ToggleModeButton.propTypes = {
onClick: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,
editorType: PropTypes.string.Required editorType: PropTypes.string.isRequired
} }
export default CSSModules(ToggleModeButton, styles) export default CSSModules(ToggleModeButton, styles)

View File

@@ -10,7 +10,7 @@ const TrashButton = ({
<button styleName='control-trashButton' <button styleName='control-trashButton'
onClick={(e) => onClick(e)} onClick={(e) => onClick(e)}
> >
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' /> <img src='../resources/icon/icon-trash.svg' />
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Trash')}</span> <span lang={i18n.locale} styleName='tooltip'>{i18n.__('Trash')}</span>
</button> </button>
) )

View File

@@ -1,8 +1,8 @@
/* eslint-disable no-undef */ /* eslint-disable no-undef */
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'development') {
// eslint-disable-next-line global-require
module.exports = require('./index.prod').default
} else {
// eslint-disable-next-line global-require // eslint-disable-next-line global-require
module.exports = require('./index.dev').default module.exports = require('./index.dev').default
} else {
// eslint-disable-next-line global-require
module.exports = require('./index.prod').default
} }

View File

@@ -90,7 +90,7 @@ class NewNoteButton extends React.Component {
<div styleName='control'> <div styleName='control'>
<button styleName='control-newNoteButton' <button styleName='control-newNoteButton'
onClick={this.handleNewNoteButtonClick}> onClick={this.handleNewNoteButtonClick}>
<img styleName='iconTag' src='../resources/icon/icon-newnote.svg' /> <img src='../resources/icon/icon-newnote.svg' />
<span styleName='control-newNoteButton-tooltip'> <span styleName='control-newNoteButton-tooltip'>
{i18n.__('Make a note')} {OSX ? '⌘' : i18n.__('Ctrl')} + N {i18n.__('Make a note')} {OSX ? '⌘' : i18n.__('Ctrl')} + N
</span> </span>

View File

@@ -1138,7 +1138,7 @@ class NoteList extends React.Component {
} }
onClick={(e) => this.handleListStyleButtonClick(e, 'DEFAULT')} onClick={(e) => this.handleListStyleButtonClick(e, 'DEFAULT')}
> >
<img styleName='iconTag' src='../resources/icon/icon-column.svg' /> <img src='../resources/icon/icon-column.svg' />
</button> </button>
<button title={i18n.__('Compressed View')} styleName={config.listStyle === 'SMALL' <button title={i18n.__('Compressed View')} styleName={config.listStyle === 'SMALL'
? 'control-button--active' ? 'control-button--active'
@@ -1146,7 +1146,7 @@ class NoteList extends React.Component {
} }
onClick={(e) => this.handleListStyleButtonClick(e, 'SMALL')} onClick={(e) => this.handleListStyleButtonClick(e, 'SMALL')}
> >
<img styleName='iconTag' src='../resources/icon/icon-column-list.svg' /> <img src='../resources/icon/icon-column-list.svg' />
</button> </button>
</div> </div>
</div> </div>

View File

@@ -8,7 +8,7 @@ const PreferenceButton = ({
onClick onClick
}) => ( }) => (
<button styleName='top-menu-preference' onClick={(e) => onClick(e)}> <button styleName='top-menu-preference' onClick={(e) => onClick(e)}>
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' /> <img src='../resources/icon/icon-setting.svg' />
<span styleName='tooltip'>{i18n.__('Preferences')}</span> <span styleName='tooltip'>{i18n.__('Preferences')}</span>
</button> </button>
) )

View File

@@ -362,14 +362,14 @@ class StorageItem extends React.Component {
<button styleName='header-addFolderButton' <button styleName='header-addFolderButton'
onClick={(e) => this.handleAddFolderButtonClick(e)} onClick={(e) => this.handleAddFolderButtonClick(e)}
> >
<img styleName='iconTag' src='../resources/icon/icon-plus.svg' /> <img src='../resources/icon/icon-plus.svg' />
</button> </button>
} }
<button styleName='header-info' <button styleName='header-info'
onClick={(e) => this.handleHeaderInfoClick(e)} onClick={(e) => this.handleHeaderInfoClick(e)}
> >
<span styleName='header-info-name'> <span>
{isFolded ? _.truncate(storage.name, {length: 1, omission: ''}) : storage.name} {isFolded ? _.truncate(storage.name, {length: 1, omission: ''}) : storage.name}
</span> </span>
{isFolded && {isFolded &&
@@ -380,7 +380,7 @@ class StorageItem extends React.Component {
</button> </button>
</div> </div>
{this.state.isOpen && {this.state.isOpen &&
<div styleName='folderList' > <div>
{folderList} {folderList}
</div> </div>
} }

View File

@@ -440,7 +440,7 @@ class SideNav extends React.Component {
const style = {} const style = {}
if (!isFolded) style.width = this.props.width if (!isFolded) style.width = this.props.width
const isTagActive = location.pathname.match(/tag/) const isTagActive = /tag/.test(location.pathname)
return ( return (
<div className='SideNav' <div className='SideNav'
styleName={isFolded ? 'root--folded' : 'root'} styleName={isFolded ? 'root--folded' : 'root'}

View File

@@ -7,8 +7,8 @@ import ee from 'browser/main/lib/eventEmitter'
import NewNoteButton from 'browser/main/NewNoteButton' import NewNoteButton from 'browser/main/NewNoteButton'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import debounce from 'lodash/debounce' import debounce from 'lodash/debounce'
import CInput from 'react-composition-input'
import { push } from 'connected-react-router' import { push } from 'connected-react-router'
import queryString from 'query-string'
class TopBar extends React.Component { class TopBar extends React.Component {
constructor (props) { constructor (props) {
@@ -17,21 +17,29 @@ class TopBar extends React.Component {
this.state = { this.state = {
search: '', search: '',
searchOptions: [], searchOptions: [],
isSearching: false, isSearching: false
isAlphabet: false,
isIME: false,
isConfirmTranslation: false
} }
const { dispatch } = this.props
this.focusSearchHandler = () => { this.focusSearchHandler = () => {
this.handleOnSearchFocus() this.handleOnSearchFocus()
} }
this.codeInitHandler = this.handleCodeInit.bind(this) this.codeInitHandler = this.handleCodeInit.bind(this)
this.updateKeyword = this.updateKeyword.bind(this) this.handleKeyDown = this.handleKeyDown.bind(this)
this.handleSearchFocus = this.handleSearchFocus.bind(this)
this.handleSearchBlur = this.handleSearchBlur.bind(this)
this.handleSearchChange = this.handleSearchChange.bind(this)
this.handleSearchClearButton = this.handleSearchClearButton.bind(this) this.handleSearchClearButton = this.handleSearchClearButton.bind(this)
this.updateKeyword = debounce(this.updateKeyword, 1000 / 60, { this.debouncedUpdateKeyword = debounce((keyword) => {
dispatch(push(`/searched/${encodeURIComponent(keyword)}`))
this.setState({
search: keyword
})
ee.emit('top:search', keyword)
}, 1000 / 60, {
maxWait: 1000 / 8 maxWait: 1000 / 8
}) })
} }
@@ -63,14 +71,14 @@ class TopBar extends React.Component {
this.refs.search.childNodes[0].blur this.refs.search.childNodes[0].blur
dispatch(push('/searched')) dispatch(push('/searched'))
e.preventDefault() e.preventDefault()
this.debouncedUpdateKeyword('')
} }
handleKeyDown (e) { handleKeyDown (e) {
// reset states // Re-apply search field on ENTER key
this.setState({ if (e.keyCode === 13) {
isAlphabet: false, this.debouncedUpdateKeyword(e.target.value)
isIME: false }
})
// Clear search on ESC // Clear search on ESC
if (e.keyCode === 27) { if (e.keyCode === 27) {
@@ -88,52 +96,11 @@ class TopBar extends React.Component {
ee.emit('list:prior') ee.emit('list:prior')
e.preventDefault() e.preventDefault()
} }
// When the key is an alphabet, del, enter or ctr
if (e.keyCode <= 90 || e.keyCode >= 186 && e.keyCode <= 222) {
this.setState({
isAlphabet: true
})
// When the key is an IME input (Japanese, Chinese)
} else if (e.keyCode === 229) {
this.setState({
isIME: true
})
}
}
handleKeyUp (e) {
// reset states
this.setState({
isConfirmTranslation: false
})
// When the key is translation confirmation (Enter, Space)
if (this.state.isIME && (e.keyCode === 32 || e.keyCode === 13)) {
this.setState({
isConfirmTranslation: true
})
const keyword = this.refs.searchInput.value
this.updateKeyword(keyword)
}
} }
handleSearchChange (e) { handleSearchChange (e) {
if (this.state.isAlphabet || this.state.isConfirmTranslation) { const keyword = e.target.value
const keyword = this.refs.searchInput.value this.debouncedUpdateKeyword(keyword)
this.updateKeyword(keyword)
} else {
e.preventDefault()
}
}
updateKeyword (keyword) {
const { dispatch } = this.props
dispatch(push(`/searched/${encodeURIComponent(keyword)}`))
this.setState({
search: keyword
})
ee.emit('top:search', keyword)
} }
handleSearchFocus (e) { handleSearchFocus (e) {
@@ -141,6 +108,7 @@ class TopBar extends React.Component {
isSearching: true isSearching: true
}) })
} }
handleSearchBlur (e) { handleSearchBlur (e) {
e.stopPropagation() e.stopPropagation()
@@ -170,7 +138,7 @@ class TopBar extends React.Component {
} }
handleCodeInit () { handleCodeInit () {
ee.emit('top:search', this.refs.searchInput.value) ee.emit('top:search', this.refs.searchInput.value || '')
} }
render () { render () {
@@ -183,24 +151,23 @@ class TopBar extends React.Component {
<div styleName='control'> <div styleName='control'>
<div styleName='control-search'> <div styleName='control-search'>
<div styleName='control-search-input' <div styleName='control-search-input'
onFocus={(e) => this.handleSearchFocus(e)} onFocus={this.handleSearchFocus}
onBlur={(e) => this.handleSearchBlur(e)} onBlur={this.handleSearchBlur}
tabIndex='-1' tabIndex='-1'
ref='search' ref='search'
> >
<input <CInput
ref='searchInput' ref='searchInput'
value={this.state.search} value={this.state.search}
onChange={(e) => this.handleSearchChange(e)} onInputChange={this.handleSearchChange}
onKeyDown={(e) => this.handleKeyDown(e)} onKeyDown={this.handleKeyDown}
onKeyUp={(e) => this.handleKeyUp(e)}
placeholder={i18n.__('Search')} placeholder={i18n.__('Search')}
type='text' type='text'
className='searchInput' className='searchInput'
/> />
{this.state.search !== '' && {this.state.search !== '' &&
<button styleName='control-search-input-clear' <button styleName='control-search-input-clear'
onClick={(e) => this.handleSearchClearButton(e)} onClick={this.handleSearchClearButton}
> >
<i className='fa fa-fw fa-times' /> <i className='fa fa-fw fa-times' />
<span styleName='control-search-input-clear-tooltip'>{i18n.__('Clear Search')}</span> <span styleName='control-search-input-clear-tooltip'>{i18n.__('Clear Search')}</span>

View File

@@ -32,6 +32,8 @@ export const DEFAULT_CONFIG = {
deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace', deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace',
pasteSmartly: OSX ? 'Command + Shift + V' : 'Ctrl + Shift + V', pasteSmartly: OSX ? 'Command + Shift + V' : 'Ctrl + Shift + V',
prettifyMarkdown: 'Shift + F', prettifyMarkdown: 'Shift + F',
insertDate: OSX ? 'Command + /' : 'Ctrl + /',
insertDateTime: OSX ? 'Command + Alt + /' : 'Ctrl + Shift + /',
toggleMenuBar: 'Alt' toggleMenuBar: 'Alt'
}, },
ui: { ui: {
@@ -49,6 +51,7 @@ export const DEFAULT_CONFIG = {
fontFamily: win ? 'Consolas' : 'Monaco', fontFamily: win ? 'Consolas' : 'Monaco',
indentType: 'space', indentType: 'space',
indentSize: '2', indentSize: '2',
lineWrapping: true,
enableRulers: false, enableRulers: false,
rulers: [80, 120], rulers: [80, 120],
displayLineNumbers: true, displayLineNumbers: true,
@@ -91,8 +94,10 @@ export const DEFAULT_CONFIG = {
breaks: true, breaks: true,
smartArrows: false, smartArrows: false,
allowCustomCSS: false, allowCustomCSS: false,
customCSS: '',
customCSS: '/* Drop Your Custom CSS Code Here */',
sanitize: 'STRICT', // 'STRICT', 'ALLOW_STYLES', 'NONE' sanitize: 'STRICT', // 'STRICT', 'ALLOW_STYLES', 'NONE'
mermaidHTMLLabel: false,
lineThroughCheckbox: true lineThroughCheckbox: true
}, },
blog: { blog: {
@@ -116,7 +121,6 @@ function validate (config) {
} }
function _save (config) { function _save (config) {
console.log(config)
window.localStorage.setItem('config', JSON.stringify(config)) window.localStorage.setItem('config', JSON.stringify(config))
} }
@@ -149,7 +153,7 @@ function get () {
const theme = consts.THEMES.find(theme => theme.name === config.editor.theme) const theme = consts.THEMES.find(theme => theme.name === config.editor.theme)
if (theme) { if (theme) {
editorTheme.setAttribute('href', `../${theme.path}`) editorTheme.setAttribute('href', theme.path)
} else { } else {
config.editor.theme = 'default' config.editor.theme = 'default'
} }
@@ -160,7 +164,13 @@ function get () {
function set (updates) { function set (updates) {
const currentConfig = get() const currentConfig = get()
const newConfig = Object.assign({}, DEFAULT_CONFIG, currentConfig, updates)
const arrangedUpdates = updates
if (updates.preview !== undefined && updates.preview.customCSS === '') {
arrangedUpdates.preview.customCSS = DEFAULT_CONFIG.preview.customCSS
}
const newConfig = Object.assign({}, DEFAULT_CONFIG, currentConfig, arrangedUpdates)
if (!validate(newConfig)) throw new Error('INVALID CONFIG') if (!validate(newConfig)) throw new Error('INVALID CONFIG')
_save(newConfig) _save(newConfig)
@@ -191,7 +201,7 @@ function set (updates) {
const newTheme = consts.THEMES.find(theme => theme.name === newConfig.editor.theme) const newTheme = consts.THEMES.find(theme => theme.name === newConfig.editor.theme)
if (newTheme) { if (newTheme) {
editorTheme.setAttribute('href', `../${newTheme.path}`) editorTheme.setAttribute('href', newTheme.path)
} }
ipcRenderer.send('config-renew', { ipcRenderer.send('config-renew', {

View File

@@ -241,6 +241,10 @@ function migrateAttachments (markdownContent, storagePath, noteKey) {
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths. * @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
*/ */
function fixLocalURLS (renderedHTML, storagePath) { function fixLocalURLS (renderedHTML, storagePath) {
const encodedWin32SeparatorRegex = /%5C/g
const storageRegex = new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g')
const storageUrl = 'file:///' + path.join(storagePath, DESTINATION_FOLDER).replace(/\\/g, '/')
/* /*
A :storage reference is like `:storage/3b6f8bd6-4edd-4b15-96e0-eadc4475b564/f939b2c3.jpg`. A :storage reference is like `:storage/3b6f8bd6-4edd-4b15-96e0-eadc4475b564/f939b2c3.jpg`.
@@ -250,8 +254,7 @@ function fixLocalURLS (renderedHTML, storagePath) {
- `(?:\\\/|%5C)` match the path seperator. `\\\/` for posix systems and `%5C` for windows. - `(?:\\\/|%5C)` match the path seperator. `\\\/` for posix systems and `%5C` for windows.
*/ */
return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(?:(?:\\\/|%5C)[-.\\w]+)+', 'g'), function (match) { return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(?:(?:\\\/|%5C)[-.\\w]+)+', 'g'), function (match) {
var encodedPathSeparators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g') return match.replace(encodedWin32SeparatorRegex, '/').replace(storageRegex, storageUrl)
return match.replace(encodedPathSeparators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
}) })
} }
@@ -617,8 +620,6 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
} }
}) })
}) })
} else {
console.info('Attachment folder ("' + attachmentFolder + '") did not exist..')
} }
} }

View File

@@ -19,6 +19,7 @@
.control .control
padding 25px 0px padding 25px 0px
text-align center text-align center
display: flex
.control-button .control-button
width 240px width 240px

View File

@@ -225,7 +225,7 @@ class FolderItem extends React.Component {
<div styleName='folderItem-left' <div styleName='folderItem-left'
style={{borderColor: folder.color}} style={{borderColor: folder.color}}
> >
<span styleName='folderItem-left-name'>{folder.name}</span> <span>{folder.name}</span>
<span styleName='folderItem-left-key'>({folder.key})</span> <span styleName='folderItem-left-key'>({folder.key})</span>
</div> </div>
<div styleName='folderItem-right'> <div styleName='folderItem-right'>
@@ -288,10 +288,10 @@ class Handle extends React.Component {
class SortableFolderItemComponent extends React.Component { class SortableFolderItemComponent extends React.Component {
render () { render () {
const StyledHandle = CSSModules(Handle, this.props.styles) const StyledHandle = CSSModules(Handle, styles)
const DragHandle = SortableHandle(StyledHandle) const DragHandle = SortableHandle(StyledHandle)
const StyledFolderItem = CSSModules(FolderItem, this.props.styles) const StyledFolderItem = CSSModules(FolderItem, styles)
return ( return (
<div> <div>

View File

@@ -22,7 +22,7 @@ class FolderList extends React.Component {
}) })
return ( return (
<div styleName='folderList'> <div>
{folderList.length > 0 {folderList.length > 0
? folderList ? folderList
: <div styleName='folderList-empty'>{i18n.__('No Folders')}</div> : <div styleName='folderList-empty'>{i18n.__('No Folders')}</div>

View File

@@ -181,7 +181,26 @@ class HotkeyTab extends React.Component {
onChange={(e) => this.handleHotkeyChange(e)} onChange={(e) => this.handleHotkeyChange(e)}
ref='prettifyMarkdown' ref='prettifyMarkdown'
value={config.hotkey.prettifyMarkdown} value={config.hotkey.prettifyMarkdown}
type='text'/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Insert Current Date')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
value={config.hotkey.insertDate}
type='text' type='text'
disabled='true'
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Insert Current Date and Time')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
value={config.hotkey.insertDateTime}
type='text'
disabled='true'
/> />
</div> </div>
</div> </div>

View File

@@ -14,6 +14,7 @@ import { getLanguages } from 'browser/lib/Languages'
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
const WIN = global.process.platform === 'win32'
const electron = require('electron') const electron = require('electron')
const ipc = electron.ipcRenderer const ipc = electron.ipcRenderer
@@ -95,6 +96,7 @@ class UiTab extends React.Component {
enableRulers: this.refs.enableEditorRulers.value === 'true', enableRulers: this.refs.enableEditorRulers.value === 'true',
rulers: this.refs.editorRulers.value.replace(/[^0-9,]/g, '').split(','), rulers: this.refs.editorRulers.value.replace(/[^0-9,]/g, '').split(','),
displayLineNumbers: this.refs.editorDisplayLineNumbers.checked, displayLineNumbers: this.refs.editorDisplayLineNumbers.checked,
lineWrapping: this.refs.editorLineWrapping.checked,
switchPreview: this.refs.editorSwitchPreview.value, switchPreview: this.refs.editorSwitchPreview.value,
keyMap: this.refs.editorKeyMap.value, keyMap: this.refs.editorKeyMap.value,
snippetDefaultLanguage: this.refs.editorSnippetDefaultLanguage.value, snippetDefaultLanguage: this.refs.editorSnippetDefaultLanguage.value,
@@ -129,6 +131,7 @@ class UiTab extends React.Component {
breaks: this.refs.previewBreaks.checked, breaks: this.refs.previewBreaks.checked,
smartArrows: this.refs.previewSmartArrows.checked, smartArrows: this.refs.previewSmartArrows.checked,
sanitize: this.refs.previewSanitize.value, sanitize: this.refs.previewSanitize.value,
mermaidHTMLLabel: this.refs.previewMermaidHTMLLabel.checked,
allowCustomCSS: this.refs.previewAllowCustomCSS.checked, allowCustomCSS: this.refs.previewAllowCustomCSS.checked,
lineThroughCheckbox: this.refs.lineThroughCheckbox.checked, lineThroughCheckbox: this.refs.lineThroughCheckbox.checked,
customCSS: this.customCSSCM.getCodeMirror().getValue() customCSS: this.customCSSCM.getCodeMirror().getValue()
@@ -141,7 +144,7 @@ class UiTab extends React.Component {
const theme = consts.THEMES.find(theme => theme.name === newCodemirrorTheme) const theme = consts.THEMES.find(theme => theme.name === newCodemirrorTheme)
if (theme) { if (theme) {
checkHighLight.setAttribute('href', `../${theme.path}`) checkHighLight.setAttribute('href', theme.path)
} }
} }
@@ -551,6 +554,17 @@ class UiTab extends React.Component {
</label> </label>
</div> </div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.lineWrapping}
ref='editorLineWrapping'
type='checkbox'
/>&nbsp;
{i18n.__('Wrap line in Snippet Note')}
</label>
</div>
<div styleName='group-checkBoxSection'> <div styleName='group-checkBoxSection'>
<label> <label>
<input onChange={(e) => this.handleUIChange(e)} <input onChange={(e) => this.handleUIChange(e)}
@@ -806,6 +820,16 @@ class UiTab extends React.Component {
</select> </select>
</div> </div>
</div> </div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.preview.mermaidHTMLLabel}
ref='previewMermaidHTMLLabel'
type='checkbox'
/>&nbsp;
{i18n.__('Enable HTML label in mermaid flowcharts')}
</label>
</div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
{i18n.__('LaTeX Inline Open Delimiter')} {i18n.__('LaTeX Inline Open Delimiter')}
@@ -889,7 +913,6 @@ class UiTab extends React.Component {
onChange={e => this.handleUIChange(e)} onChange={e => this.handleUIChange(e)}
ref={e => (this.customCSSCM = e)} ref={e => (this.customCSSCM = e)}
value={config.preview.customCSS} value={config.preview.customCSS}
defaultValue={'/* Drop Your Custom CSS Code Here */\n'}
options={{ options={{
lineNumbers: true, lineNumbers: true,
mode: 'css', mode: 'css',

View File

@@ -147,7 +147,7 @@ class Preferences extends React.Component {
key={tab.target} key={tab.target}
onClick={(e) => this.handleNavButtonClick(tab.target)(e)} onClick={(e) => this.handleNavButtonClick(tab.target)(e)}
> >
<span styleName='nav-button-label'> <span>
{tab.label} {tab.label}
</span> </span>
{isUiHotkeyTab ? this.haveToSaveNotif(tab[tab.label].type, tab[tab.label].message) : null} {isUiHotkeyTab ? this.haveToSaveNotif(tab[tab.label].type, tab[tab.label].message) : null}

View File

@@ -476,7 +476,8 @@ const reducer = combineReducers({
router: connectRouter(history) router: connectRouter(history)
}) })
const store = createStore(reducer, undefined, compose( const store = createStore(reducer, undefined, process.env.NODE_ENV === 'development'
applyMiddleware(routerMiddleware(history)), DevTools.instrument())) ? compose(applyMiddleware(routerMiddleware(history)), DevTools.instrument())
: applyMiddleware(routerMiddleware(history)))
export { store, history } export { store, history }

View File

@@ -3,6 +3,7 @@ const BrowserWindow = electron.BrowserWindow
const shell = electron.shell const shell = electron.shell
const ipc = electron.ipcMain const ipc = electron.ipcMain
const mainWindow = require('./main-window') const mainWindow = require('./main-window')
const os = require('os')
const macOS = process.platform === 'darwin' const macOS = process.platform === 'darwin'
// const WIN = process.platform === 'win32' // const WIN = process.platform === 'win32'
@@ -411,6 +412,28 @@ const help = {
click () { shell.openExternal('https://github.com/TobseF/boostnote-markdown-cheatsheet/blob/master/BOOSTNOTE_MARKDOWN_CHEAT_SHEET.md') } click () { shell.openExternal('https://github.com/TobseF/boostnote-markdown-cheatsheet/blob/master/BOOSTNOTE_MARKDOWN_CHEAT_SHEET.md') }
} }
] ]
},
{
type: 'separator'
},
{
label: 'About',
click () {
const version = electron.app.getVersion()
const electronVersion = process.versions.electron
const chromeVersion = process.versions.chrome
const nodeVersion = process.versions.node
const v8Version = process.versions.v8
const OSInfo = `${os.type()} ${os.arch()} ${os.release()}`
const detail = `Version: ${version}\nElectron: ${electronVersion}\nChrome: ${chromeVersion}\nNode.js: ${nodeVersion}\nV8: ${v8Version}\nOS: ${OSInfo}`
electron.dialog.showMessageBox(BrowserWindow.getFocusedWindow(),
{
title: 'BoostNote',
message: 'BoostNote',
type: 'info',
detail: `\n${detail}`
})
}
} }
] ]
} }

View File

@@ -54,7 +54,7 @@ const mainWindow = new BrowserWindow({
}, },
icon: path.resolve(__dirname, '../resources/app.png') icon: path.resolve(__dirname, '../resources/app.png')
}) })
const url = path.resolve(__dirname, process.env.NODE_ENV === 'production' ? './main.production.html' : './main.development.html') const url = path.resolve(__dirname, process.env.NODE_ENV === 'development' ? './main.development.html' : './main.production.html')
mainWindow.loadURL('file://' + url) mainWindow.loadURL('file://' + url)
mainWindow.setMenuBarVisibility(false) mainWindow.setMenuBarVisibility(false)

View File

@@ -110,7 +110,6 @@
<script src="../extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js"></script> <script src="../extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js"></script>
<script src="../extra_scripts/codemirror/mode/bfm/bfm.js"></script> <script src="../extra_scripts/codemirror/mode/bfm/bfm.js"></script>
<script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script> <script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script>
<script src="../extra_scripts/codemirror/mode/bfm/bfm.js"></script>
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script> <script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>
<script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script> <script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script>

View File

@@ -105,7 +105,6 @@
<script src="../extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js"></script> <script src="../extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js"></script>
<script src="../extra_scripts/codemirror/mode/bfm/bfm.js"></script> <script src="../extra_scripts/codemirror/mode/bfm/bfm.js"></script>
<script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script> <script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script>
<script src="../extra_scripts/codemirror/mode/bfm/bfm.js"></script>
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script> <script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>
<script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script> <script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script>

View File

@@ -156,5 +156,7 @@
"⚠ 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! ⚠",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -212,5 +212,7 @@
"⚠ 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! ⚠",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -187,5 +187,7 @@
"Snippet Default Language": "Snippet Default Language", "Snippet Default Language": "Snippet Default Language",
"New notes are tagged with the filtering tags": "New notes are tagged with the filtering tags", "New notes are tagged with the filtering tags": "New notes are tagged with the filtering tags",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -158,5 +158,7 @@
"Spellcheck disabled": "Deshabilitar corrector ortográfico", "Spellcheck disabled": "Deshabilitar corrector ortográfico",
"Show menu bar": "Mostrar barra del menú", "Show menu bar": "Mostrar barra del menú",
"Auto Detect": "Detección automática", "Auto Detect": "Detección automática",
"Snippet Default Language": "Lenguaje por defecto de los fragmentos de código" "Snippet Default Language": "Lenguaje por defecto de los fragmentos de código",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -160,5 +160,7 @@
"⚠ 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! ⚠",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -172,5 +172,7 @@
"Snippet name": "Nom du snippet", "Snippet name": "Nom du snippet",
"Snippet prefix": "Préfixe du snippet", "Snippet prefix": "Préfixe du snippet",
"Delete Note": "Supprimer la note", "Delete Note": "Supprimer la note",
"New notes are tagged with the filtering tags": "Les nouvelles notes sont taggées avec les tags de filtrage" "New notes are tagged with the filtering tags": "Les nouvelles notes sont taggées avec les tags de filtrage",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -180,5 +180,7 @@
"⚠ 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! ⚠",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -160,5 +160,7 @@
"⚠ 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! ⚠",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -219,5 +219,7 @@
"⚠ 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! ⚠": "⚠ このノートのストレージに存在しない添付ファイルへのリンクを貼り付けました。添付ファイルへのリンクの貼り付けは同一ストレージ内でのみサポートされています。代わりに添付ファイルをドラッグアンドドロップしてください! ⚠",
"Spellcheck disabled": "スペルチェック無効", "Spellcheck disabled": "スペルチェック無効",
"Show menu bar": "メニューバーを表示", "Show menu bar": "メニューバーを表示",
"Auto Detect": "自動検出" "Auto Detect": "自動検出",
"Enable HTML label in mermaid flowcharts": "mermaid flowchartでHTMLラベルを有効にする ⚠ このオプションには潜在的なXSSの危険性があります。",
"Wrap line in Snippet Note": "行を右端で折り返すSnippet Note"
} }

View File

@@ -163,5 +163,7 @@
"⚠ 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! ⚠",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -156,5 +156,7 @@
"⚠ 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! ⚠",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -165,5 +165,7 @@
"Add tag...": "Dodaj tag...", "Add tag...": "Dodaj tag...",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -156,5 +156,7 @@
"⚠ 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! ⚠",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -155,5 +155,7 @@
"⚠ 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! ⚠": "⚠ Você colou um link referente a um anexo que não pôde ser encontrado no local de armazenamento desta nota. A vinculação de anexos de referência de links só é suportada se o local de origem e de destino for o mesmo de armazenamento. Por favor, arraste e solte o anexo na nota! ⚠", "⚠ 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! ⚠": "⚠ Você colou um link referente a um anexo que não pôde ser encontrado no local de armazenamento desta nota. A vinculação de anexos de referência de links só é suportada se o local de origem e de destino for o mesmo de armazenamento. Por favor, arraste e solte o anexo na nota! ⚠",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -153,5 +153,7 @@
"⚠ 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! ⚠",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -155,5 +155,7 @@
"⚠ 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! ⚠",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -182,5 +182,7 @@
"Snippet Default Language": "ทำการ Snippet ภาษาที่เป็นค่าเริ่มต้น", "Snippet Default Language": "ทำการ Snippet ภาษาที่เป็นค่าเริ่มต้น",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -155,5 +155,7 @@
"Allow dangerous html tags": "Tehlikeli html etiketlerine izin ver", "Allow dangerous html tags": "Tehlikeli html etiketlerine izin ver",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

4
locales/zh-CN.json Executable file → Normal file
View File

@@ -220,5 +220,7 @@
"Render newlines in Markdown paragraphs as <br>":"在 Markdown 段落中使用 <br> 换行", "Render newlines in Markdown paragraphs as <br>":"在 Markdown 段落中使用 <br> 换行",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

4
locales/zh-TW.json Executable file → Normal file
View File

@@ -164,5 +164,7 @@
"⚠ 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! ⚠",
"Spellcheck disabled": "Spellcheck disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar", "Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect" "Auto Detect": "Auto Detect",
"Enable HTML label in mermaid flowcharts": "Enable HTML label in mermaid flowcharts ⚠ This option potentially has a risk of XSS.",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
} }

View File

@@ -1,7 +1,7 @@
{ {
"name": "boost", "name": "boost",
"productName": "Boostnote", "productName": "Boostnote",
"version": "0.11.17", "version": "0.12.1",
"main": "index.js", "main": "index.js",
"description": "Boostnote", "description": "Boostnote",
"license": "GPL-3.0", "license": "GPL-3.0",
@@ -106,6 +106,7 @@
"react-autosuggest": "^9.4.0", "react-autosuggest": "^9.4.0",
"react-codemirror": "^1.0.0", "react-codemirror": "^1.0.0",
"react-color": "^2.2.2", "react-color": "^2.2.2",
"react-composition-input": "^1.1.1",
"react-debounce-render": "^4.0.1", "react-debounce-render": "^4.0.1",
"react-dom": "^16.8.6", "react-dom": "^16.8.6",
"react-image-carousel": "^2.0.18", "react-image-carousel": "^2.0.18",

View File

@@ -287,7 +287,11 @@ it('should replace the all ":storage" path with the actual storage path', functi
' </p>\n' + ' </p>\n' +
' <pre class="fence" data-line="8">\n' + ' <pre class="fence" data-line="8">\n' +
' <span class="filename"></span>\n' + ' <span class="filename"></span>\n' +
' <div class="gallery" data-autoplay="undefined" data-height="undefined">:storage' + mdurl.encode(path.sep) + noteKey + mdurl.encode(path.sep) + 'f939b2c3.jpg</div>\n' + ' <div class="gallery" data-autoplay="undefined" data-height="undefined">:storage' + mdurl.encode(path.win32.sep) + noteKey + mdurl.encode(path.win32.sep) + 'f939b2c3.jpg</div>\n' +
' </pre>\n' +
' <pre class="fence" data-line="10">\n' +
' <span class="filename"></span>\n' +
' <div class="gallery" data-autoplay="undefined" data-height="undefined">:storage' + mdurl.encode(path.posix.sep) + noteKey + mdurl.encode(path.posix.sep) + 'f939b2c3.jpg</div>\n' +
' </pre>\n' + ' </pre>\n' +
' </body>\n' + ' </body>\n' +
'</html>' '</html>'
@@ -300,17 +304,21 @@ it('should replace the all ":storage" path with the actual storage path', functi
' <body data-theme="default">\n' + ' <body data-theme="default">\n' +
' <h2 data-line="0" id="Headline">Headline</h2>\n' + ' <h2 data-line="0" id="Headline">Headline</h2>\n' +
' <p data-line="2">\n' + ' <p data-line="2">\n' +
' <img src="file:///' + storagePath + path.sep + storageFolder + path.sep + noteKey + path.sep + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' + ' <img src="file:///' + storagePath + '/' + storageFolder + '/' + noteKey + '/' + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
' </p>\n' + ' </p>\n' +
' <p data-line="4">\n' + ' <p data-line="4">\n' +
' <a href="file:///' + storagePath + path.sep + storageFolder + path.sep + noteKey + path.sep + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' + ' <a href="file:///' + storagePath + '/' + storageFolder + '/' + noteKey + '/' + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
' </p>\n' + ' </p>\n' +
' <p data-line="6">\n' + ' <p data-line="6">\n' +
' <img src="file:///' + storagePath + path.sep + storageFolder + path.sep + noteKey + path.sep + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' + ' <img src="file:///' + storagePath + '/' + storageFolder + '/' + noteKey + '/' + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
' </p>\n' + ' </p>\n' +
' <pre class="fence" data-line="8">\n' + ' <pre class="fence" data-line="8">\n' +
' <span class="filename"></span>\n' + ' <span class="filename"></span>\n' +
' <div class="gallery" data-autoplay="undefined" data-height="undefined">file:///' + storagePath + path.sep + storageFolder + path.sep + noteKey + path.sep + 'f939b2c3.jpg</div>\n' + ' <div class="gallery" data-autoplay="undefined" data-height="undefined">file:///' + storagePath + '/' + storageFolder + '/' + noteKey + '/' + 'f939b2c3.jpg</div>\n' +
' </pre>\n' +
' <pre class="fence" data-line="10">\n' +
' <span class="filename"></span>\n' +
' <div class="gallery" data-autoplay="undefined" data-height="undefined">file:///' + storagePath + '/' + storageFolder + '/' + noteKey + '/' + 'f939b2c3.jpg</div>\n' +
' </pre>\n' + ' </pre>\n' +
' </body>\n' + ' </body>\n' +
'</html>' '</html>'
@@ -345,10 +353,10 @@ it('should replace the ":storage" path with the actual storage path when they ha
' <body data-theme="default">\n' + ' <body data-theme="default">\n' +
' <h2 data-line="0" id="Headline">Headline</h2>\n' + ' <h2 data-line="0" id="Headline">Headline</h2>\n' +
' <p data-line="2">\n' + ' <p data-line="2">\n' +
' <img src="file:///' + storagePath + path.sep + storageFolder + path.sep + noteKey + path.sep + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' + ' <img src="file:///' + storagePath + '/' + storageFolder + '/' + noteKey + '/' + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
' </p>\n' + ' </p>\n' +
' <p data-line="4">\n' + ' <p data-line="4">\n' +
' <a href="file:///' + storagePath + path.sep + storageFolder + path.sep + noteKey + path.sep + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' + ' <a href="file:///' + storagePath + '/' + storageFolder + '/' + noteKey + '/' + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
' </p>\n' + ' </p>\n' +
' </body>\n' + ' </body>\n' +
'</html>' '</html>'

View File

@@ -3,6 +3,9 @@ const copyFile = require('browser/main/lib/dataApi/copyFile')
const path = require('path') const path = require('path')
const fs = require('fs') const fs = require('fs')
const os = require('os')
const execSync = require('child_process').execSync
const removeDirCommand = os.platform() === 'win32' ? 'rmdir /s /q ' : 'rm -rf '
const testFile = 'test.txt' const testFile = 'test.txt'
const srcFolder = path.join(__dirname, '🤔') const srcFolder = path.join(__dirname, '🤔')
@@ -29,7 +32,7 @@ test('`copyFile` should handle encoded URI on src path', (t) => {
test.after((t) => { test.after((t) => {
fs.unlinkSync(srcPath) fs.unlinkSync(srcPath)
fs.unlinkSync(dstPath) fs.unlinkSync(dstPath)
fs.rmdirSync(srcFolder) execSync(removeDirCommand + '"' + srcFolder + '"')
fs.rmdirSync(dstFolder) execSync(removeDirCommand + '"' + dstFolder + '"')
}) })

View File

@@ -104,6 +104,11 @@ Term 2 with *inline markup*
` `
const shortcuts = '<kbd>Ctrl</kbd>\n\n[[Ctrl]]' const shortcuts = '<kbd>Ctrl</kbd>\n\n[[Ctrl]]'
const footnote = `
^[hello-world]
hello-world: https://github.com/BoostIO/Boostnote/
`
export default { export default {
basic, basic,
codeblock, codeblock,
@@ -115,5 +120,6 @@ export default {
subTexts, subTexts,
supTexts, supTexts,
deflists, deflists,
shortcuts shortcuts,
footnote
} }

View File

@@ -4,8 +4,8 @@ jest.mock('electron', () => {
}) })
const spellcheck = require('browser/lib/spellcheck') const spellcheck = require('browser/lib/spellcheck')
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder') const buildEditorContextMenu = require('browser/lib/contextMenuBuilder').buildEditorContextMenu
const buildMarkdownPreviewContextMenu = require('browser/lib/contextMenuBuilder') const buildMarkdownPreviewContextMenu = require('browser/lib/contextMenuBuilder').buildMarkdownPreviewContextMenu
beforeEach(() => { beforeEach(() => {
menuBuilderParameter = null menuBuilderParameter = null

View File

@@ -68,3 +68,8 @@ test('Markdown.render() should render shortcuts correctly', t => {
const rendered = md.render(markdownFixtures.shortcuts) const rendered = md.render(markdownFixtures.shortcuts)
t.snapshot(rendered) t.snapshot(rendered)
}) })
test('Markdown.render() should render footnote correctly', t => {
const rendered = md.render(markdownFixtures.footnote)
t.snapshot(rendered)
})

58
tests/lib/slugify-test.js Normal file
View File

@@ -0,0 +1,58 @@
import test from 'ava'
import slugify from 'browser/lib/slugify'
test('alphabet and digit', t => {
const upperAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
const lowerAlphabet = 'abcdefghijklmnopqrstuvwxyz'
const digit = '0123456789'
const testCase = upperAlphabet + lowerAlphabet + digit
const decodeSlug = decodeURI(slugify(testCase))
t.true(decodeSlug === testCase)
})
test('should delete unavailable symbols', t => {
const availableSymbols = '_-'
const testCase = availableSymbols + '][!\'#$%&()*+,./:;<=>?@\\^{|}~`'
const decodeSlug = decodeURI(slugify(testCase))
t.true(decodeSlug === availableSymbols)
})
test('should convert from white spaces between words to hyphens', t => {
const testCase = 'This is one'
const expectedString = 'This-is-one'
const decodeSlug = decodeURI(slugify(testCase))
t.true(decodeSlug === expectedString)
})
test('should remove leading white spaces', t => {
const testCase = ' This is one'
const expectedString = 'This-is-one'
const decodeSlug = decodeURI(slugify(testCase))
t.true(decodeSlug === expectedString)
})
test('should remove trailing white spaces', t => {
const testCase = 'This is one '
const expectedString = 'This-is-one'
const decodeSlug = decodeURI(slugify(testCase))
t.true(decodeSlug === expectedString)
})
test('2-byte charactor support', t => {
const testCase = '菠萝芒果テストÀžƁƵ'
const decodeSlug = decodeURI(slugify(testCase))
t.true(decodeSlug === testCase)
})
test('emoji', t => {
const testCase = '🌸'
const decodeSlug = decodeURI(slugify(testCase))
t.true(decodeSlug === testCase)
})

View File

@@ -4,6 +4,21 @@ The actual snapshot is saved in `markdown-test.js.snap`.
Generated by [AVA](https://ava.li). Generated by [AVA](https://ava.li).
## Markdown.render() should render footnote correctly
> Snapshot 1
`<p data-line="1"><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup><br />␊
hello-world: <a href="https://github.com/BoostIO/Boostnote/">https://github.com/BoostIO/Boostnote/</a></p>␊
<hr class="footnotes-sep" />␊
<section class="footnotes">␊
<ol class="footnotes-list">␊
<li id="fn1" class="footnote-item"><p>hello-world <a href="#fnref1" class="footnote-backref">↩︎</a></p>␊
</li>␊
</ol>␊
</section>␊
`
## Markdown.render() should render line breaks correctly ## Markdown.render() should render line breaks correctly
> Snapshot 1 > Snapshot 1

View File

@@ -7751,6 +7751,13 @@ react-color@^2.2.2:
reactcss "^1.2.0" reactcss "^1.2.0"
tinycolor2 "^1.4.1" tinycolor2 "^1.4.1"
react-composition-input@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/react-composition-input/-/react-composition-input-1.1.1.tgz#51fc711f8b1c7d11e39210639175f0b48de44aff"
integrity sha512-xzRAUvsrEdSjI1tQXu3ouPHkHVZnunx6OoAFsv4YxVV6fIBHc9XZuxkmJwoxSatPxJ6WN94k91PBWQTsL6h/ZA==
dependencies:
prop-types "^15.6.2"
react-css-modules@^4.7.9: react-css-modules@^4.7.9:
version "4.7.9" version "4.7.9"
resolved "https://registry.yarnpkg.com/react-css-modules/-/react-css-modules-4.7.9.tgz#459235e149a0df7a62b092ae079d53cb0b6154ee" resolved "https://registry.yarnpkg.com/react-css-modules/-/react-css-modules-4.7.9.tgz#459235e149a0df7a62b092ae079d53cb0b6154ee"