diff --git a/.editorconfig b/.editorconfig
index a4730cbf..8c5bd614 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,4 +1,4 @@
-# EditorConfig is awesome: http://EditorConfig.org
+# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
diff --git a/.vscode/launch.json b/.vscode/launch.json
index a742a59e..9d1cc4ec 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -17,7 +17,7 @@
"${workspaceFolder}/index.js"
],
"windows": {
- "runtimeExecutable": "${workspaceFolder}/node_modeules/.bin/electron.cmd"
+ "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
}
},
{
diff --git a/Backers.md b/Backers.md
deleted file mode 100644
index 18d221bf..00000000
--- a/Backers.md
+++ /dev/null
@@ -1,72 +0,0 @@
-
this.handleDropImage(e)}
+ return (<
+ div className={
+ className == null ? 'CodeEditor' : `CodeEditor ${className}`
+ }
+ ref='root'
+ tabIndex='-1'
+ style={
+ {
+ fontFamily,
+ fontSize: fontSize,
+ width: width
+ }
+ }
+ onDrop={
+ e => this.handleDropImage(e)
+ }
/>
)
}
diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js
index 10667feb..c910821d 100644
--- a/browser/components/MarkdownEditor.js
+++ b/browser/components/MarkdownEditor.js
@@ -7,6 +7,7 @@ import MarkdownPreview from 'browser/components/MarkdownPreview'
import eventEmitter from 'browser/main/lib/eventEmitter'
import { findStorage } from 'browser/lib/findStorage'
import ConfigManager from 'browser/main/lib/ConfigManager'
+import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
class MarkdownEditor extends React.Component {
constructor (props) {
@@ -221,6 +222,28 @@ class MarkdownEditor extends React.Component {
this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`)
}
+ handleDropImage (dropEvent) {
+ dropEvent.preventDefault()
+ const { storageKey, noteKey } = this.props
+
+ this.setState({
+ status: 'CODE'
+ }, () => {
+ this.refs.code.focus()
+
+ this.refs.code.editor.execCommand('goDocEnd')
+ this.refs.code.editor.execCommand('goLineEnd')
+ this.refs.code.editor.execCommand('newlineAndIndent')
+
+ attachmentManagement.handleAttachmentDrop(
+ this.refs.code,
+ storageKey,
+ noteKey,
+ dropEvent
+ )
+ })
+ }
+
handleKeyUp (e) {
const keyPressed = this.state.keyPressed
keyPressed.delete(e.keyCode)
@@ -232,7 +255,7 @@ class MarkdownEditor extends React.Component {
}
render () {
- const {className, value, config, storageKey, noteKey, getNote} = this.props
+ const {className, value, config, storageKey, noteKey, linesHighlighted, getNote} = this.props
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
@@ -270,14 +293,20 @@ class MarkdownEditor extends React.Component {
enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers}
displayLineNumbers={config.editor.displayLineNumbers}
+ matchingPairs={config.editor.matchingPairs}
+ matchingTriples={config.editor.matchingTriples}
+ explodingPairs={config.editor.explodingPairs}
scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey}
noteKey={noteKey}
fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
+ linesHighlighted={linesHighlighted}
onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)}
spellCheck={config.editor.spellcheck}
+ enableSmartPaste={config.editor.enableSmartPaste}
+ hotkey={config.hotkey}
switchPreview={config.editor.switchPreview}
/>
this.handleDropImage(e)}
/>
)
diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js
index 8edd67b5..67da01bd 100755
--- a/browser/components/MarkdownPreview.js
+++ b/browser/components/MarkdownPreview.js
@@ -28,6 +28,8 @@ import uri2path from 'file-uri-to-path'
import { remote, shell } from 'electron'
import attachmentManagement from '../main/lib/dataApi/attachmentManagement'
import filenamify from 'filenamify'
+import { render } from 'react-dom'
+import Carousel from 'react-image-carousel'
import ConfigManager from '../main/lib/ConfigManager'
const dialog = remote.dialog
@@ -69,7 +71,7 @@ export default class MarkdownPreview extends React.Component {
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
this.printHandler = () => this.handlePrint()
- this.linkClickHandler = this.handlelinkClick.bind(this)
+ this.linkClickHandler = this.handleLinkClick.bind(this)
this.initMarkdown = this.initMarkdown.bind(this)
this.initMarkdown()
}
@@ -224,6 +226,8 @@ export default class MarkdownPreview extends React.Component {
}
componentDidMount () {
+ const { onDrop } = this.props
+
this.refs.root.setAttribute('sandbox', 'allow-scripts')
this.refs.root.contentWindow.document.body.addEventListener(
'contextmenu',
@@ -261,7 +265,7 @@ export default class MarkdownPreview extends React.Component {
)
this.refs.root.contentWindow.document.addEventListener(
'drop',
- this.preventImageDroppedHandler
+ onDrop || this.preventImageDroppedHandler
)
this.refs.root.contentWindow.document.addEventListener(
'dragover',
@@ -278,6 +282,8 @@ export default class MarkdownPreview extends React.Component {
}
componentWillUnmount () {
+ const { onDrop } = this.props
+
this.refs.root.contentWindow.document.body.removeEventListener(
'contextmenu',
this.contextMenuHandler
@@ -296,7 +302,7 @@ export default class MarkdownPreview extends React.Component {
)
this.refs.root.contentWindow.document.removeEventListener(
'drop',
- this.preventImageDroppedHandler
+ onDrop || this.preventImageDroppedHandler
)
this.refs.root.contentWindow.document.removeEventListener(
'dragover',
@@ -532,6 +538,34 @@ export default class MarkdownPreview extends React.Component {
mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme)
}
)
+
+ _.forEach(
+ this.refs.root.contentWindow.document.querySelectorAll('.gallery'),
+ el => {
+ const images = el.innerHTML.split(/\n/g).filter(i => i.length > 0)
+ el.innerHTML = ''
+
+ const height = el.attributes.getNamedItem('data-height')
+ if (height && height.value !== 'undefined') {
+ el.style.height = height.value + 'vh'
+ }
+
+ let autoplay = el.attributes.getNamedItem('data-autoplay')
+ if (autoplay && autoplay.value !== 'undefined') {
+ autoplay = parseInt(autoplay.value, 10) || 0
+ } else {
+ autoplay = 0
+ }
+
+ render(
+ this.handleMouseDown(e)} >
diff --git a/browser/components/SnippetTab.styl b/browser/components/SnippetTab.styl
index a31b8594..d101f318 100644
--- a/browser/components/SnippetTab.styl
+++ b/browser/components/SnippetTab.styl
@@ -3,19 +3,30 @@
flex 1
min-width 70px
overflow hidden
+ border-left 1px solid $ui-borderColor
+ border-top 1px solid $ui-borderColor
&:hover
+ background-color alpha($ui-button--active-backgroundColor, 20%)
.deleteButton
- color $ui-inactive-text-color
- &:hover
- background-color darken($ui-backgroundColor, 15%)
- &:active
- color white
- background-color $ui-active-color
+ color: $ui-text-color
+ visibility visible
+ transition 0.15s
+ .button
+ color: $ui-text-color
+ transition 0.15s
.root--active
@extend .root
min-width 100px
- border-bottom $ui-border
+ background-color alpha($ui-button--active-backgroundColor, 60%)
+ .deleteButton
+ visibility visible
+ color: $ui-text-color
+ transition 0.15s
+ .button
+ font-weight bold
+ color: $ui-text-color
+ transition 0.15s
.button
width 100%
@@ -27,8 +38,7 @@
background-color transparent
transition 0.15s
border-left 4px solid transparent
- &:hover
- background-color $ui-button--hover-backgroundColor
+ color $ui-inactive-text-color
.deleteButton
position absolute
@@ -42,6 +52,7 @@
color $ui-inactive-text-color
background-color transparent
border-radius 2px
+ visibility hidden
.input
height 29px
@@ -50,76 +61,66 @@
width 100%
outline none
+body[data-theme="default"], body[data-theme="white"]
+ .root--active
+ &:hover
+ background-color alpha($ui-button--active-backgroundColor, 60%)
+
body[data-theme="dark"]
.root
- color $ui-dark-text-color
border-color $ui-dark-borderColor
+ border-top 1px solid $ui-dark-borderColor
&:hover
- background-color $ui-dark-button--hover-backgroundColor
+ background-color alpha($ui-dark-button--active-backgroundColor, 20%)
+ transition 0.15s
+ .button
+ color $ui-dark-text-color
+ transition 0.15s
.deleteButton
- color $ui-dark-inactive-text-color
- &:hover
- background-color darken($ui-dark-button--hover-backgroundColor, 15%)
- &:active
- color $ui-dark-text-color
- background-color $ui-dark-button--active-backgroundColor
+ color $ui-dark-text-color
+ transition 0.15s
.root--active
- color $ui-dark-text-color
- border-color $ui-dark-borderColor
- &:hover
- background-color $ui-dark-button--hover-backgroundColor
- .deleteButton
- color $ui-dark-inactive-text-color
- &:hover
- background-color darken($ui-dark-button--hover-backgroundColor, 15%)
- &:active
- color $ui-dark-text-color
- background-color $ui-dark-button--active-backgroundColor
+ background-color $ui-dark-button--active-backgroundColor
+ border-left 1px solid $ui-dark-borderColor
+ border-top 1px solid $ui-dark-borderColor
+ .button
+ color $ui-dark-text-color
+ .deleteButton
+ color $ui-dark-text-color
.button
border none
- color $ui-dark-text-color
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
- &:hover
- color $ui-dark-text-color
- background-color $ui-dark-button--hover-backgroundColor
.input
- background-color $ui-dark-button--hover-backgroundColor
+ background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color
-
- .deleteButton
- color alpha($ui-dark-text-color, 30%)
+ transition 0.15s
body[data-theme="solarized-dark"]
.root
- color $ui-solarized-dark-text-color
- border-color $ui-dark-borderColor
- &:hover
- background-color $ui-solarized-dark-noteDetail-backgroundColor
- .deleteButton
- color $ui-solarized-dark-text-color
- &:hover
- background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%)
- &:active
- color $ui-solarized-dark-text-color
- background-color $ui-dark-button--active-backgroundColor
-
- .root--active
- color $ui-solarized-dark-text-color
border-color $ui-solarized-dark-borderColor
&:hover
background-color $ui-solarized-dark-noteDetail-backgroundColor
+ transition 0.15s
.deleteButton
- color $ui-solarized-dark-text-color
- &:hover
- background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%)
- &:active
- color $ui-solarized-dark-text-color
- background-color $ui-dark-button--active-backgroundColor
+ color $ui-solarized-dark-button--active-color
+ transition 0.15s
+ .button
+ color $ui-solarized-dark-button--active-color
+ transition 0.15s
+
+ .root--active
+ color $ui-solarized-dark-button--active-color
+ background-color $ui-solarized-dark-button-backgroundColor
+ border-color $ui-solarized-dark-borderColor
+ .deleteButton
+ color $ui-solarized-dark-button--active-color
+ .button
+ color $ui-solarized-dark-button--active-color
.button
border none
@@ -127,101 +128,75 @@ body[data-theme="solarized-dark"]
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
- &:hover
- color $ui-solarized-dark-text-color
- background-color $ui-solarized-dark-noteDetail-backgroundColor
.input
background-color $ui-solarized-dark-noteDetail-backgroundColor
- color $ui-solarized-dark-text-color
-
- .deleteButton
- color alpha($ui-solarized-dark-text-color, 30%)
+ color $ui-solarized-dark-button--active-color
+ transition 0.15s
body[data-theme="monokai"]
.root
- color $ui-monokai-text-color
- border-color $ui-dark-borderColor
- &:hover
- background-color $ui-monokai-noteDetail-backgroundColor
- .deleteButton
- color $ui-monokai-text-color
- &:hover
- background-color darken($ui-monokai-noteDetail-backgroundColor, 15%)
- &:active
- color $ui-monokai-text-color
- background-color $ui-dark-button--active-backgroundColor
-
- .root--active
- color $ui-monokai-text-color
border-color $ui-monokai-borderColor
&:hover
background-color $ui-monokai-noteDetail-backgroundColor
+ transition 0.15s
.deleteButton
color $ui-monokai-text-color
- &:hover
- background-color darken($ui-monokai-noteDetail-backgroundColor, 15%)
- &:active
- color $ui-monokai-text-color
- background-color $ui-dark-button--active-backgroundColor
+ transition 0.15s
+ .button
+ color $ui-monokai-text-color
+ transition 0.15s
+ .root--active
+ color $ui-monokai-active-color
+ background-color $ui-monokai-button-backgroundColor
+ border-color $ui-monokai-borderColor
+ .deleteButton
+ color $ui-monokai-text-color
+ .button
+ color $ui-monokai-active-color
+
.button
border none
- color $ui-monokai-text-color
+ color $ui-inactive-text-color
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
- &:hover
- color $ui-monokai-text-color
- background-color $ui-monokai-noteDetail-backgroundColor
.input
background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color
-
- .deleteButton
- color alpha($ui-monokai-text-color, 30%)
+ transition 0.15s
body[data-theme="dracula"]
.root
- color $ui-dracula-text-color
- border-color $ui-dark-borderColor
- &:hover
- background-color $ui-dracula-noteDetail-backgroundColor
- .deleteButton
- color $ui-dracula-text-color
- &:hover
- background-color darken($ui-dracula-noteDetail-backgroundColor, 15%)
- &:active
- color $ui-dracula-text-color
- background-color $ui-dark-button--active-backgroundColor
-
- .root--active
- color $ui-dracula-text-color
border-color $ui-dracula-borderColor
&:hover
background-color $ui-dracula-noteDetail-backgroundColor
+ transition 0.15s
.deleteButton
color $ui-dracula-text-color
- &:hover
- background-color darken($ui-dracula-noteDetail-backgroundColor, 15%)
- &:active
- color $ui-dracula-text-color
- background-color $ui-dark-button--active-backgroundColor
+ transition 0.15s
+ .button
+ color $ui-dracula-text-color
+ transition 0.15s
+
+ .root--active
+ color $ui-dracula-text-color
+ background-color $ui-dracula-button-backgroundColor
+ border-color $ui-dracula-borderColor
+ .deleteButton
+ color $ui-dracula-text-color
+ .button
+ color $ui-dracula-active-color
.button
border none
- color $ui-dracula-text-color
+ color $ui-inactive-text-color
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
- &:hover
- color $ui-dracula-text-color
- background-color $ui-dracula-noteDetail-backgroundColor
.input
background-color $ui-dracula-noteDetail-backgroundColor
- color $ui-dracula-text-color
-
- .deleteButton
- color alpha($ui-dracula-text-color, 30%)
\ No newline at end of file
+ color $ui-dracula-text-color
\ No newline at end of file
diff --git a/browser/components/markdown.styl b/browser/components/markdown.styl
index b7f219b8..da767a9f 100644
--- a/browser/components/markdown.styl
+++ b/browser/components/markdown.styl
@@ -55,11 +55,12 @@ body
line-height 1.6
overflow-x hidden
background-color $ui-noteDetail-backgroundColor
+ // do not allow display line breaks
+ .katex-display > .katex
+ white-space nowrap
+ // allow inline line breaks
.katex
- font 400 1.2em 'KaTeX_Main'
- line-height 1.2em
white-space initial
- text-indent 0
.katex .mfrac>.vlist>span:nth-child(2)
top 0 !important
.katex-error
@@ -183,6 +184,10 @@ ul
display list-item
&.taskListItem
list-style none
+ &>input
+ margin-left -1.6em
+ &>p
+ margin-left -1.8em
p
margin 0
&>li>ul, &>li>ol
@@ -416,6 +421,26 @@ pre.fence
canvas, svg
max-width 100% !important
+ .gallery
+ width 100%
+ height 50vh
+
+ .carousel
+ .carousel-main img
+ min-width auto
+ max-width 100%
+ min-height auto
+ max-height 100%
+
+ .carousel-footer::-webkit-scrollbar-corner
+ background-color transparent
+
+ .carousel-main, .carousel-footer
+ background-color $ui-noteDetail-backgroundColor
+ .prev, .next
+ color $ui-text-color
+ background-color $ui-tag-backgroundColor
+
themeDarkBackground = darken(#21252B, 10%)
themeDarkText = #f9f9f9
themeDarkBorder = lighten(themeDarkBackground, 20%)
@@ -475,6 +500,14 @@ body[data-theme="dark"]
border-color themeDarkBorder
background-color themeDarkPreview
+ pre.fence
+ .gallery
+ .carousel-main, .carousel-footer
+ background-color $ui-dark-noteDetail-backgroundColor
+ .prev, .next
+ color $ui-dark-text-color
+ background-color $ui-dark-tag-backgroundColor
+
themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor
themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%)
themeSolarizedDarkTableHead = themeSolarizedDarkTableEven
@@ -510,6 +543,14 @@ body[data-theme="solarized-dark"]
border-color themeDarkBorder
background-color $ui-solarized-dark-noteDetail-backgroundColor
+ pre.fence
+ .gallery
+ .carousel-main, .carousel-footer
+ background-color $ui-solarized-dark-noteDetail-backgroundColor
+ .prev, .next
+ color $ui-solarized-dark-button--active-color
+ background-color $ui-solarized-dark-button-backgroundColor
+
themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
themeMonokaiTableHead = themeMonokaiTableEven
@@ -538,6 +579,7 @@ body[data-theme="monokai"]
border-right solid 1px themeMonokaiTableBorder
kbd
background-color themeDarkBackground
+
dl
border-color themeDarkBorder
background-color themeMonokaiTableHead
@@ -547,6 +589,14 @@ body[data-theme="monokai"]
border-color themeDarkBorder
background-color $ui-monokai-noteDetail-backgroundColor
+ pre.fence
+ .gallery
+ .carousel-main, .carousel-footer
+ background-color $ui-monokai-noteDetail-backgroundColor
+ .prev, .next
+ color $ui-monokai-button--active-color
+ background-color $ui-monokai-button-backgroundColor
+
themeDraculaTableOdd = $ui-dracula-noteDetail-backgroundColor
themeDraculaTableEven = darken($ui-dracula-noteDetail-backgroundColor, 10%)
themeDraculaTableHead = themeDraculaTableEven
@@ -575,6 +625,7 @@ body[data-theme="dracula"]
border-right solid 1px themeDraculaTableBorder
kbd
background-color themeDarkBackground
+
dl
border-color themeDarkBorder
background-color themeDraculaTableHead
@@ -583,3 +634,11 @@ body[data-theme="dracula"]
dd
border-color themeDarkBorder
background-color $ui-dracula-noteDetail-backgroundColor
+
+ pre.fence
+ .gallery
+ .carousel-main, .carousel-footer
+ background-color $ui-dracula-noteDetail-backgroundColor
+ .prev, .next
+ color $ui-dracula-button--active-color
+ background-color $ui-dracula-button-backgroundColor
diff --git a/browser/lib/markdown-toc-generator.js b/browser/lib/markdown-toc-generator.js
index eae448ec..af1c833f 100644
--- a/browser/lib/markdown-toc-generator.js
+++ b/browser/lib/markdown-toc-generator.js
@@ -2,51 +2,27 @@
* @fileoverview Markdown table of contents generator
*/
+import { EOL } from 'os'
import toc from 'markdown-toc'
-import diacritics from 'diacritics-map'
-import stripColor from 'strip-color'
import mdlink from 'markdown-link'
+import slugify from './slugify'
-const EOL = require('os').EOL
+const hasProp = Object.prototype.hasOwnProperty
/**
- * @caseSensitiveSlugify Custom slugify function
- * Same implementation that the original used by markdown-toc (node_modules/markdown-toc/lib/utils.js),
- * but keeps original case to properly handle https://github.com/BoostIO/Boostnote/issues/2067
+ * From @enyaxu/markdown-it-anchor
*/
-function caseSensitiveSlugify (str) {
- function replaceDiacritics (str) {
- return str.replace(/[À-ž]/g, function (ch) {
- return diacritics[ch] || ch
- })
- }
-
- function getTitle (str) {
- if (/^\[[^\]]+\]\(/.test(str)) {
- var m = /^\[([^\]]+)\]/.exec(str)
- if (m) return m[1]
- }
- return str
- }
-
- str = getTitle(str)
- str = stripColor(str)
- // str = str.toLowerCase() //let's be case sensitive
-
- // `.split()` is often (but not always) faster than `.replace()`
- str = str.split(' ').join('-')
- str = str.split(/\t/).join('--')
- str = str.split(/<\/?[^>]+>/).join('')
- str = str.split(/[|$&`~=\\\/@+*!?({[\]})<>=.,;:'"^]/).join('')
- str = str.split(/[。?!,、;:“”【】()〔〕[]﹃﹄“ ”‘’﹁﹂—…-~《》〈〉「」]/).join('')
- str = replaceDiacritics(str)
- return str
+function uniqueSlug (slug, slugs, opts) {
+ let uniq = slug
+ let i = opts.uniqueSlugStartIndex
+ while (hasProp.call(slugs, uniq)) uniq = `${slug}-${i++}`
+ slugs[uniq] = true
+ return uniq
}
-function linkify (tok, text, slug, opts) {
- var uniqeID = opts.num === 0 ? '' : '-' + opts.num
- tok.content = mdlink(text, '#' + slug + uniqeID)
- return tok
+function linkify (token) {
+ token.content = mdlink(token.content, '#' + token.slug)
+ return token
}
const TOC_MARKER_START = ''
@@ -91,8 +67,23 @@ export function generateInEditor (editor) {
* @returns generatedTOC String containing generated TOC
*/
export function generate (markdownText) {
- const generatedToc = toc(markdownText, {slugify: caseSensitiveSlugify, linkify: linkify})
- return TOC_MARKER_START + EOL + EOL + generatedToc.content + EOL + EOL + TOC_MARKER_END
+ const slugs = {}
+ const opts = {
+ uniqueSlugStartIndex: 1
+ }
+
+ const result = toc(markdownText, {
+ slugify: title => {
+ return uniqueSlug(slugify(title), slugs, opts)
+ },
+ linkify: false
+ })
+
+ const md = toc.bullets(result.json.map(linkify), {
+ highest: result.highest
+ })
+
+ return TOC_MARKER_START + EOL + EOL + md + EOL + EOL + TOC_MARKER_END
}
function wrapTocWithEol (toc, editor) {
diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js
index a7de2abc..7dd0aa57 100644
--- a/browser/lib/markdown.js
+++ b/browser/lib/markdown.js
@@ -7,7 +7,6 @@ import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager'
import katex from 'katex'
import { lastFindInArray } from './utils'
-import anchor from '@enyaxu/markdown-it-anchor'
function createGutter (str, firstLineNumber) {
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
@@ -119,14 +118,8 @@ class Markdown {
this.md.use(require('markdown-it-imsize'))
this.md.use(require('markdown-it-footnote'))
this.md.use(require('markdown-it-multimd-table'))
- this.md.use(anchor, {
- slugify: (title) => {
- var slug = encodeURI(title.trim()
- .replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
- .replace(/\s+/g, '-'))
- .replace(/\-+$/, '')
- return slug
- }
+ this.md.use(require('@enyaxu/markdown-it-anchor'), {
+ slugify: require('./slugify')
})
this.md.use(require('markdown-it-kbd'))
this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error']})
@@ -157,6 +150,21 @@ class Markdown {
${token.content}
`
},
+ gallery: token => {
+ const content = token.content.split('\n').slice(0, -1).map(line => {
+ const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
+ if (match) {
+ return match[1]
+ } else {
+ return line
+ }
+ }).join('\n')
+
+ return `
+ ${token.fileName}
+ ${content}
+ `
+ },
mermaid: token => {
updatedOptions.onFence('mermaid')
diff --git a/browser/lib/newNote.js b/browser/lib/newNote.js
index 0b64d0e1..9511f847 100644
--- a/browser/lib/newNote.js
+++ b/browser/lib/newNote.js
@@ -18,7 +18,8 @@ export function createMarkdownNote (storage, folder, dispatch, location, params,
folder: folder,
title: '',
tags,
- content: ''
+ content: '',
+ linesHighlighted: []
})
.then(note => {
const noteHash = note.key
@@ -56,7 +57,8 @@ export function createSnippetNote (storage, folder, dispatch, location, params,
{
name: '',
mode: config.editor.snippetDefaultLanguage || 'text',
- content: ''
+ content: '',
+ linesHighlighted: []
}
]
})
diff --git a/browser/lib/slugify.js b/browser/lib/slugify.js
new file mode 100644
index 00000000..a3447a90
--- /dev/null
+++ b/browser/lib/slugify.js
@@ -0,0 +1,17 @@
+import diacritics from 'diacritics-map'
+
+function replaceDiacritics (str) {
+ return str.replace(/[À-ž]/g, function (ch) {
+ return diacritics[ch] || ch
+ })
+}
+
+module.exports = function slugify (title) {
+ let slug = title.trim()
+
+ slug = replaceDiacritics(slug)
+
+ slug = slug.replace(/[^\w\s-]/g, '').replace(/\s+/g, '-')
+
+ return encodeURI(slug).replace(/\-+$/, '')
+}
diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js
index 26c7551a..12cbbe34 100755
--- a/browser/main/Detail/MarkdownNoteDetail.js
+++ b/browser/main/Detail/MarkdownNoteDetail.js
@@ -39,12 +39,14 @@ class MarkdownNoteDetail extends React.Component {
isMovingNote: false,
note: Object.assign({
title: '',
- content: ''
+ content: '',
+ linesHighlighted: []
}, props.note),
isLockButtonShown: false,
isLocked: false,
editorType: props.config.editor.type
}
+
this.dispatchTimer = null
this.toggleLockButton = this.handleToggleLockButton.bind(this)
@@ -73,7 +75,7 @@ class MarkdownNoteDetail extends React.Component {
if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) {
if (this.saveQueue != null) this.saveNow()
this.setState({
- note: Object.assign({}, nextProps.note)
+ note: Object.assign({linesHighlighted: []}, nextProps.note)
}, () => {
this.refs.content.reload()
if (this.refs.tags) this.refs.tags.reset()
@@ -367,6 +369,7 @@ class MarkdownNoteDetail extends React.Component {
value={note.content}
storageKey={note.storage}
noteKey={note.key}
+ linesHighlighted={note.linesHighlighted}
onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
getNote={this.getNote}
@@ -378,6 +381,7 @@ class MarkdownNoteDetail extends React.Component {
value={note.content}
storageKey={note.storage}
noteKey={note.key}
+ linesHighlighted={note.linesHighlighted}
onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
getNote={this.getNote}
diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js
index 4a5076da..d8b5798d 100644
--- a/browser/main/Detail/SnippetNoteDetail.js
+++ b/browser/main/Detail/SnippetNoteDetail.js
@@ -48,7 +48,7 @@ class SnippetNoteDetail extends React.Component {
note: Object.assign({
description: ''
}, props.note, {
- snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet))
+ snippets: props.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet))
})
}
@@ -76,8 +76,9 @@ class SnippetNoteDetail extends React.Component {
const nextNote = Object.assign({
description: ''
}, nextProps.note, {
- snippets: nextProps.note.snippets.map((snippet) => Object.assign({}, snippet))
+ snippets: nextProps.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet))
})
+
this.setState({
snippetIndex: 0,
note: nextNote
@@ -410,6 +411,8 @@ class SnippetNoteDetail extends React.Component {
return (e) => {
const snippets = this.state.note.snippets.slice()
snippets[index].content = this.refs['code-' + index].value
+ snippets[index].linesHighlighted = e.options.linesHighlighted
+
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState(state => ({
note: state.note
@@ -602,7 +605,8 @@ class SnippetNoteDetail extends React.Component {
note.snippets = note.snippets.concat([{
name: '',
mode: config.editor.snippetDefaultLanguage || 'text',
- content: ''
+ content: '',
+ linesHighlighted: []
}])
const snippetIndex = note.snippets.length - 1
@@ -692,10 +696,8 @@ class SnippetNoteDetail extends React.Component {
const viewList = note.snippets.map((snippet, index) => {
const isActive = this.state.snippetIndex === index
-
let syntax = CodeMirror.findModeByName(convertModeName(snippet.mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
-
return
this.handleCodeChange(index)(e)}
ref={'code-' + index}
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
@@ -712,18 +715,24 @@ class SnippetNoteDetail extends React.Component {
: this.handleCodeChange(index)(e)}
ref={'code-' + index}
+ enableSmartPaste={config.editor.enableSmartPaste}
+ hotkey={config.hotkey}
/>
}
diff --git a/browser/main/Detail/SnippetNoteDetail.styl b/browser/main/Detail/SnippetNoteDetail.styl
index e3bb31c6..1af93645 100644
--- a/browser/main/Detail/SnippetNoteDetail.styl
+++ b/browser/main/Detail/SnippetNoteDetail.styl
@@ -31,7 +31,7 @@
.tabList
absolute left right
- top 55px
+ top 70px
height 30px
display flex
background-color $ui-noteDetail-backgroundColor
@@ -57,6 +57,9 @@
.tabList .tabButton
navWhiteButtonColor()
width 30px
+ border-left 1px solid $ui-borderColor
+ border-top 1px solid $ui-borderColor
+ border-right 1px solid $ui-borderColor
.tabView
absolute left right bottom
@@ -98,17 +101,34 @@
opacity 0
transition 0.1s
-body[data-theme="white"]
+body[data-theme="white"], body[data-theme="default"]
.root
box-shadow $note-detail-box-shadow
border none
+ .tabButton
+ &:hover
+ background-color alpha($ui-button--active-backgroundColor, 20%)
+ color $ui-text-color
+ transition 0.15s
+
body[data-theme="dark"]
.root
border-left 1px solid $ui-dark-borderColor
background-color $ui-dark-noteDetail-backgroundColor
box-shadow none
+ .tabList .tabButton
+ border-color $ui-dark-borderColor
+ &:hover
+ background-color alpha($ui-dark-button--active-backgroundColor, 20%)
+
+ .tabButton
+ &:hover
+ background-color alpha($ui-dark-button--active-backgroundColor, 20%)
+ color $ui-dark-text-color
+ transition 0.15s
+
.body
background-color $ui-dark-noteDetail-backgroundColor
@@ -118,7 +138,6 @@ body[data-theme="dark"]
border 1px solid $ui-dark-borderColor
.tabList
- background-color $ui-button--active-backgroundColor
background-color $ui-dark-noteDetail-backgroundColor
.tabList .list
@@ -150,6 +169,15 @@ body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color
border 1px solid $ui-solarized-dark-borderColor
+ .tabList .tabButton
+ border-color $ui-solarized-dark-borderColor
+
+ .tabButton
+ &:hover
+ color $ui-solarized-dark-button--active-color
+ background-color $ui-solarized-dark-noteDetail-backgroundColor
+ transition 0.15s
+
.tabList
background-color $ui-solarized-dark-noteDetail-backgroundColor
color $ui-solarized-dark-text-color
@@ -167,6 +195,14 @@ body[data-theme="monokai"]
color $ui-monokai-text-color
border 1px solid $ui-monokai-borderColor
+ .tabList .tabButton
+ border-color $ui-monokai-borderColor
+
+ .tabButton
+ &:hover
+ color $ui-monokai-text-color
+ background-color $ui-monokai-noteDetail-backgroundColor
+
.tabList
background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color
@@ -184,6 +220,14 @@ body[data-theme="dracula"]
color $ui-dracula-text-color
border 1px solid $ui-dracula-borderColor
+ .tabList .tabButton
+ border-color $ui-dracula-borderColor
+
+ .tabButton
+ &:hover
+ color $ui-dracula-text-color
+ background-color $ui-dracula-noteDetail-backgroundColor
+
.tabList
background-color $ui-dracula-noteDetail-backgroundColor
color $ui-dracula-text-color
\ No newline at end of file
diff --git a/browser/main/Detail/TagSelect.js b/browser/main/Detail/TagSelect.js
index 6ced475b..c5221f66 100644
--- a/browser/main/Detail/TagSelect.js
+++ b/browser/main/Detail/TagSelect.js
@@ -45,8 +45,14 @@ class TagSelect extends React.Component {
value = _.isArray(value)
? value.slice()
: []
- value.push(newTag)
- value = _.uniq(value)
+
+ if (!_.includes(value, newTag)) {
+ value.push(newTag)
+ }
+
+ if (this.props.saveTagsAlphabetically) {
+ value = _.sortBy(value)
+ }
this.setState({
newTag: ''
diff --git a/browser/main/Main.js b/browser/main/Main.js
index c426f2bd..556c5daf 100644
--- a/browser/main/Main.js
+++ b/browser/main/Main.js
@@ -96,12 +96,14 @@ class Main extends React.Component {
{
name: 'example.html',
mode: 'html',
- content: "\n\n
Enjoy Boostnote!
\n\n"
+ content: "\n\n
Enjoy Boostnote!
\n\n",
+ linesHighlighted: []
},
{
name: 'example.js',
mode: 'javascript',
- content: "var boostnote = document.getElementById('enjoy').innerHTML\n\nconsole.log(boostnote)"
+ content: "var boostnote = document.getElementById('enjoy').innerHTML\n\nconsole.log(boostnote)",
+ linesHighlighted: []
}
]
})
@@ -234,8 +236,8 @@ class Main extends React.Component {
if (this.state.isRightSliderFocused) {
const offset = this.refs.body.getBoundingClientRect().left
let newListWidth = e.pageX - offset
- if (newListWidth < 10) {
- newListWidth = 10
+ if (newListWidth < 180) {
+ newListWidth = 180
} else if (newListWidth > 600) {
newListWidth = 600
}
diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js
index 78748a7f..23b3684a 100644
--- a/browser/main/NoteList/index.js
+++ b/browser/main/NoteList/index.js
@@ -2,7 +2,6 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
-import debounceRender from 'react-debounce-render'
import styles from './NoteList.styl'
import moment from 'moment'
import _ from 'lodash'
@@ -766,7 +765,8 @@ class NoteList extends React.Component {
type: firstNote.type,
folder: folder.key,
title: firstNote.title + ' ' + i18n.__('copy'),
- content: firstNote.content
+ content: firstNote.content,
+ linesHighlighted: firstNote.linesHighlighted
})
.then((note) => {
attachmentManagement.cloneAttachments(firstNote, note)
@@ -1184,4 +1184,4 @@ NoteList.propTypes = {
})
}
-export default debounceRender(CSSModules(NoteList, styles))
+export default CSSModules(NoteList, styles)
diff --git a/browser/main/TopBar/index.js b/browser/main/TopBar/index.js
index a5687ecb..91256daf 100644
--- a/browser/main/TopBar/index.js
+++ b/browser/main/TopBar/index.js
@@ -6,6 +6,7 @@ import _ from 'lodash'
import ee from 'browser/main/lib/eventEmitter'
import NewNoteButton from 'browser/main/NewNoteButton'
import i18n from 'browser/lib/i18n'
+import debounce from 'lodash/debounce'
class TopBar extends React.Component {
constructor (props) {
@@ -25,6 +26,10 @@ class TopBar extends React.Component {
}
this.codeInitHandler = this.handleCodeInit.bind(this)
+
+ this.updateKeyword = debounce(this.updateKeyword, 1000 / 60, {
+ maxWait: 1000 / 8
+ })
}
componentDidMount () {
@@ -94,7 +99,6 @@ class TopBar extends React.Component {
}
handleKeyUp (e) {
- const { router } = this.context
// reset states
this.setState({
isConfirmTranslation: false
@@ -106,21 +110,21 @@ class TopBar extends React.Component {
isConfirmTranslation: true
})
const keyword = this.refs.searchInput.value
- router.push(`/searched/${encodeURIComponent(keyword)}`)
- this.setState({
- search: keyword
- })
+ this.updateKeyword(keyword)
}
}
handleSearchChange (e) {
- const { router } = this.context
- const keyword = this.refs.searchInput.value
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
- router.push(`/searched/${encodeURIComponent(keyword)}`)
+ const keyword = this.refs.searchInput.value
+ this.updateKeyword(keyword)
} else {
e.preventDefault()
}
+ }
+
+ updateKeyword (keyword) {
+ this.context.router.push(`/searched/${encodeURIComponent(keyword)}`)
this.setState({
search: keyword
})
diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js
index 2521a8fd..970f4ce5 100644
--- a/browser/main/lib/ConfigManager.js
+++ b/browser/main/lib/ConfigManager.js
@@ -25,7 +25,8 @@ export const DEFAULT_CONFIG = {
hotkey: {
toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M',
- deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace'
+ deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace',
+ pasteSmartly: OSX ? 'Command + Shift + V' : 'Ctrl + Shift + V'
},
ui: {
language: 'en',
@@ -44,6 +45,9 @@ export const DEFAULT_CONFIG = {
enableRulers: false,
rulers: [80, 120],
displayLineNumbers: true,
+ matchingPairs: '()[]{}\'\'""$$**``',
+ matchingTriples: '```"""\'\'\'',
+ explodingPairs: '[]{}``$$',
switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK'
delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE'
scrollPastEnd: false,
@@ -52,7 +56,8 @@ export const DEFAULT_CONFIG = {
enableTableEditor: false,
enableFrontMatterTitle: true,
frontMatterTitleField: 'title',
- spellcheck: false
+ spellcheck: false,
+ enableSmartPaste: false
},
preview: {
fontSize: '14',
diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js
index 4eec7ca6..c9f096f8 100644
--- a/browser/main/lib/dataApi/attachmentManagement.js
+++ b/browser/main/lib/dataApi/attachmentManagement.js
@@ -227,7 +227,15 @@ function migrateAttachments (markdownContent, storagePath, noteKey) {
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
*/
function fixLocalURLS (renderedHTML, storagePath) {
- return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '.*?"', 'g'), function (match) {
+ /*
+ A :storage reference is like `:storage/3b6f8bd6-4edd-4b15-96e0-eadc4475b564/f939b2c3.jpg`.
+
+ - `STORAGE_FOLDER_PLACEHOLDER` will match `:storage`
+ - `(?:(?:\\\/|%5C)[\\w.]+)+` will match `/3b6f8bd6-4edd-4b15-96e0-eadc4475b564/f939b2c3.jpg`
+ - `(?:\\\/|%5C)[\\w.]+` will either match `/3b6f8bd6-4edd-4b15-96e0-eadc4475b564` or `/f939b2c3.jpg`
+ - `(?:\\\/|%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) {
var encodedPathSeparators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g')
return match.replace(encodedPathSeparators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
})
@@ -316,6 +324,44 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
reader.readAsDataURL(blob)
}
+/**
+ * @description Creates a new file in the storage folder belonging to the current note and inserts the correct markdown code
+ * @param {CodeEditor} codeEditor Markdown editor. Its insertAttachmentMd() method will be called to include the markdown code
+ * @param {String} storageKey Key of the current storage
+ * @param {String} noteKey Key of the current note
+ * @param {NativeImage} image The native image
+ */
+function handlePastNativeImage (codeEditor, storageKey, noteKey, image) {
+ if (!codeEditor) {
+ throw new Error('codeEditor has to be given')
+ }
+ if (!storageKey) {
+ throw new Error('storageKey has to be given')
+ }
+
+ if (!noteKey) {
+ throw new Error('noteKey has to be given')
+ }
+ if (!image) {
+ throw new Error('image has to be given')
+ }
+
+ const targetStorage = findStorage.findStorage(storageKey)
+ const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
+
+ createAttachmentDestinationFolder(targetStorage.path, noteKey)
+
+ const imageName = `${uniqueSlug()}.png`
+ const imagePath = path.join(destinationDir, imageName)
+
+ const binaryData = image.toPNG()
+ fs.writeFileSync(imagePath, binaryData, 'binary')
+
+ const imageReferencePath = path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, imageName)
+ const imageMd = generateAttachmentMarkdown(imageName, imageReferencePath, true)
+ codeEditor.insertAttachmentMd(imageMd)
+}
+
/**
* @description Returns all attachment paths of the given markdown
* @param {String} markdownContent content in which the attachment paths should be found
@@ -550,6 +596,7 @@ module.exports = {
generateAttachmentMarkdown,
handleAttachmentDrop,
handlePastImageEvent,
+ handlePastNativeImage,
getAttachmentsInMarkdownContent,
getAbsolutePathsOfAttachmentsInContent,
removeStorageAndNoteReferences,
diff --git a/browser/main/lib/dataApi/createNote.js b/browser/main/lib/dataApi/createNote.js
index e5d44489..5bfa2457 100644
--- a/browser/main/lib/dataApi/createNote.js
+++ b/browser/main/lib/dataApi/createNote.js
@@ -16,6 +16,7 @@ function validateInput (input) {
switch (input.type) {
case 'MARKDOWN_NOTE':
if (!_.isString(input.content)) input.content = ''
+ if (!_.isArray(input.linesHighlighted)) input.linesHighlighted = []
break
case 'SNIPPET_NOTE':
if (!_.isString(input.description)) input.description = ''
@@ -23,7 +24,8 @@ function validateInput (input) {
input.snippets = [{
name: '',
mode: 'text',
- content: ''
+ content: '',
+ linesHighlighted: []
}]
}
break
diff --git a/browser/main/lib/dataApi/createSnippet.js b/browser/main/lib/dataApi/createSnippet.js
index 5d189217..2e585c9f 100644
--- a/browser/main/lib/dataApi/createSnippet.js
+++ b/browser/main/lib/dataApi/createSnippet.js
@@ -9,7 +9,8 @@ function createSnippet (snippetFile) {
id: crypto.randomBytes(16).toString('hex'),
name: 'Unnamed snippet',
prefix: [],
- content: ''
+ content: '',
+ linesHighlighted: []
}
fetchSnippet(null, snippetFile).then((snippets) => {
snippets.push(newSnippet)
diff --git a/browser/main/lib/dataApi/migrateFromV5Storage.js b/browser/main/lib/dataApi/migrateFromV5Storage.js
index b11e66e9..78d78746 100644
--- a/browser/main/lib/dataApi/migrateFromV5Storage.js
+++ b/browser/main/lib/dataApi/migrateFromV5Storage.js
@@ -69,7 +69,8 @@ function importAll (storage, data) {
isStarred: false,
title: article.title,
content: '# ' + article.title + '\n\n' + article.content,
- key: noteKey
+ key: noteKey,
+ linesHighlighted: article.linesHighlighted
}
notes.push(newNote)
} else {
@@ -87,7 +88,8 @@ function importAll (storage, data) {
snippets: [{
name: article.mode,
mode: article.mode,
- content: article.content
+ content: article.content,
+ linesHighlighted: article.linesHighlighted
}]
}
notes.push(newNote)
diff --git a/browser/main/lib/dataApi/updateNote.js b/browser/main/lib/dataApi/updateNote.js
index 147fbc06..ce9fabcf 100644
--- a/browser/main/lib/dataApi/updateNote.js
+++ b/browser/main/lib/dataApi/updateNote.js
@@ -39,6 +39,9 @@ function validateInput (input) {
if (input.content != null) {
if (!_.isString(input.content)) validatedInput.content = ''
else validatedInput.content = input.content
+
+ if (!_.isArray(input.linesHighlighted)) validatedInput.linesHighlighted = []
+ else validatedInput.linesHighlighted = input.linesHighlighted
}
return validatedInput
case 'SNIPPET_NOTE':
@@ -51,7 +54,8 @@ function validateInput (input) {
validatedInput.snippets = [{
name: '',
mode: 'text',
- content: ''
+ content: '',
+ linesHighlighted: []
}]
} else {
validatedInput.snippets = input.snippets
@@ -96,12 +100,14 @@ function updateNote (storageKey, noteKey, input) {
snippets: [{
name: '',
mode: 'text',
- content: ''
+ content: '',
+ linesHighlighted: []
}]
}
: {
type: 'MARKDOWN_NOTE',
- content: ''
+ content: '',
+ linesHighlighted: []
}
noteData.title = ''
if (storage.folders.length === 0) throw new Error('Failed to restore note: No folder exists.')
diff --git a/browser/main/lib/dataApi/updateSnippet.js b/browser/main/lib/dataApi/updateSnippet.js
index f2310b8e..f132d83f 100644
--- a/browser/main/lib/dataApi/updateSnippet.js
+++ b/browser/main/lib/dataApi/updateSnippet.js
@@ -12,7 +12,8 @@ function updateSnippet (snippet, snippetFile) {
if (
currentSnippet.name === snippet.name &&
currentSnippet.prefix === snippet.prefix &&
- currentSnippet.content === snippet.content
+ currentSnippet.content === snippet.content &&
+ currentSnippet.linesHighlighted === snippet.linesHighlighted
) {
// if everything is the same then don't write to disk
resolve(snippets)
@@ -20,6 +21,7 @@ function updateSnippet (snippet, snippetFile) {
currentSnippet.name = snippet.name
currentSnippet.prefix = snippet.prefix
currentSnippet.content = snippet.content
+ currentSnippet.linesHighlighted = (snippet.linesHighlighted)
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
if (err) reject(err)
resolve(snippets)
diff --git a/browser/main/modals/PreferencesModal/HotkeyTab.js b/browser/main/modals/PreferencesModal/HotkeyTab.js
index 7ad6f606..25098faa 100644
--- a/browser/main/modals/PreferencesModal/HotkeyTab.js
+++ b/browser/main/modals/PreferencesModal/HotkeyTab.js
@@ -79,7 +79,8 @@ class HotkeyTab extends React.Component {
config.hotkey = {
toggleMain: this.refs.toggleMain.value,
toggleMode: this.refs.toggleMode.value,
- deleteNote: this.refs.deleteNote.value
+ deleteNote: this.refs.deleteNote.value,
+ pasteSmartly: this.refs.pasteSmartly.value
}
this.setState({
config
@@ -149,6 +150,17 @@ class HotkeyTab extends React.Component {
/>
+
{i18n.__('Code Block Theme')}
diff --git a/browser/styles/index.styl b/browser/styles/index.styl
index 56cb0eab..b9f9c41e 100644
--- a/browser/styles/index.styl
+++ b/browser/styles/index.styl
@@ -240,10 +240,8 @@ navWhiteButtonColor()
&:hover
background-color alpha($ui-button--active-backgroundColor, 20%)
transition 0.15s
- color $ui-text-color
&:active, &:active:hover
background-color $ui-button--active-backgroundColor
- color $ui-text-color
transition 0.15s
// UI Button
diff --git a/package.json b/package.json
index 128a21af..f12360a0 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "boost",
"productName": "Boostnote",
- "version": "0.11.11",
+ "version": "0.11.12",
"main": "index.js",
"description": "Boostnote",
"license": "GPL-3.0",
@@ -99,8 +99,10 @@
"react-codemirror": "^0.3.0",
"react-debounce-render": "^4.0.1",
"react-dom": "^15.0.2",
+ "react-image-carousel": "^2.0.18",
"react-redux": "^4.4.5",
"react-sortable-hoc": "^0.6.7",
+ "react-transition-group": "^2.5.0",
"redux": "^3.5.2",
"sander": "^0.5.1",
"sanitize-html": "^1.18.2",
@@ -142,6 +144,7 @@
"grunt": "^0.4.5",
"grunt-electron-installer": "2.1.0",
"history": "^1.17.0",
+ "husky": "^1.1.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^22.4.3",
"jest-localstorage-mock": "^2.2.0",
@@ -189,5 +192,10 @@
"/tests/jest.js",
"jest-localstorage-mock"
]
+ },
+ "husky": {
+ "hooks": {
+ "pre-commit": "npm run lint"
+ }
}
}
diff --git a/readme.md b/readme.md
index ef90dfcc..aba9b92a 100644
--- a/readme.md
+++ b/readme.md
@@ -27,7 +27,7 @@ Boostnote is an open source project. It's an independent project with its ongoin
Issues on Boostnote can be funded by anyone and the money will be distributed to contributors and maintainers. If you use Boostnote please consider becoming a backer:
-[](https://issuehunt.io/repos/53266139)
+[](https://issuehunt.io/repos/53266139)
## Community
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
diff --git a/tests/dataApi/createNote-test.js b/tests/dataApi/createNote-test.js
index 47446aab..3606dfd4 100644
--- a/tests/dataApi/createNote-test.js
+++ b/tests/dataApi/createNote-test.js
@@ -25,13 +25,16 @@ test.serial('Create a note', (t) => {
const storageKey = t.context.storage.cache.key
const folderKey = t.context.storage.json.folders[0].key
+ const randLinesHighlightedArray = new Array(10).fill().map(() => Math.round(Math.random() * 10))
+
const input1 = {
type: 'SNIPPET_NOTE',
description: faker.lorem.lines(),
snippets: [{
name: faker.system.fileName(),
mode: 'text',
- content: faker.lorem.lines()
+ content: faker.lorem.lines(),
+ linesHighlighted: randLinesHighlightedArray
}],
tags: faker.lorem.words().split(' '),
folder: folderKey
@@ -42,7 +45,8 @@ test.serial('Create a note', (t) => {
type: 'MARKDOWN_NOTE',
content: faker.lorem.lines(),
tags: faker.lorem.words().split(' '),
- folder: folderKey
+ folder: folderKey,
+ linesHighlighted: randLinesHighlightedArray
}
input2.title = input2.content.split('\n').shift()
@@ -59,6 +63,7 @@ test.serial('Create a note', (t) => {
t.is(storageKey, data1.storage)
const jsonData1 = CSON.readFileSync(path.join(storagePath, 'notes', data1.key + '.cson'))
+
t.is(input1.title, data1.title)
t.is(input1.title, jsonData1.title)
t.is(input1.description, data1.description)
@@ -71,6 +76,8 @@ test.serial('Create a note', (t) => {
t.is(input1.snippets[0].content, jsonData1.snippets[0].content)
t.is(input1.snippets[0].name, data1.snippets[0].name)
t.is(input1.snippets[0].name, jsonData1.snippets[0].name)
+ t.deepEqual(input1.snippets[0].linesHighlighted, data1.snippets[0].linesHighlighted)
+ t.deepEqual(input1.snippets[0].linesHighlighted, jsonData1.snippets[0].linesHighlighted)
t.is(storageKey, data2.storage)
const jsonData2 = CSON.readFileSync(path.join(storagePath, 'notes', data2.key + '.cson'))
@@ -80,6 +87,8 @@ test.serial('Create a note', (t) => {
t.is(input2.content, jsonData2.content)
t.is(input2.tags.length, data2.tags.length)
t.is(input2.tags.length, jsonData2.tags.length)
+ t.deepEqual(input2.linesHighlighted, data2.linesHighlighted)
+ t.deepEqual(input2.linesHighlighted, jsonData2.linesHighlighted)
})
})
diff --git a/tests/dataApi/createSnippet-test.js b/tests/dataApi/createSnippet-test.js
index f06cf861..638b76ca 100644
--- a/tests/dataApi/createSnippet-test.js
+++ b/tests/dataApi/createSnippet-test.js
@@ -26,6 +26,7 @@ test.serial('Create a snippet', (t) => {
t.is(snippet.name, data.name)
t.deepEqual(snippet.prefix, data.prefix)
t.is(snippet.content, data.content)
+ t.deepEqual(snippet.linesHighlighted, data.linesHighlighted)
})
})
diff --git a/tests/dataApi/updateNote-test.js b/tests/dataApi/updateNote-test.js
index 6043ee1e..da47c30c 100644
--- a/tests/dataApi/updateNote-test.js
+++ b/tests/dataApi/updateNote-test.js
@@ -26,13 +26,17 @@ test.serial('Update a note', (t) => {
const storageKey = t.context.storage.cache.key
const folderKey = t.context.storage.json.folders[0].key
+ const randLinesHighlightedArray = new Array(10).fill().map(() => Math.round(Math.random() * 10))
+ const randLinesHighlightedArray2 = new Array(15).fill().map(() => Math.round(Math.random() * 15))
+
const input1 = {
type: 'SNIPPET_NOTE',
description: faker.lorem.lines(),
snippets: [{
name: faker.system.fileName(),
mode: 'text',
- content: faker.lorem.lines()
+ content: faker.lorem.lines(),
+ linesHighlighted: randLinesHighlightedArray
}],
tags: faker.lorem.words().split(' '),
folder: folderKey
@@ -43,7 +47,8 @@ test.serial('Update a note', (t) => {
type: 'MARKDOWN_NOTE',
content: faker.lorem.lines(),
tags: faker.lorem.words().split(' '),
- folder: folderKey
+ folder: folderKey,
+ linesHighlighted: randLinesHighlightedArray
}
input2.title = input2.content.split('\n').shift()
@@ -53,7 +58,8 @@ test.serial('Update a note', (t) => {
snippets: [{
name: faker.system.fileName(),
mode: 'text',
- content: faker.lorem.lines()
+ content: faker.lorem.lines(),
+ linesHighlighted: randLinesHighlightedArray2
}],
tags: faker.lorem.words().split(' ')
}
@@ -62,7 +68,8 @@ test.serial('Update a note', (t) => {
const input4 = {
type: 'MARKDOWN_NOTE',
content: faker.lorem.lines(),
- tags: faker.lorem.words().split(' ')
+ tags: faker.lorem.words().split(' '),
+ linesHighlighted: randLinesHighlightedArray2
}
input4.title = input4.content.split('\n').shift()
@@ -99,6 +106,8 @@ test.serial('Update a note', (t) => {
t.is(input3.snippets[0].content, jsonData1.snippets[0].content)
t.is(input3.snippets[0].name, data1.snippets[0].name)
t.is(input3.snippets[0].name, jsonData1.snippets[0].name)
+ t.deepEqual(input3.snippets[0].linesHighlighted, data1.snippets[0].linesHighlighted)
+ t.deepEqual(input3.snippets[0].linesHighlighted, jsonData1.snippets[0].linesHighlighted)
const jsonData2 = CSON.readFileSync(path.join(storagePath, 'notes', data2.key + '.cson'))
t.is(input4.title, data2.title)
@@ -107,6 +116,8 @@ test.serial('Update a note', (t) => {
t.is(input4.content, jsonData2.content)
t.is(input4.tags.length, data2.tags.length)
t.is(input4.tags.length, jsonData2.tags.length)
+ t.deepEqual(input4.linesHighlighted, data2.linesHighlighted)
+ t.deepEqual(input4.linesHighlighted, jsonData2.linesHighlighted)
})
})
diff --git a/yarn.lock b/yarn.lock
index 48dd9056..604880e5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -61,7 +61,6 @@
"@enyaxu/markdown-it-anchor@^5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@enyaxu/markdown-it-anchor/-/markdown-it-anchor-5.0.2.tgz#d173f7b60b492aabc17dfba864c4d071f5595f72"
- integrity sha512-HBQ+by3IFHh2i5nw8fzn9qrdA+6uwzre68EzHpBX/WrwgnKrfvckPzdi7MphKp2C617edfpeibucslHDNPYkvQ==
"@ladjs/time-require@^0.1.4":
version "0.1.4"
@@ -1690,6 +1689,10 @@ ci-info@^1.0.0:
version "1.1.3"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2"
+ci-info@^1.5.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
+
circular-json@^0.3.1:
version "0.3.3"
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
@@ -2107,6 +2110,14 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+cosmiconfig@^5.0.6:
+ version "5.0.6"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.6.tgz#dca6cf680a0bd03589aff684700858c81abeeb39"
+ dependencies:
+ is-directory "^0.3.1"
+ js-yaml "^3.9.0"
+ parse-json "^4.0.0"
+
create-error-class@^3.0.0, create-error-class@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
@@ -2749,6 +2760,10 @@ doctrine@^2.0.0, doctrine@^2.0.2:
dependencies:
esutils "^2.0.2"
+dom-helpers@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6"
+
dom-serializer@0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
@@ -3364,6 +3379,18 @@ execa@^0.7.0:
signal-exit "^3.0.0"
strip-eof "^1.0.0"
+execa@^0.9.0:
+ version "0.9.0"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-0.9.0.tgz#adb7ce62cf985071f60580deb4a88b9e34712d01"
+ dependencies:
+ cross-spawn "^5.0.1"
+ get-stream "^3.0.0"
+ is-stream "^1.1.0"
+ npm-run-path "^2.0.0"
+ p-finally "^1.0.0"
+ signal-exit "^3.0.0"
+ strip-eof "^1.0.0"
+
exit-hook@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
@@ -3670,6 +3697,12 @@ find-up@^2.0.0, find-up@^2.1.0:
dependencies:
locate-path "^2.0.0"
+find-up@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
+ dependencies:
+ locate-path "^3.0.0"
+
findup-sync@~0.1.2:
version "0.1.3"
resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.1.3.tgz#7f3e7a97b82392c653bf06589bd85190e93c3683"
@@ -3915,6 +3948,10 @@ get-stdin@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
+get-stdin@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
+
get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
@@ -4452,6 +4489,21 @@ humanize-plus@^1.8.1:
version "1.8.2"
resolved "https://registry.yarnpkg.com/humanize-plus/-/humanize-plus-1.8.2.tgz#a65b34459ad6367adbb3707a82a3c9f916167030"
+husky@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/husky/-/husky-1.1.0.tgz#7271e85f5d98b54349788839b720c9a60cd95dba"
+ dependencies:
+ cosmiconfig "^5.0.6"
+ execa "^0.9.0"
+ find-up "^3.0.0"
+ get-stdin "^6.0.0"
+ is-ci "^1.2.1"
+ pkg-dir "^3.0.0"
+ please-upgrade-node "^3.1.1"
+ read-pkg "^4.0.1"
+ run-node "^1.0.0"
+ slash "^2.0.0"
+
i18n-2@^0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/i18n-2/-/i18n-2-0.7.2.tgz#7efb1a7adc67869adea0688951577464aa793aaf"
@@ -4662,6 +4714,12 @@ is-ci@^1.0.10, is-ci@^1.0.7:
dependencies:
ci-info "^1.0.0"
+is-ci@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c"
+ dependencies:
+ ci-info "^1.5.0"
+
is-data-descriptor@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@@ -4694,6 +4752,10 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2:
is-data-descriptor "^1.0.0"
kind-of "^6.0.2"
+is-directory@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
+
is-dotfile@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
@@ -5324,6 +5386,10 @@ js-tokens@^3.0.0, js-tokens@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
+"js-tokens@^3.0.0 || ^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+
js-yaml@^3.10.0, js-yaml@^3.5.1, js-yaml@^3.7.0:
version "3.11.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef"
@@ -5331,7 +5397,7 @@ js-yaml@^3.10.0, js-yaml@^3.5.1, js-yaml@^3.7.0:
argparse "^1.0.7"
esprima "^4.0.0"
-js-yaml@^3.12.0, js-yaml@^3.8.1:
+js-yaml@^3.12.0, js-yaml@^3.8.1, js-yaml@^3.9.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
dependencies:
@@ -5688,6 +5754,13 @@ locate-path@^2.0.0:
p-locate "^2.0.0"
path-exists "^3.0.0"
+locate-path@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
+ dependencies:
+ p-locate "^3.0.0"
+ path-exists "^3.0.0"
+
lodash-es@^4.2.1:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05"
@@ -5805,6 +5878,12 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3
dependencies:
js-tokens "^3.0.0"
+loose-envify@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+ dependencies:
+ js-tokens "^3.0.0 || ^4.0.0"
+
loud-rejection@^1.0.0, loud-rejection@^1.2.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
@@ -6681,16 +6760,32 @@ p-limit@^1.1.0:
dependencies:
p-try "^1.0.0"
+p-limit@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.0.0.tgz#e624ed54ee8c460a778b3c9f3670496ff8a57aec"
+ dependencies:
+ p-try "^2.0.0"
+
p-locate@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
dependencies:
p-limit "^1.1.0"
+p-locate@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
+ dependencies:
+ p-limit "^2.0.0"
+
p-try@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
+p-try@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1"
+
package-hash@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-1.2.0.tgz#003e56cd57b736a6ed6114cc2b81542672770e44"
@@ -6886,12 +6981,24 @@ pkg-dir@^2.0.0:
dependencies:
find-up "^2.1.0"
+pkg-dir@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
+ dependencies:
+ find-up "^3.0.0"
+
pkg-up@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f"
dependencies:
find-up "^2.1.0"
+please-upgrade-node@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz#ed320051dfcc5024fae696712c8288993595e8ac"
+ dependencies:
+ semver-compare "^1.0.0"
+
plist@^2.0.0, plist@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/plist/-/plist-2.1.0.tgz#57ccdb7a0821df21831217a3cad54e3e146a1025"
@@ -7241,6 +7348,13 @@ prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.5.8,
loose-envify "^1.3.1"
object-assign "^4.1.1"
+prop-types@^15.6.2:
+ version "15.6.2"
+ resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
+ dependencies:
+ loose-envify "^1.3.1"
+ object-assign "^4.1.1"
+
proxy-addr@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
@@ -7420,6 +7534,10 @@ react-dom@^15.0.2:
object-assign "^4.1.0"
prop-types "^15.5.10"
+react-image-carousel@^2.0.18:
+ version "2.0.18"
+ resolved "https://registry.yarnpkg.com/react-image-carousel/-/react-image-carousel-2.0.18.tgz#5868ea09bd9cca09c4467d3d02695cd4e7792f28"
+
react-input-autosize@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-1.2.0.tgz#87241071159f742123897691da6796ec33b57d05"
@@ -7427,6 +7545,10 @@ react-input-autosize@^1.1.0:
create-react-class "^15.5.2"
prop-types "^15.5.8"
+react-lifecycles-compat@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
+
react-proxy@^1.1.7:
version "1.1.8"
resolved "https://registry.yarnpkg.com/react-proxy/-/react-proxy-1.1.8.tgz#9dbfd9d927528c3aa9f444e4558c37830ab8c26a"
@@ -7492,6 +7614,15 @@ react-transform-hmr@^1.0.3:
global "^4.3.0"
react-proxy "^1.1.7"
+react-transition-group@^2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.5.0.tgz#70bca0e3546102c4dc5cf3f5f57f73447cce6874"
+ dependencies:
+ dom-helpers "^3.3.1"
+ loose-envify "^1.4.0"
+ prop-types "^15.6.2"
+ react-lifecycles-compat "^3.0.4"
+
react@^15.5.4:
version "15.6.2"
resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72"
@@ -7545,6 +7676,14 @@ read-pkg@^2.0.0:
normalize-package-data "^2.3.2"
path-type "^2.0.0"
+read-pkg@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-4.0.1.tgz#963625378f3e1c4d48c85872b5a6ec7d5d093237"
+ dependencies:
+ normalize-package-data "^2.3.2"
+ parse-json "^4.0.0"
+ pify "^3.0.0"
+
readable-stream@^1.1.8, readable-stream@~1.1.9:
version "1.1.14"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
@@ -7895,6 +8034,10 @@ run-async@^0.1.0:
dependencies:
once "^1.3.0"
+run-node@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/run-node/-/run-node-1.0.0.tgz#46b50b946a2aa2d4947ae1d886e9856fd9cabe5e"
+
run-parallel@^1.1.2:
version "1.1.9"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679"
@@ -7994,6 +8137,10 @@ section-iterator@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/section-iterator/-/section-iterator-2.0.0.tgz#bf444d7afeeb94ad43c39ad2fb26151627ccba2a"
+semver-compare@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
+
semver-diff@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
@@ -8153,6 +8300,10 @@ slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
+slash@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
+
slice-ansi@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"