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 @@
-
Sponsors & Backers
-
-Boostnote is an open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome backers. If you'd like to join them, please consider:
-
-- [Become a backer or sponsor on Open Collective.](https://opencollective.com/boostnoteio)
-
----
-
-## Backers via OpenCollective
-
-### [Gold Sponsors / $1,000 per month](https://opencollective.com/boostnoteio/order/2259)
-- Get your logo on our Readme.md on GitHub and the frontpage of https://boostnote.io/.
-
-### [Silver Sponsors / $250 per month](https://opencollective.com/boostnoteio/order/2257)
-- Get your logo on our Readme.md on GitHub and the frontpage of https://boostnote.io/.
-
-### [Bronze Sponsors / $50 per month](https://opencollective.com/boostnoteio/order/2258)
-- Get your name and Url (or E-mail) on Readme.md on GitHub.
-
-### [Backers3 / $10 per month](https://opencollective.com/boostnoteio/order/2176)
-- [Ralph03](https://opencollective.com/ralph03)
-
-- [Nikolas Dan](https://opencollective.com/nikolas-dan)
-
-### [Backers2 / $5 per month](https://opencollective.com/boostnoteio/order/2175)
-- [Yeojong Kim](https://twitter.com/yeojoy)
-
-- [Scotia Draven](https://opencollective.com/scotia-draven)
-
-- [A. J. Vargas](https://opencollective.com/aj-vargas)
-
-### [Backers1](https://opencollective.com/boostnoteio/order/2563) and One-time sponsors
-- Ryosuke Tamura - $30
-
-- tatoosh11 - $10
-
-- Alexander Borovkov - $10
-
-- spoonhoop - $5
-
-- Drew Williams - $2
-
-- Andy Shaw - $2
-
-- mysafesky -$2
-
----
-
-## Backers via Bountysource
-https://salt.bountysource.com/teams/boostnote
-
-- Kuzz - $65
-
-- Intense Raiden - $45
-
-- ravy22 - $25
-
-- trentpolack - $20
-
-- hikariru - $10
-
-- kolchan11 - $10
-
-- RonWalker22 - $10
-
-- hocchuc - $5
-
-- Adam - $5
-
-- Steve - $5
-
-- evmin - $5
diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js
index 7719ed90..aa380e38 100644
--- a/browser/components/CodeEditor.js
+++ b/browser/components/CodeEditor.js
@@ -38,6 +38,7 @@ export default class CodeEditor extends React.Component {
trailing: true
})
this.changeHandler = (editor, changeObject) => this.handleChange(editor, changeObject)
+ this.highlightHandler = (editor, changeObject) => this.handleHighlight(editor, changeObject)
this.focusHandler = () => {
ipcRenderer.send('editor:focused', true)
}
@@ -177,6 +178,9 @@ export default class CodeEditor extends React.Component {
}
}
},
+ 'Cmd-Left': function (cm) {
+ cm.execCommand('goLineLeft')
+ },
'Cmd-T': function (cm) {
// Do nothing
},
@@ -235,6 +239,7 @@ export default class CodeEditor extends React.Component {
this.editor = CodeMirror(this.refs.root, {
rulers: buildCMRulers(rulers, enableRulers),
value: this.props.value,
+ linesHighlighted: this.props.linesHighlighted,
lineNumbers: this.props.displayLineNumbers,
lineWrapping: true,
theme: this.props.theme,
@@ -261,6 +266,7 @@ export default class CodeEditor extends React.Component {
this.editor.on('focus', this.focusHandler)
this.editor.on('blur', this.blurHandler)
this.editor.on('change', this.changeHandler)
+ this.editor.on('gutterClick', this.highlightHandler)
this.editor.on('paste', this.pasteHandler)
if (this.props.switchPreview !== 'RIGHTCLICK') {
this.editor.on('contextmenu', this.contextMenuHandler)
@@ -339,6 +345,8 @@ export default class CodeEditor extends React.Component {
this.setState({
clientWidth: this.refs.root.clientWidth
})
+
+ this.initialHighlighting()
}
expandSnippet (line, cursor, cm, snippets) {
@@ -537,12 +545,96 @@ export default class CodeEditor extends React.Component {
handleChange (editor, changeObject) {
spellcheck.handleChange(editor, changeObject)
+
+ this.updateHighlight(editor, changeObject)
+
this.value = editor.getValue()
if (this.props.onChange) {
this.props.onChange(editor)
}
}
+ incrementLines (start, linesAdded, linesRemoved, editor) {
+ let highlightedLines = editor.options.linesHighlighted
+
+ const totalHighlightedLines = highlightedLines.length
+
+ let offset = linesAdded - linesRemoved
+
+ // Store new items to be added as we're changing the lines
+ let newLines = []
+
+ let i = totalHighlightedLines
+
+ while (i--) {
+ const lineNumber = highlightedLines[i]
+
+ // Interval that will need to be updated
+ // Between start and (start + offset) remove highlight
+ if (lineNumber >= start) {
+ highlightedLines.splice(highlightedLines.indexOf(lineNumber), 1)
+
+ // Lines that need to be relocated
+ if (lineNumber >= (start + linesRemoved)) {
+ newLines.push(lineNumber + offset)
+ }
+ }
+ }
+
+ // Adding relocated lines
+ highlightedLines.push(...newLines)
+
+ if (this.props.onChange) {
+ this.props.onChange(editor)
+ }
+ }
+
+ handleHighlight (editor, changeObject) {
+ const lines = editor.options.linesHighlighted
+
+ if (!lines.includes(changeObject)) {
+ lines.push(changeObject)
+ editor.addLineClass(changeObject, 'text', 'CodeMirror-activeline-background')
+ } else {
+ lines.splice(lines.indexOf(changeObject), 1)
+ editor.removeLineClass(changeObject, 'text', 'CodeMirror-activeline-background')
+ }
+ if (this.props.onChange) {
+ this.props.onChange(editor)
+ }
+ }
+
+ updateHighlight (editor, changeObject) {
+ const linesAdded = changeObject.text.length - 1
+ const linesRemoved = changeObject.removed.length - 1
+
+ // If no lines added or removed return
+ if (linesAdded === 0 && linesRemoved === 0) {
+ return
+ }
+
+ let start = changeObject.from.line
+
+ switch (changeObject.origin) {
+ case '+insert", "undo':
+ start += 1
+ break
+
+ case 'paste':
+ case '+delete':
+ case '+input':
+ if (changeObject.to.ch !== 0 || changeObject.from.ch !== 0) {
+ start += 1
+ }
+ break
+
+ default:
+ return
+ }
+
+ this.incrementLines(start, linesAdded, linesRemoved, editor)
+ }
+
moveCursorTo (row, col) {}
scrollToLine (event, num) {
@@ -567,6 +659,7 @@ export default class CodeEditor extends React.Component {
this.value = this.props.value
this.editor.setValue(this.props.value)
this.editor.clearHistory()
+ this.restartHighlighting()
this.editor.on('change', this.changeHandler)
this.editor.refresh()
}
@@ -758,6 +851,29 @@ export default class CodeEditor extends React.Component {
})
}
+ initialHighlighting () {
+ if (this.editor.options.linesHighlighted == null) {
+ return
+ }
+
+ const totalHighlightedLines = this.editor.options.linesHighlighted.length
+ const totalAvailableLines = this.editor.lineCount()
+
+ for (let i = 0; i < totalHighlightedLines; i++) {
+ const lineNumber = this.editor.options.linesHighlighted[i]
+ if (lineNumber > totalAvailableLines) {
+ // make sure that we skip the invalid lines althrough this case should not be happened.
+ continue
+ }
+ this.editor.addLineClass(lineNumber, 'text', 'CodeMirror-activeline-background')
+ }
+ }
+
+ restartHighlighting () {
+ this.editor.options.linesHighlighted = this.props.linesHighlighted
+ this.initialHighlighting()
+ }
+
mapImageResponse (response, pastedTxt) {
return new Promise((resolve, reject) => {
try {
diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js
index d3270c18..db0c4374 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} = this.props
+ const {className, value, config, storageKey, noteKey, linesHighlighted} = this.props
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
@@ -275,6 +298,7 @@ class MarkdownEditor extends React.Component {
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}
@@ -314,6 +338,7 @@ class MarkdownEditor extends React.Component {
customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS}
lineThroughCheckbox={config.preview.lineThroughCheckbox}
+ onDrop={(e) => this.handleDropImage(e)}
/>
)
diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js
index 7bfd8a10..9f190773 100755
--- a/browser/components/MarkdownPreview.js
+++ b/browser/components/MarkdownPreview.js
@@ -21,6 +21,8 @@ import yaml from 'js-yaml'
import context from 'browser/lib/context'
import i18n from 'browser/lib/i18n'
import fs from 'fs'
+import { render } from 'react-dom'
+import Carousel from 'react-image-carousel'
import ConfigManager from '../main/lib/ConfigManager'
const { remote, shell } = require('electron')
@@ -40,7 +42,8 @@ const appPath = fileUrl(
)
const CSS_FILES = [
`${appPath}/node_modules/katex/dist/katex.min.css`,
- `${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`
]
function buildStyle (
@@ -207,7 +210,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()
}
@@ -410,6 +413,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',
@@ -447,7 +452,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',
@@ -464,6 +469,8 @@ export default class MarkdownPreview extends React.Component {
}
componentWillUnmount () {
+ const { onDrop } = this.props
+
this.refs.root.contentWindow.document.body.removeEventListener(
'contextmenu',
this.contextMenuHandler
@@ -482,7 +489,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',
@@ -772,6 +779,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(
+ ,
+ el
+ )
+ }
+ )
}
focus () {
@@ -814,7 +849,7 @@ export default class MarkdownPreview extends React.Component {
return new window.Notification(title, options)
}
- handlelinkClick (e) {
+ handleLinkClick (e) {
e.preventDefault()
e.stopPropagation()
diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js
index bd79bc24..f8f8b366 100644
--- a/browser/components/MarkdownSplitEditor.js
+++ b/browser/components/MarkdownSplitEditor.js
@@ -24,9 +24,9 @@ class MarkdownSplitEditor extends React.Component {
this.refs.code.setValue(value)
}
- handleOnChange () {
+ handleOnChange (e) {
this.value = this.refs.code.value
- this.props.onChange()
+ this.props.onChange(e)
}
handleScroll (e) {
@@ -136,7 +136,7 @@ class MarkdownSplitEditor extends React.Component {
}
render () {
- const {config, value, storageKey, noteKey} = this.props
+ const {config, value, storageKey, noteKey, linesHighlighted} = this.props
const storage = findStorage(storageKey)
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
@@ -169,7 +169,8 @@ class MarkdownSplitEditor extends React.Component {
enableTableEditor={config.editor.enableTableEditor}
storageKey={storageKey}
noteKey={noteKey}
- onChange={this.handleOnChange.bind(this)}
+ linesHighlighted={linesHighlighted}
+ onChange={(e) => this.handleOnChange(e)}
onScroll={this.handleScroll.bind(this)}
spellCheck={config.editor.spellcheck}
enableSmartPaste={config.editor.enableSmartPaste}
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 2a7b66b0..0ea15ba9 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
@@ -118,14 +117,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']})
@@ -152,6 +145,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 => {
return `
${token.fileName}
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 b4e7a5b3..bc6cd499 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)
@@ -71,7 +73,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()
@@ -361,6 +363,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}
/>
@@ -371,6 +374,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}
/>
diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js
index 887e5237..ebe61ba9 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,6 +715,7 @@ class SnippetNoteDetail extends React.Component {
: \n\nEnjoy Boostnote!
\n\n