1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 17:56:25 +00:00

Merge branch 'master' into filter-tags-and-folders

This commit is contained in:
amedora
2019-05-09 09:23:17 +09:00
49 changed files with 546 additions and 290 deletions

View File

@@ -14,6 +14,8 @@ import {
import TextEditorInterface from 'browser/lib/TextEditorInterface' import TextEditorInterface from 'browser/lib/TextEditorInterface'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite' import iconv from 'iconv-lite'
import { isMarkdownTitleURL } from 'browser/lib/utils'
import styles from '../components/CodeEditor.styl' import styles from '../components/CodeEditor.styl'
const { ipcRenderer, remote, clipboard } = require('electron') const { ipcRenderer, remote, clipboard } = require('electron')
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
@@ -22,6 +24,8 @@ const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
import TurndownService from 'turndown' import TurndownService from 'turndown'
import {languageMaps} from '../lib/CMLanguageList' import {languageMaps} from '../lib/CMLanguageList'
import snippetManager from '../lib/SnippetManager' import snippetManager from '../lib/SnippetManager'
import {generateInEditor, tocExistsInEditor} from 'browser/lib/markdown-toc-generator'
import markdownlint from 'markdownlint'
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
@@ -34,6 +38,38 @@ function translateHotkey (hotkey) {
return hotkey.replace(/\s*\+\s*/g, '-').replace(/Command/g, 'Cmd').replace(/Control/g, 'Ctrl') return hotkey.replace(/\s*\+\s*/g, '-').replace(/Command/g, 'Cmd').replace(/Control/g, 'Ctrl')
} }
const validatorOfMarkdown = (text, updateLinting) => {
const lintOptions = {
'strings': {
'content': text
}
}
return markdownlint(lintOptions, (err, result) => {
if (!err) {
const foundIssues = []
result.content.map(item => {
let ruleNames = ''
item.ruleNames.map((ruleName, index) => {
ruleNames += ruleName
if (index === item.ruleNames.length - 1) {
ruleNames += ': '
} else {
ruleNames += '/'
}
})
foundIssues.push({
from: CodeMirror.Pos(item.lineNumber, 0),
to: CodeMirror.Pos(item.lineNumber, 1),
message: ruleNames + item.ruleDescription,
severity: 'warning'
})
})
updateLinting(foundIssues)
}
})
}
export default class CodeEditor extends React.Component { export default class CodeEditor extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
@@ -197,6 +233,26 @@ export default class CodeEditor extends React.Component {
'Cmd-T': function (cm) { 'Cmd-T': function (cm) {
// Do nothing // Do nothing
}, },
'Ctrl-/': function (cm) {
if (global.process.platform === 'darwin') { return }
const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleDateString())
},
'Cmd-/': function (cm) {
if (global.process.platform !== 'darwin') { return }
const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleDateString())
},
'Shift-Ctrl-/': function (cm) {
if (global.process.platform === 'darwin') { return }
const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleString())
},
'Shift-Cmd-/': function (cm) {
if (global.process.platform !== 'darwin') { return }
const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleString())
},
Enter: 'boostNewLineAndIndentContinueMarkdownList', Enter: 'boostNewLineAndIndentContinueMarkdownList',
'Ctrl-C': cm => { 'Ctrl-C': cm => {
if (cm.getOption('keyMap').substr(0, 3) === 'vim') { if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
@@ -233,6 +289,7 @@ export default class CodeEditor extends React.Component {
snippetManager.init() snippetManager.init()
this.updateDefaultKeyMap() this.updateDefaultKeyMap()
const checkMarkdownNoteIsOpening = this.props.mode === 'Boost Flavored Markdown'
this.value = this.props.value this.value = this.props.value
this.editor = CodeMirror(this.refs.root, { this.editor = CodeMirror(this.refs.root, {
rulers: buildCMRulers(rulers, enableRulers), rulers: buildCMRulers(rulers, enableRulers),
@@ -249,7 +306,11 @@ export default class CodeEditor extends React.Component {
inputStyle: 'textarea', inputStyle: 'textarea',
dragDrop: false, dragDrop: false,
foldGutter: true, foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], lint: checkMarkdownNoteIsOpening ? {
'getAnnotations': validatorOfMarkdown,
'async': true
} : false,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
autoCloseBrackets: { autoCloseBrackets: {
pairs: this.props.matchingPairs, pairs: this.props.matchingPairs,
triples: this.props.matchingTriples, triples: this.props.matchingTriples,
@@ -594,6 +655,34 @@ export default class CodeEditor extends React.Component {
handleChange (editor, changeObject) { handleChange (editor, changeObject) {
spellcheck.handleChange(editor, changeObject) spellcheck.handleChange(editor, changeObject)
// The current note contains an toc. We'll check for changes on headlines.
// origin is undefined when markdownTocGenerator replace the old tod
if (tocExistsInEditor(editor) && changeObject.origin !== undefined) {
let requireTocUpdate
// Check if one of the changed lines contains a headline
for (let line = 0; line < changeObject.text.length; line++) {
if (this.linePossibleContainsHeadline(editor.getLine(changeObject.from.line + line))) {
requireTocUpdate = true
break
}
}
if (!requireTocUpdate) {
// Check if one of the removed lines contains a headline
for (let line = 0; line < changeObject.removed.length; line++) {
if (this.linePossibleContainsHeadline(changeObject.removed[line])) {
requireTocUpdate = true
break
}
}
}
if (requireTocUpdate) {
generateInEditor(editor)
}
}
this.updateHighlight(editor, changeObject) this.updateHighlight(editor, changeObject)
this.value = editor.getValue() this.value = editor.getValue()
@@ -602,6 +691,12 @@ export default class CodeEditor extends React.Component {
} }
} }
linePossibleContainsHeadline (currentLine) {
// We can't check if the line start with # because when some write text before
// the # we also need to update the toc
return currentLine.includes('# ')
}
incrementLines (start, linesAdded, linesRemoved, editor) { incrementLines (start, linesAdded, linesRemoved, editor) {
const highlightedLines = editor.options.linesHighlighted const highlightedLines = editor.options.linesHighlighted
@@ -809,6 +904,8 @@ export default class CodeEditor extends React.Component {
if (isInFencedCodeBlock(editor)) { if (isInFencedCodeBlock(editor)) {
this.handlePasteText(editor, pastedTxt) this.handlePasteText(editor, pastedTxt)
} else if (fetchUrlTitle && isMarkdownTitleURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(editor, pastedTxt)
} else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) { } else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(editor, pastedTxt) this.handlePasteUrl(editor, pastedTxt)
} else if (attachmentManagement.isAttachmentLink(pastedTxt)) { } else if (attachmentManagement.isAttachmentLink(pastedTxt)) {
@@ -850,7 +947,17 @@ export default class CodeEditor extends React.Component {
} }
handlePasteUrl (editor, pastedTxt) { handlePasteUrl (editor, pastedTxt) {
const taggedUrl = `<${pastedTxt}>` let taggedUrl = `<${pastedTxt}>`
let urlToFetch = pastedTxt
let titleMark = ''
if (isMarkdownTitleURL(pastedTxt)) {
const pastedTxtSplitted = pastedTxt.split(' ')
titleMark = `${pastedTxtSplitted[0]} `
urlToFetch = pastedTxtSplitted[1]
taggedUrl = `<${urlToFetch}>`
}
editor.replaceSelection(taggedUrl) editor.replaceSelection(taggedUrl)
const isImageReponse = response => { const isImageReponse = response => {
@@ -862,22 +969,23 @@ export default class CodeEditor extends React.Component {
const replaceTaggedUrl = replacement => { const replaceTaggedUrl = replacement => {
const value = editor.getValue() const value = editor.getValue()
const cursor = editor.getCursor() const cursor = editor.getCursor()
const newValue = value.replace(taggedUrl, replacement) const newValue = value.replace(taggedUrl, titleMark + replacement)
const newCursor = Object.assign({}, cursor, { const newCursor = Object.assign({}, cursor, {
ch: cursor.ch + newValue.length - value.length ch: cursor.ch + newValue.length - (value.length - titleMark.length)
}) })
editor.setValue(newValue) editor.setValue(newValue)
editor.setCursor(newCursor) editor.setCursor(newCursor)
} }
fetch(pastedTxt, { fetch(urlToFetch, {
method: 'get' method: 'get'
}) })
.then(response => { .then(response => {
if (isImageReponse(response)) { if (isImageReponse(response)) {
return this.mapImageResponse(response, pastedTxt) return this.mapImageResponse(response, urlToFetch)
} else { } else {
return this.mapNormalResponse(response, pastedTxt) return this.mapNormalResponse(response, urlToFetch)
} }
}) })
.then(replacement => { .then(replacement => {

View File

@@ -3,4 +3,3 @@
.spellcheck-select .spellcheck-select
border: none border: none
text-decoration underline wavy red

View File

@@ -8,7 +8,7 @@ import consts from 'browser/lib/consts'
import Raphael from 'raphael' import Raphael from 'raphael'
import flowchart from 'flowchart' import flowchart from 'flowchart'
import mermaidRender from './render/MermaidRender' import mermaidRender from './render/MermaidRender'
import SequenceDiagram from 'js-sequence-diagrams' import SequenceDiagram from '@rokt33r/js-sequence-diagrams'
import Chart from 'chart.js' import Chart from 'chart.js'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import htmlTextHelper from 'browser/lib/htmlTextHelper' import htmlTextHelper from 'browser/lib/htmlTextHelper'
@@ -255,7 +255,7 @@ export default class MarkdownPreview extends React.Component {
return return
} }
// No contextMenu was passed to us -> execute our own link-opener // No contextMenu was passed to us -> execute our own link-opener
if (event.target.tagName.toLowerCase() === 'a') { if (event.target.tagName.toLowerCase() === 'a' && event.target.getAttribute('href')) {
const href = event.target.href const href = event.target.href
const isLocalFile = href.startsWith('file:') const isLocalFile = href.startsWith('file:')
if (isLocalFile) { if (isLocalFile) {
@@ -282,33 +282,27 @@ export default class MarkdownPreview extends React.Component {
handleMouseDown (e) { handleMouseDown (e) {
const config = ConfigManager.get() const config = ConfigManager.get()
const clickElement = e.target
const targetTag = clickElement.tagName // The direct parent HTML of where was clicked ie "BODY" or "DIV"
const lineNumber = getSourceLineNumberByElement(clickElement) // Line location of element clicked.
if (config.editor.switchPreview === 'RIGHTCLICK' && e.buttons === 2 && config.editor.type === 'SPLIT') { if (config.editor.switchPreview === 'RIGHTCLICK' && e.buttons === 2 && config.editor.type === 'SPLIT') {
eventEmitter.emit('topbar:togglemodebutton', 'CODE') eventEmitter.emit('topbar:togglemodebutton', 'CODE')
} }
if (e.ctrlKey) { if (e.ctrlKey) {
if (config.editor.type === 'SPLIT') { if (config.editor.type === 'SPLIT') {
const clickElement = e.target
const lineNumber = getSourceLineNumberByElement(clickElement)
if (lineNumber !== -1) { if (lineNumber !== -1) {
eventEmitter.emit('line:jump', lineNumber) eventEmitter.emit('line:jump', lineNumber)
} }
} else { } else {
const clickElement = e.target
const lineNumber = getSourceLineNumberByElement(clickElement)
if (lineNumber !== -1) { if (lineNumber !== -1) {
eventEmitter.emit('editor:focus') eventEmitter.emit('editor:focus')
eventEmitter.emit('line:jump', lineNumber) eventEmitter.emit('line:jump', lineNumber)
} }
} }
} }
if (e.target != null) {
switch (e.target.tagName) { if (this.props.onMouseDown != null && targetTag === 'BODY') this.props.onMouseDown(e)
case 'A':
case 'INPUT':
return null
}
}
if (this.props.onMouseDown != null) this.props.onMouseDown(e)
} }
handleMouseUp (e) { handleMouseUp (e) {
@@ -676,14 +670,14 @@ export default class MarkdownPreview extends React.Component {
) )
} }
GetCodeThemeLink (theme) { GetCodeThemeLink (name) {
theme = consts.THEMES.some(_theme => _theme === theme) && const theme = consts.THEMES.find(theme => theme.name === name)
theme !== 'default'
? theme if (theme) {
: 'elegant' return `${appPath}/${theme.path}`
return theme.startsWith('solarized') } else {
? `${appPath}/node_modules/codemirror/theme/solarized.css` return `${appPath}/node_modules/codemirror/theme/elegant.css`
: `${appPath}/node_modules/codemirror/theme/${theme}.css` }
} }
rewriteIframe () { rewriteIframe () {
@@ -741,9 +735,9 @@ export default class MarkdownPreview extends React.Component {
} }
) )
codeBlockTheme = consts.THEMES.some(_theme => _theme === codeBlockTheme) codeBlockTheme = consts.THEMES.find(theme => theme.name === codeBlockTheme)
? codeBlockTheme
: 'default' const codeBlockThemeClassName = codeBlockTheme ? codeBlockTheme.className : 'cm-s-default'
_.forEach( _.forEach(
this.refs.root.contentWindow.document.querySelectorAll('.code code'), this.refs.root.contentWindow.document.querySelectorAll('.code code'),
@@ -766,14 +760,11 @@ export default class MarkdownPreview extends React.Component {
}) })
} }
} }
el.parentNode.appendChild(copyIcon) el.parentNode.appendChild(copyIcon)
el.innerHTML = '' el.innerHTML = ''
if (codeBlockTheme.indexOf('solarized') === 0) { el.parentNode.className += ` ${codeBlockThemeClassName}`
const [refThema, color] = codeBlockTheme.split(' ')
el.parentNode.className += ` cm-s-${refThema} cm-s-${color}`
} else {
el.parentNode.className += ` cm-s-${codeBlockTheme}`
}
CodeMirror.runMode(content, syntax.mime, el, { CodeMirror.runMode(content, syntax.mime, el, {
tabSize: indentSize tabSize: indentSize
}) })
@@ -888,78 +879,96 @@ export default class MarkdownPreview extends React.Component {
const markdownPreviewIframe = document.querySelector('.MarkdownPreview') const markdownPreviewIframe = document.querySelector('.MarkdownPreview')
const rect = markdownPreviewIframe.getBoundingClientRect() const rect = markdownPreviewIframe.getBoundingClientRect()
const config = { attributes: true, subtree: true }
const imgObserver = new MutationObserver((mutationList) => {
for (const mu of mutationList) {
if (mu.target.className === 'carouselContent-enter-done') {
this.setImgOnClickEventHelper(mu.target, rect)
break
}
}
})
const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('img') const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('img')
for (const img of imgList) { for (const img of imgList) {
img.onclick = () => { const parentEl = img.parentElement
const widthMagnification = document.body.clientWidth / img.width this.setImgOnClickEventHelper(img, rect)
const heightMagnification = document.body.clientHeight / img.height imgObserver.observe(parentEl, config)
const baseOnWidth = widthMagnification < heightMagnification }
const magnification = baseOnWidth ? widthMagnification : heightMagnification }
const zoomImgWidth = img.width * magnification setImgOnClickEventHelper (img, rect) {
const zoomImgHeight = img.height * magnification img.onclick = () => {
const zoomImgTop = (document.body.clientHeight - zoomImgHeight) / 2 const widthMagnification = document.body.clientWidth / img.width
const zoomImgLeft = (document.body.clientWidth - zoomImgWidth) / 2 const heightMagnification = document.body.clientHeight / img.height
const originalImgTop = img.y + rect.top const baseOnWidth = widthMagnification < heightMagnification
const originalImgLeft = img.x + rect.left const magnification = baseOnWidth ? widthMagnification : heightMagnification
const originalImgRect = {
top: `${originalImgTop}px`,
left: `${originalImgLeft}px`,
width: `${img.width}px`,
height: `${img.height}px`
}
const zoomInImgRect = {
top: `${baseOnWidth ? zoomImgTop : 0}px`,
left: `${baseOnWidth ? 0 : zoomImgLeft}px`,
width: `${zoomImgWidth}px`,
height: `${zoomImgHeight}px`
}
const animationSpeed = 300
const zoomImg = document.createElement('img') const zoomImgWidth = img.width * magnification
zoomImg.src = img.src const zoomImgHeight = img.height * magnification
const zoomImgTop = (document.body.clientHeight - zoomImgHeight) / 2
const zoomImgLeft = (document.body.clientWidth - zoomImgWidth) / 2
const originalImgTop = img.y + rect.top
const originalImgLeft = img.x + rect.left
const originalImgRect = {
top: `${originalImgTop}px`,
left: `${originalImgLeft}px`,
width: `${img.width}px`,
height: `${img.height}px`
}
const zoomInImgRect = {
top: `${baseOnWidth ? zoomImgTop : 0}px`,
left: `${baseOnWidth ? 0 : zoomImgLeft}px`,
width: `${zoomImgWidth}px`,
height: `${zoomImgHeight}px`
}
const animationSpeed = 300
const zoomImg = document.createElement('img')
zoomImg.src = img.src
zoomImg.style = `
position: absolute;
top: ${baseOnWidth ? zoomImgTop : 0}px;
left: ${baseOnWidth ? 0 : zoomImgLeft}px;
width: ${zoomImgWidth};
height: ${zoomImgHeight}px;
`
zoomImg.animate([
originalImgRect,
zoomInImgRect
], animationSpeed)
const overlay = document.createElement('div')
overlay.style = `
background-color: rgba(0,0,0,0.5);
cursor: zoom-out;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: ${document.body.clientHeight}px;
z-index: 100;
`
overlay.onclick = () => {
zoomImg.style = ` zoomImg.style = `
position: absolute; position: absolute;
top: ${baseOnWidth ? zoomImgTop : 0}px; top: ${originalImgTop}px;
left: ${baseOnWidth ? 0 : zoomImgLeft}px; left: ${originalImgLeft}px;
width: ${zoomImgWidth}; width: ${img.width}px;
height: ${zoomImgHeight}px; height: ${img.height}px;
` `
zoomImg.animate([ const zoomOutImgAnimation = zoomImg.animate([
originalImgRect, zoomInImgRect,
zoomInImgRect originalImgRect
], animationSpeed) ], animationSpeed)
zoomOutImgAnimation.onfinish = () => overlay.remove()
const overlay = document.createElement('div')
overlay.style = `
background-color: rgba(0,0,0,0.5);
cursor: zoom-out;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: ${document.body.clientHeight}px;
z-index: 100;
`
overlay.onclick = () => {
zoomImg.style = `
position: absolute;
top: ${originalImgTop}px;
left: ${originalImgLeft}px;
width: ${img.width}px;
height: ${img.height}px;
`
const zoomOutImgAnimation = zoomImg.animate([
zoomInImgRect,
originalImgRect
], animationSpeed)
zoomOutImgAnimation.onfinish = () => overlay.remove()
}
overlay.appendChild(zoomImg)
document.body.appendChild(overlay)
} }
overlay.appendChild(zoomImg)
document.body.appendChild(overlay)
} }
this.getWindow().scrollTo(0, 0)
} }
focus () { focus () {
@@ -1006,9 +1015,11 @@ export default class MarkdownPreview extends React.Component {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
const href = e.target.href const href = e.target.getAttribute('href')
const linkHash = href.split('/').pop() const linkHash = href.split('/').pop()
if (!href) return
const regexNoteInternalLink = /main.html#(.+)/ const regexNoteInternalLink = /main.html#(.+)/
if (regexNoteInternalLink.test(linkHash)) { if (regexNoteInternalLink.test(linkHash)) {
const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1]) const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1])

View File

@@ -16,8 +16,8 @@ const NavToggleButton = ({isFolded, handleToggleButtonClick}) => (
onClick={(e) => handleToggleButtonClick(e)} onClick={(e) => handleToggleButtonClick(e)}
> >
{isFolded {isFolded
? <i className='fa fa-angle-double-right' /> ? <i className='fa fa-angle-double-right fa-2x' />
: <i className='fa fa-angle-double-left' /> : <i className='fa fa-angle-double-left fa-2x' />
} }
</button> </button>
) )

View File

@@ -7,7 +7,7 @@
border-radius 16.5px border-radius 16.5px
height 34px height 34px
width 34px width 34px
line-height 32px line-height 100%
padding 0 padding 0
&:hover &:hover
border: 1px solid #1EC38B; border: 1px solid #1EC38B;

View File

@@ -3,14 +3,43 @@ const fs = require('sander')
const { remote } = require('electron') const { remote } = require('electron')
const { app } = remote const { app } = remote
const themePath = process.env.NODE_ENV === 'production' const CODEMIRROR_THEME_PATH = 'node_modules/codemirror/theme'
? path.join(app.getAppPath(), './node_modules/codemirror/theme') const CODEMIRROR_EXTRA_THEME_PATH = 'extra_scripts/codemirror/theme'
: require('path').resolve('./node_modules/codemirror/theme')
const themes = fs.readdirSync(themePath) const isProduction = process.env.NODE_ENV === 'production'
.map((themePath) => { const paths = [
return themePath.substring(0, themePath.lastIndexOf('.')) isProduction ? path.join(app.getAppPath(), CODEMIRROR_THEME_PATH) : path.resolve(CODEMIRROR_THEME_PATH),
}) isProduction ? path.join(app.getAppPath(), CODEMIRROR_EXTRA_THEME_PATH) : path.resolve(CODEMIRROR_EXTRA_THEME_PATH)
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light') ]
const themes = paths
.map(directory => fs.readdirSync(directory).map(file => {
const name = file.substring(0, file.lastIndexOf('.'))
return {
name,
path: path.join(directory.split(/\//g).slice(-3).join('/'), file),
className: `cm-s-${name}`
}
}))
.reduce((accumulator, value) => accumulator.concat(value), [])
.sort((a, b) => a.name.localeCompare(b.name))
themes.splice(themes.findIndex(({ name }) => name === 'solarized'), 1, {
name: 'solarized dark',
path: `${CODEMIRROR_THEME_PATH}/solarized.css`,
className: `cm-s-solarized cm-s-dark`
}, {
name: 'solarized light',
path: `${CODEMIRROR_THEME_PATH}/solarized.css`,
className: `cm-s-solarized cm-s-light`
})
themes.splice(0, 0, {
name: 'default',
path: `${CODEMIRROR_THEME_PATH}/elegant.css`,
className: `cm-s-default`
})
const snippetFile = process.env.NODE_ENV !== 'test' const snippetFile = process.env.NODE_ENV !== 'test'
? path.join(app.getPath('userData'), 'snippets.json') ? path.join(app.getPath('userData'), 'snippets.json')
@@ -35,7 +64,7 @@ const consts = {
'Dodger Blue', 'Dodger Blue',
'Violet Eggplant' 'Violet Eggplant'
], ],
THEMES: ['default'].concat(themes), THEMES: themes,
SNIPPET_FILE: snippetFile, SNIPPET_FILE: snippetFile,
DEFAULT_EDITOR_FONT_FAMILY: [ DEFAULT_EDITOR_FONT_FAMILY: [
'Monaco', 'Monaco',

View File

@@ -28,6 +28,8 @@ function linkify (token) {
const TOC_MARKER_START = '<!-- toc -->' const TOC_MARKER_START = '<!-- toc -->'
const TOC_MARKER_END = '<!-- tocstop -->' const TOC_MARKER_END = '<!-- tocstop -->'
const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
/** /**
* Takes care of proper updating given editor with TOC. * Takes care of proper updating given editor with TOC.
* If TOC doesn't exit in the editor, it's inserted at current caret position. * If TOC doesn't exit in the editor, it's inserted at current caret position.
@@ -35,12 +37,6 @@ const TOC_MARKER_END = '<!-- tocstop -->'
* @param editor CodeMirror editor to be updated with TOC * @param editor CodeMirror editor to be updated with TOC
*/ */
export function generateInEditor (editor) { export function generateInEditor (editor) {
const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
function tocExistsInEditor () {
return tocRegex.test(editor.getValue())
}
function updateExistingToc () { function updateExistingToc () {
const toc = generate(editor.getValue()) const toc = generate(editor.getValue())
const search = editor.getSearchCursor(tocRegex) const search = editor.getSearchCursor(tocRegex)
@@ -54,13 +50,17 @@ export function generateInEditor (editor) {
editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor()) editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor())
} }
if (tocExistsInEditor()) { if (tocExistsInEditor(editor)) {
updateExistingToc() updateExistingToc()
} else { } else {
addTocAtCursorPosition() addTocAtCursorPosition()
} }
} }
export function tocExistsInEditor (editor) {
return tocRegex.test(editor.getValue())
}
/** /**
* Generates MD TOC based on MD document passed as string. * Generates MD TOC based on MD document passed as string.
* @param markdownText MD document * @param markdownText MD document
@@ -94,5 +94,6 @@ function wrapTocWithEol (toc, editor) {
export default { export default {
generate, generate,
generateInEditor generateInEditor,
tocExistsInEditor
} }

View File

@@ -181,7 +181,7 @@ class Markdown {
}) })
const deflate = require('markdown-it-plantuml/lib/deflate') const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), '', { this.md.use(require('markdown-it-plantuml'), {
generateSource: function (umlCode) { generateSource: function (umlCode) {
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg' const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg'

View File

@@ -132,8 +132,13 @@ export function isObjectEqual (a, b) {
return true return true
} }
export function isMarkdownTitleURL (str) {
return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
}
export default { export default {
lastFindInArray, lastFindInArray,
escapeHtmlCharacters, escapeHtmlCharacters,
isObjectEqual isObjectEqual,
isMarkdownTitleURL
} }

View File

@@ -895,7 +895,7 @@ class NoteList extends React.Component {
if (!location.pathname.match(/\/trashed/)) this.addNotesFromFiles(filepaths) if (!location.pathname.match(/\/trashed/)) this.addNotesFromFiles(filepaths)
} }
// Add notes to the current folder // Add notes to the current folder
addNotesFromFiles (filepaths) { addNotesFromFiles (filepaths) {
const { dispatch, location } = this.props const { dispatch, location } = this.props
const { storage, folder } = this.resolveTargetFolder() const { storage, folder } = this.resolveTargetFolder()
@@ -919,13 +919,20 @@ class NoteList extends React.Component {
} }
dataApi.createNote(storage.key, newNote) dataApi.createNote(storage.key, newNote)
.then((note) => { .then((note) => {
dispatch({ attachmentManagement.importAttachments(note.content, filepath, storage.key, note.key)
type: 'UPDATE_NOTE', .then((newcontent) => {
note: note note.content = newcontent
})
hashHistory.push({ dataApi.updateNote(storage.key, note.key, note)
pathname: location.pathname,
query: {key: getNoteKey(note)} dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: {key: getNoteKey(note)}
})
}) })
}) })
}) })

View File

@@ -132,16 +132,12 @@ function get () {
document.head.appendChild(editorTheme) document.head.appendChild(editorTheme)
} }
config.editor.theme = consts.THEMES.some((theme) => theme === config.editor.theme) const theme = consts.THEMES.find(theme => theme.name === config.editor.theme)
? config.editor.theme
: 'default'
if (config.editor.theme !== 'default') { if (theme) {
if (config.editor.theme.startsWith('solarized')) { editorTheme.setAttribute('href', `../${theme.path}`)
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/solarized.css') } else {
} else { config.editor.theme = 'default'
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + config.editor.theme + '.css')
}
} }
} }
@@ -177,16 +173,11 @@ function set (updates) {
editorTheme.setAttribute('rel', 'stylesheet') editorTheme.setAttribute('rel', 'stylesheet')
document.head.appendChild(editorTheme) document.head.appendChild(editorTheme)
} }
const newTheme = consts.THEMES.some((theme) => theme === newConfig.editor.theme)
? newConfig.editor.theme
: 'default'
if (newTheme !== 'default') { const newTheme = consts.THEMES.find(theme => theme.name === newConfig.editor.theme)
if (newTheme.startsWith('solarized')) {
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/solarized.css') if (newTheme) {
} else { editorTheme.setAttribute('href', `../${newTheme.path}`)
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + newTheme + '.css')
}
} }
ipcRenderer.send('config-renew', { ipcRenderer.send('config-renew', {

View File

@@ -85,7 +85,7 @@ function getOrientation (file) {
return view.getUint16(offset + (i * 12) + 8, little) return view.getUint16(offset + (i * 12) + 8, little)
} }
} }
} else if ((marker & 0xFF00) !== 0xFF00) { // If not start with 0xFF, not a Marker } else if ((marker & 0xFF00) !== 0xFF00) { // If not start with 0xFF, not a Marker.
break break
} else { } else {
offset += view.getUint16(offset, false) offset += view.getUint16(offset, false)
@@ -278,27 +278,40 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
let promise let promise
if (dropEvent.dataTransfer.files.length > 0) { if (dropEvent.dataTransfer.files.length > 0) {
promise = Promise.all(Array.from(dropEvent.dataTransfer.files).map(file => { promise = Promise.all(Array.from(dropEvent.dataTransfer.files).map(file => {
if (file.type.startsWith('image')) { const filePath = file.path
if (file.type === 'image/gif' || file.type === 'image/svg+xml') { const fileType = file.type // EX) 'image/gif' or 'text/html'
return copyAttachment(file.path, storageKey, noteKey).then(fileName => ({ if (fileType.startsWith('image')) {
if (fileType === 'image/gif' || fileType === 'image/svg+xml') {
return copyAttachment(filePath, storageKey, noteKey).then(fileName => ({
fileName, fileName,
title: path.basename(file.path), title: path.basename(filePath),
isImage: true isImage: true
})) }))
} else { } else {
return fixRotate(file) return getOrientation(file)
.then(data => copyAttachment({type: 'base64', data: data, sourceFilePath: file.path}, storageKey, noteKey) .then((orientation) => {
.then(fileName => ({ if (orientation === -1) { // The image rotation is correct and does not need adjustment
return copyAttachment(filePath, storageKey, noteKey)
} else {
return fixRotate(file).then(data => copyAttachment({
type: 'base64',
data: data,
sourceFilePath: filePath
}, storageKey, noteKey))
}
})
.then(fileName =>
({
fileName, fileName,
title: path.basename(file.path), title: path.basename(filePath),
isImage: true isImage: true
})) })
) )
} }
} else { } else {
return copyAttachment(file.path, storageKey, noteKey).then(fileName => ({ return copyAttachment(filePath, storageKey, noteKey).then(fileName => ({
fileName, fileName,
title: path.basename(file.path), title: path.basename(filePath),
isImage: false isImage: false
})) }))
} }
@@ -325,13 +338,18 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
canvas.height = image.height canvas.height = image.height
context.drawImage(image, 0, 0) context.drawImage(image, 0, 0)
return copyAttachment({type: 'base64', data: canvas.toDataURL(), sourceFilePath: imageURL}, storageKey, noteKey) return copyAttachment({
type: 'base64',
data: canvas.toDataURL(),
sourceFilePath: imageURL
}, storageKey, noteKey)
}) })
.then(fileName => ({ .then(fileName => ({
fileName, fileName,
title: imageURL, title: imageURL,
isImage: true isImage: true
}))]) }))
])
} }
promise.then(files => { promise.then(files => {
@@ -449,6 +467,54 @@ function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
return result return result
} }
/**
* @description Copies the attachments to the storage folder and returns the mardown content it should be replaced with
* @param {String} markDownContent content in which the attachment paths should be found
* @param {String} filepath The path of the file with attachments to import
* @param {String} storageKey Storage key of the destination storage
* @param {String} noteKey Key of the current note. Will be used as subfolder in :storage
*/
function importAttachments (markDownContent, filepath, storageKey, noteKey) {
return new Promise((resolve, reject) => {
const nameRegex = /(!\[.*?]\()(.+?\..+?)(\))/g
let attachPath = nameRegex.exec(markDownContent)
const promiseArray = []
const attachmentPaths = []
const groupIndex = 2
while (attachPath) {
let attachmentPath = attachPath[groupIndex]
attachmentPaths.push(attachmentPath)
attachmentPath = path.isAbsolute(attachmentPath) ? attachmentPath : path.join(path.dirname(filepath), attachmentPath)
promiseArray.push(this.copyAttachment(attachmentPath, storageKey, noteKey))
attachPath = nameRegex.exec(markDownContent)
}
let numResolvedPromises = 0
if (promiseArray.length === 0) {
resolve(markDownContent)
}
for (let j = 0; j < promiseArray.length; j++) {
promiseArray[j]
.then((fileName) => {
const newPath = path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName)
markDownContent = markDownContent.replace(attachmentPaths[j], newPath)
})
.catch((e) => {
console.error('File does not exist in path: ' + attachmentPaths[j])
})
.finally(() => {
numResolvedPromises++
if (numResolvedPromises === promiseArray.length) {
resolve(markDownContent)
}
})
}
})
}
/** /**
* @description Moves the attachments of the current note to the new location. * @description Moves the attachments of the current note to the new location.
* Returns a modified version of the given content so that the links to the attachments point to the new note key. * Returns a modified version of the given content so that the links to the attachments point to the new note key.
@@ -656,6 +722,7 @@ module.exports = {
handlePasteNativeImage, handlePasteNativeImage,
getAttachmentsInMarkdownContent, getAttachmentsInMarkdownContent,
getAbsolutePathsOfAttachmentsInContent, getAbsolutePathsOfAttachmentsInContent,
importAttachments,
removeStorageAndNoteReferences, removeStorageAndNoteReferences,
deleteAttachmentFolder, deleteAttachmentFolder,
deleteAttachmentsNotPresentInNote, deleteAttachmentsNotPresentInNote,

View File

@@ -8,7 +8,7 @@ import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote'
class NewNoteModal extends React.Component { class NewNoteModal extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
this.lock = false
this.state = {} this.state = {}
} }
@@ -22,9 +22,12 @@ class NewNoteModal extends React.Component {
handleMarkdownNoteButtonClick (e) { handleMarkdownNoteButtonClick (e) {
const { storage, folder, dispatch, location, params, config } = this.props const { storage, folder, dispatch, location, params, config } = this.props
createMarkdownNote(storage, folder, dispatch, location, params, config).then(() => { if (!this.lock) {
setTimeout(this.props.close, 200) this.lock = true
}) createMarkdownNote(storage, folder, dispatch, location, params, config).then(() => {
setTimeout(this.props.close, 200)
})
}
} }
handleMarkdownNoteButtonKeyDown (e) { handleMarkdownNoteButtonKeyDown (e) {
@@ -36,9 +39,12 @@ class NewNoteModal extends React.Component {
handleSnippetNoteButtonClick (e) { handleSnippetNoteButtonClick (e) {
const { storage, folder, dispatch, location, params, config } = this.props const { storage, folder, dispatch, location, params, config } = this.props
createSnippetNote(storage, folder, dispatch, location, params, config).then(() => { if (!this.lock) {
setTimeout(this.props.close, 200) this.lock = true
}) createSnippetNote(storage, folder, dispatch, location, params, config).then(() => {
setTimeout(this.props.close, 200)
})
}
} }
handleSnippetNoteButtonKeyDown (e) { handleSnippetNoteButtonKeyDown (e) {

View File

@@ -18,6 +18,14 @@
margin-bottom 15px margin-bottom 15px
margin-top 30px margin-top 30px
.group-header--sub
@extend .group-header
margin-bottom 10px
.group-header2--sub
@extend .group-header2
margin-bottom 10px
.group-section .group-section
margin-bottom 20px margin-bottom 20px
display flex display flex
@@ -148,10 +156,12 @@ body[data-theme="dark"]
color $ui-dark-text-color color $ui-dark-text-color
.group-header .group-header
.group-header--sub
color $ui-dark-text-color color $ui-dark-text-color
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
.group-header2 .group-header2
.group-header2--sub
color $ui-dark-text-color color $ui-dark-text-color
.group-section-control-input .group-section-control-input
@@ -176,10 +186,12 @@ body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
.group-header .group-header
.group-header--sub
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
border-color $ui-solarized-dark-borderColor border-color $ui-solarized-dark-borderColor
.group-header2 .group-header2
.group-header2--sub
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
.group-section-control-input .group-section-control-input
@@ -203,10 +215,12 @@ body[data-theme="monokai"]
color $ui-monokai-text-color color $ui-monokai-text-color
.group-header .group-header
.group-header--sub
color $ui-monokai-text-color color $ui-monokai-text-color
border-color $ui-monokai-borderColor border-color $ui-monokai-borderColor
.group-header2 .group-header2
.group-header2--sub
color $ui-monokai-text-color color $ui-monokai-text-color
.group-section-control-input .group-section-control-input
@@ -230,10 +244,12 @@ body[data-theme="dracula"]
color $ui-dracula-text-color color $ui-dracula-text-color
.group-header .group-header
.group-header--sub
color $ui-dracula-text-color color $ui-dracula-text-color
border-color $ui-dracula-borderColor border-color $ui-dracula-borderColor
.group-header2 .group-header2
.group-header2--sub
color $ui-dracula-text-color color $ui-dracula-text-color
.group-section-control-input .group-section-control-input

View File

@@ -22,18 +22,16 @@ class Crowdfunding extends React.Component {
render () { render () {
return ( return (
<div styleName='root'> <div styleName='root'>
<div styleName='header'>{i18n.__('Crowdfunding')}</div> <div styleName='group-header'>{i18n.__('Crowdfunding')}</div>
<p>{i18n.__('Thank you for using Boostnote!')}</p> <p>{i18n.__('Thank you for using Boostnote!')}</p>
<br /> <br />
<p>{i18n.__('We launched IssueHunt which is an issue-based crowdfunding / sourcing platform for open source projects.')}</p> <p>{i18n.__('We launched IssueHunt which is an issue-based crowdfunding / sourcing platform for open source projects.')}</p>
<p>{i18n.__('Anyone can put a bounty on not only a bug but also on OSS feature requests listed on IssueHunt. Collected funds will be distributed to project owners and contributors.')}</p> <p>{i18n.__('Anyone can put a bounty on not only a bug but also on OSS feature requests listed on IssueHunt. Collected funds will be distributed to project owners and contributors.')}</p>
<br /> <div styleName='group-header2--sub'>{i18n.__('Sustainable Open Source Ecosystem')}</div>
<p>{i18n.__('### Sustainable Open Source Ecosystem')}</p>
<p>{i18n.__('We discussed about open-source ecosystem and IssueHunt concept with the Boostnote team repeatedly. We actually also discussed with Matz who father of Ruby.')}</p> <p>{i18n.__('We discussed about open-source ecosystem and IssueHunt concept with the Boostnote team repeatedly. We actually also discussed with Matz who father of Ruby.')}</p>
<p>{i18n.__('The original reason why we made IssueHunt was to reward our contributors of Boostnote project. Weve got tons of Github stars and hundred of contributors in two years.')}</p> <p>{i18n.__('The original reason why we made IssueHunt was to reward our contributors of Boostnote project. Weve got tons of Github stars and hundred of contributors in two years.')}</p>
<p>{i18n.__('We thought that it will be nice if we can pay reward for our contributors.')}</p> <p>{i18n.__('We thought that it will be nice if we can pay reward for our contributors.')}</p>
<br /> <div styleName='group-header2--sub'>{i18n.__('We believe Meritocracy')}</div>
<p>{i18n.__('### We believe Meritocracy')}</p>
<p>{i18n.__('We think developers who have skills and do great things must be rewarded properly.')}</p> <p>{i18n.__('We think developers who have skills and do great things must be rewarded properly.')}</p>
<p>{i18n.__('OSS projects are used in everywhere on the internet, but no matter how they great, most of owners of those projects need to have another job to sustain their living.')}</p> <p>{i18n.__('OSS projects are used in everywhere on the internet, but no matter how they great, most of owners of those projects need to have another job to sustain their living.')}</p>
<p>{i18n.__('It sometimes looks like exploitation.')}</p> <p>{i18n.__('It sometimes looks like exploitation.')}</p>

View File

@@ -1,14 +1,8 @@
@import('./Tab') @import('./ConfigTab')
.root
padding 15px
white-space pre
line-height 1.4
color alpha($ui-text-color, 90%)
width 100%
font-size 14px
p p
font-size 16px font-size 16px
line-height 1.4
.cf-link .cf-link
height 35px height 35px

View File

@@ -69,8 +69,7 @@ class InfoTab extends React.Component {
render () { render () {
return ( return (
<div styleName='root'> <div styleName='root'>
<div styleName='group-header'>{i18n.__('Community')}</div>
<div styleName='header--sub'>{i18n.__('Community')}</div>
<div styleName='top'> <div styleName='top'>
<ul styleName='list'> <ul styleName='list'>
<li> <li>
@@ -108,7 +107,7 @@ class InfoTab extends React.Component {
<hr /> <hr />
<div styleName='header--sub'>{i18n.__('About')}</div> <div styleName='group-header--sub'>{i18n.__('About')}</div>
<div styleName='top'> <div styleName='top'>
<div styleName='icon-space'> <div styleName='icon-space'>
@@ -143,7 +142,7 @@ class InfoTab extends React.Component {
<hr styleName='separate-line' /> <hr styleName='separate-line' />
<div styleName='policy'>{i18n.__('Analytics')}</div> <div styleName='group-header2--sub'>{i18n.__('Analytics')}</div>
<div>{i18n.__('Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.')}</div> <div>{i18n.__('Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.')}</div>
<div>{i18n.__('You can see how it works on ')}<a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</div> <div>{i18n.__('You can see how it works on ')}<a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</div>
<br /> <br />

View File

@@ -1,16 +1,4 @@
@import('./Tab') @import('./ConfigTab.styl')
.root
padding 15px
white-space pre
line-height 1.4
color alpha($ui-text-color, 90%)
width 100%
font-size 14px
.top
text-align left
margin-bottom 20px
.icon-space .icon-space
margin 20px 0 margin 20px 0
@@ -45,13 +33,21 @@
.separate-line .separate-line
margin 40px 0 margin 40px 0
.policy
width 100%
font-size 20px
margin-bottom 10px
.policy-submit .policy-submit
margin-top 10px margin-top 10px
height 35px
border-radius 2px
border none
background-color alpha(#1EC38B, 90%)
padding-left 20px
padding-right 20px
text-decoration none
color white
font-weight 600
font-size 16px
&:hover
background-color #1EC38B
transition 0.2s
.policy-confirm .policy-confirm
margin-top 10px margin-top 10px
@@ -60,11 +56,14 @@
body[data-theme="dark"] body[data-theme="dark"]
.root .root
color alpha($tab--dark-text-color, 80%) color alpha($tab--dark-text-color, 80%)
.appId
color $ui-dark-text-color
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.root .root
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
.appId
color $ui-solarized-dark-text-color
.list .list
a a
color $ui-solarized-dark-active-color color $ui-solarized-dark-active-color
@@ -72,6 +71,8 @@ body[data-theme="solarized-dark"]
body[data-theme="monokai"] body[data-theme="monokai"]
.root .root
color $ui-monokai-text-color color $ui-monokai-text-color
.appId
color $ui-monokai-text-color
.list .list
a a
color $ui-monokai-active-color color $ui-monokai-active-color
@@ -79,6 +80,8 @@ body[data-theme="monokai"]
body[data-theme="dracula"] body[data-theme="dracula"]
.root .root
color $ui-dracula-text-color color $ui-dracula-text-color
.appId
color $ui-dracula-text-color
.list .list
a a
color $ui-dracula-active-color color $ui-dracula-active-color

View File

@@ -91,7 +91,7 @@ class SnippetTab extends React.Component {
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4 if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
return ( return (
<div styleName='root'> <div styleName='root'>
<div styleName='header'>{i18n.__('Snippets')}</div> <div styleName='group-header'>{i18n.__('Snippets')}</div>
<SnippetList <SnippetList
onSnippetSelect={this.handleSnippetSelect.bind(this)} onSnippetSelect={this.handleSnippetSelect.bind(this)}
onSnippetDeleted={this.handleDeleteSnippet.bind(this)} onSnippetDeleted={this.handleDeleteSnippet.bind(this)}

View File

@@ -1,14 +1,5 @@
@import('./Tab')
@import('./ConfigTab') @import('./ConfigTab')
.root
padding 15px
white-space pre
line-height 1.4
color alpha($ui-text-color, 90%)
width 100%
font-size 14px
.group .group
margin-bottom 45px margin-bottom 45px
@@ -127,7 +118,7 @@
background darken(#f5f5f5, 5) background darken(#f5f5f5, 5)
.snippet-detail .snippet-detail
width 70% width 67%
height calc(100% - 200px) height calc(100% - 200px)
position absolute position absolute
left 33% left 33%

View File

@@ -1,8 +1,4 @@
@import('./Tab') @import('./ConfigTab')
.root
padding 15px
color $ui-text-color
.list .list
margin-bottom 15px margin-bottom 15px

View File

@@ -128,8 +128,13 @@ class UiTab extends React.Component {
const newCodemirrorTheme = this.refs.editorTheme.value const newCodemirrorTheme = this.refs.editorTheme.value
if (newCodemirrorTheme !== codemirrorTheme) { if (newCodemirrorTheme !== codemirrorTheme) {
checkHighLight.setAttribute('href', `../node_modules/codemirror/theme/${newCodemirrorTheme.split(' ')[0]}.css`) const theme = consts.THEMES.find(theme => theme.name === newCodemirrorTheme)
if (theme) {
checkHighLight.setAttribute('href', `../${theme.path}`)
}
} }
this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme }, () => { this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme }, () => {
const {ui, editor, preview} = this.props.config const {ui, editor, preview} = this.props.config
this.currentConfig = {ui, editor, preview} this.currentConfig = {ui, editor, preview}
@@ -355,7 +360,7 @@ class UiTab extends React.Component {
> >
{ {
themes.map((theme) => { themes.map((theme) => {
return (<option value={theme} key={theme}>{theme}</option>) return (<option value={theme.name} key={theme.name}>{theme.name}</option>)
}) })
} }
</select> </select>
@@ -670,7 +675,7 @@ class UiTab extends React.Component {
> >
{ {
themes.map((theme) => { themes.map((theme) => {
return (<option value={theme} key={theme}>{theme}</option>) return (<option value={theme.name} key={theme.name}>{theme.name}</option>)
}) })
} }
</select> </select>
@@ -846,6 +851,7 @@ class UiTab extends React.Component {
onChange={e => this.handleUIChange(e)} onChange={e => this.handleUIChange(e)}
ref={e => (this.customCSSCM = e)} ref={e => (this.customCSSCM = e)}
value={config.preview.customCSS} value={config.preview.customCSS}
defaultValue={'/* Drop Your Custom CSS Code Here */\n'}
options={{ options={{
lineNumbers: true, lineNumbers: true,
mode: 'css', mode: 'css',

View File

@@ -44,7 +44,9 @@ function data (state = defaultDataMap(), action) {
const folderNoteSet = getOrInitItem(state.folderNoteMap, folderKey) const folderNoteSet = getOrInitItem(state.folderNoteMap, folderKey)
folderNoteSet.add(uniqueKey) folderNoteSet.add(uniqueKey)
assignToTags(note.tags, state, uniqueKey) if (!note.isTrashed) {
assignToTags(note.tags, state, uniqueKey)
}
}) })
return state return state
case 'UPDATE_NOTE': case 'UPDATE_NOTE':

25
extra_scripts/codemirror/theme/nord.css vendored Normal file
View File

@@ -0,0 +1,25 @@
/* Theme: nord */
.cm-s-nord.CodeMirror { color: #d8dee9; }
.cm-s-nord.CodeMirror { background: #2e3440; }
.cm-s-nord .CodeMirror-cursor { color: #d8dee9; border-color: #d8dee9; }
.cm-s-nord .CodeMirror-activeline-background { background: #434c5e52 !important; }
.cm-s-nord .CodeMirror-selected { background: undefined; }
.cm-s-nord .cm-comment { color: #4c566a; }
.cm-s-nord .cm-string { color: #a3be8c; }
.cm-s-nord .cm-string-2 { color: #8fbcbb; }
.cm-s-nord .cm-property { color: #8fbcbb; }
.cm-s-nord .cm-qualifier { color: #8fbcbb; }
.cm-s-nord .cm-tag { color: #81a1c1; }
.cm-s-nord .cm-attribute { color: #8fbcbb; }
.cm-s-nord .cm-number { color: #b48ead; }
.cm-s-nord .cm-keyword { color: #81a1c1; }
.cm-s-nord .cm-operator { color: #81a1c1; }
.cm-s-nord .cm-error { background: #bf616a; color: #d8dee9; }
.cm-s-nord .cm-invalidchar { background: #bf616a; color: #d8dee9; }
.cm-s-nord .cm-variable { color: #d8dee9; }
.cm-s-nord .cm-variable-2 { color: #8fbcbb; }
.cm-s-nord .CodeMirror-gutters {
background: #3b4252;
color: #d8dee9;
}

View File

@@ -39,7 +39,7 @@ module.exports = function (grunt) {
name: 'boostnote', name: 'boostnote',
productName: 'Boostnote', productName: 'Boostnote',
genericName: 'Boostnote', genericName: 'Boostnote',
productDescription: 'The opensource note app for developer.', productDescription: 'The opensource note app for developers.',
arch: 'amd64', arch: 'amd64',
categories: [ categories: [
'Development', 'Development',
@@ -58,7 +58,7 @@ module.exports = function (grunt) {
name: 'boostnote', name: 'boostnote',
productName: 'Boostnote', productName: 'Boostnote',
genericName: 'Boostnote', genericName: 'Boostnote',
productDescription: 'The opensource note app for developer.', productDescription: 'The opensource note app for developers.',
arch: 'x86_64', arch: 'x86_64',
categories: [ categories: [
'Development', 'Development',
@@ -149,6 +149,7 @@ module.exports = function (grunt) {
case 'osx': case 'osx':
Object.assign(opts, { Object.assign(opts, {
platform: 'darwin', platform: 'darwin',
darwinDarkModeSupport: true,
icon: path.join(__dirname, 'resources/app.icns'), icon: path.join(__dirname, 'resources/app.icns'),
'app-category-type': 'public.app-category.developer-tools' 'app-category-type': 'public.app-category.developer-tools'
}) })

View File

@@ -10,6 +10,7 @@
<link rel="stylesheet" href="../node_modules/codemirror/lib/codemirror.css"> <link rel="stylesheet" href="../node_modules/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="../node_modules/katex/dist/katex.min.css"> <link rel="stylesheet" href="../node_modules/katex/dist/katex.min.css">
<link rel="stylesheet" href="../node_modules/codemirror/addon/dialog/dialog.css"> <link rel="stylesheet" href="../node_modules/codemirror/addon/dialog/dialog.css">
<link rel="stylesheet" href="../node_modules/codemirror/addon/lint/lint.css">
<link rel="stylesheet" href="../extra_scripts/codemirror/mode/bfm/bfm.css"> <link rel="stylesheet" href="../extra_scripts/codemirror/mode/bfm/bfm.css">
<title>Boostnote</title> <title>Boostnote</title>
@@ -125,13 +126,15 @@
<script src="../node_modules/codemirror/addon/dialog/dialog.js"></script> <script src="../node_modules/codemirror/addon/dialog/dialog.js"></script>
<script src="../node_modules/codemirror/addon/display/rulers.js"></script> <script src="../node_modules/codemirror/addon/display/rulers.js"></script>
<script src="../node_modules/codemirror/addon/lint/lint.js"></script>
<script src="../node_modules/raphael/raphael.min.js"></script> <script src="../node_modules/raphael/raphael.min.js"></script>
<script src="../node_modules/flowchart.js/release/flowchart.min.js"></script> <script src="../node_modules/flowchart.js/release/flowchart.min.js"></script>
<script> <script>
window._ = require('lodash') window._ = require('lodash')
</script> </script>
<script src="../node_modules/js-sequence-diagrams/fucknpm/sequence-diagram-min.js"></script> <script src="../node_modules/@rokt33r/js-sequence-diagrams/dist/sequence-diagram-min.js"></script>
<script src="../node_modules/react/dist/react.min.js"></script> <script src="../node_modules/react/dist/react.min.js"></script>
<script src="../node_modules/react-dom/dist/react-dom.min.js"></script> <script src="../node_modules/react-dom/dist/react-dom.min.js"></script>
<script src="../node_modules/redux/dist/redux.min.js"></script> <script src="../node_modules/redux/dist/redux.min.js"></script>
@@ -154,4 +157,4 @@
</style> </style>
</body> </body>
</html> </html>

View File

@@ -76,7 +76,7 @@
"Website": "Website", "Website": "Website",
"Development": "Development", "Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.", " : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "Analytics", "Analytics": "Analytics",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.",

View File

@@ -76,7 +76,7 @@
"Website": "Website", "Website": "Website",
"Development": "Entwicklung", "Development": "Entwicklung",
" : Development configurations for Boostnote.": " : Entwicklungseinstellungen für Boostnote.", " : Development configurations for Boostnote.": " : Entwicklungseinstellungen für Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "Analytics", "Analytics": "Analytics",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote sammelt anonyme Daten, um die App zu verbessern. Persönliche Informationen, wie z.B. der Inhalt deiner Notizen, werden dabei nicht erfasst.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote sammelt anonyme Daten, um die App zu verbessern. Persönliche Informationen, wie z.B. der Inhalt deiner Notizen, werden dabei nicht erfasst.",

View File

@@ -83,7 +83,7 @@
"Website": "Website", "Website": "Website",
"Development": "Development", "Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.", " : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "Analytics", "Analytics": "Analytics",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.",

View File

@@ -76,7 +76,7 @@
"Website": "Página web", "Website": "Página web",
"Development": "Desarrollo", "Development": "Desarrollo",
" : Development configurations for Boostnote.": " : Configuraciones de desarrollo para Boostnote.", " : Development configurations for Boostnote.": " : Configuraciones de desarrollo para Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licencia: GPL v3", "License: GPL v3": "Licencia: GPL v3",
"Analytics": "Analítica", "Analytics": "Analítica",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote recopila datos anónimos con el único propósito de mejorar la aplicación, y de ninguna manera recopila información personal como el contenido de sus notas.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote recopila datos anónimos con el único propósito de mejorar la aplicación, y de ninguna manera recopila información personal como el contenido de sus notas.",

View File

@@ -76,7 +76,7 @@
"Website": "وبسایت", "Website": "وبسایت",
"Development": "توسعه", "Development": "توسعه",
" : Development configurations for Boostnote.": " : پیکربندی توسعه برای Boostnote.", " : Development configurations for Boostnote.": " : پیکربندی توسعه برای Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "لایسنس: GPL v3", "License: GPL v3": "لایسنس: GPL v3",
"Analytics": "تجزیه و تحلیل", "Analytics": "تجزیه و تحلیل",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Bosstnote اطلاعات ناشناس را برای بهبود عملکرد برنامه جمع آوری می‌کند.اطلاعات شخصی شما مثل محتوای یادداشت ها هرگز برای هیچ هدفی جمع آوری نمی‌شوند", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Bosstnote اطلاعات ناشناس را برای بهبود عملکرد برنامه جمع آوری می‌کند.اطلاعات شخصی شما مثل محتوای یادداشت ها هرگز برای هیچ هدفی جمع آوری نمی‌شوند",

View File

@@ -77,7 +77,7 @@
"Website": "Site web", "Website": "Site web",
"Development": "Développement", "Development": "Développement",
" : Development configurations for Boostnote.": " : Configurations de développement pour Boostnote.", " : Development configurations for Boostnote.": " : Configurations de développement pour Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "Analytics", "Analytics": "Analytics",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collecte des données anonymisées dans le seul but d'améliorer l'application, et ne collecte aucune donnée personnelle telle que le contenu de vos notes.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collecte des données anonymisées dans le seul but d'améliorer l'application, et ne collecte aucune donnée personnelle telle que le contenu de vos notes.",

View File

@@ -82,7 +82,7 @@
"Website": "Weboldal", "Website": "Weboldal",
"Development": "Fejlesztés", "Development": "Fejlesztés",
" : Development configurations for Boostnote.": " : Információk a Boostnote fejlesztéséről.", " : Development configurations for Boostnote.": " : Információk a Boostnote fejlesztéséről.",
"Copyright (C) 2017 - 2018 BoostIO": "Szerzői jog (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Szerzői jog (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licensz: GPL v3", "License: GPL v3": "Licensz: GPL v3",
"Analytics": "Adatok elemzése", "Analytics": "Adatok elemzése",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "A Boostnote névtelen adatokat gyűjt össze az alkalmazás tökéletesítése céljából, és szigorúan nem gyűjt semmilyen személyes adatot, például a jegyzetek tartalmát.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "A Boostnote névtelen adatokat gyűjt össze az alkalmazás tökéletesítése céljából, és szigorúan nem gyűjt semmilyen személyes adatot, például a jegyzetek tartalmát.",

View File

@@ -76,7 +76,7 @@
"Website": "Sito Web", "Website": "Sito Web",
"Development": "Sviluppo", "Development": "Sviluppo",
" : Development configurations for Boostnote.": " : Configurazioni di sviluppo per Boostnote.", " : Development configurations for Boostnote.": " : Configurazioni di sviluppo per Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licenza: GPL v3", "License: GPL v3": "Licenza: GPL v3",
"Analytics": "Statistiche", "Analytics": "Statistiche",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote raccoglie dati anonimi al solo scopo di migliorare l'applicazione, e non raccoglie nessuna informazione personale rigurado il contenuto delle note.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote raccoglie dati anonimi al solo scopo di migliorare l'applicazione, e non raccoglie nessuna informazione personale rigurado il contenuto delle note.",

View File

@@ -106,7 +106,7 @@
"Website": "ウェブサイト", "Website": "ウェブサイト",
"Development": "開発", "Development": "開発",
" : Development configurations for Boostnote.": " : Boostnote の開発構成", " : Development configurations for Boostnote.": " : Boostnote の開発構成",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "ライセンス: GPL v3", "License: GPL v3": "ライセンス: GPL v3",
"Analytics": "解析", "Analytics": "解析",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote はアプリケーションの機能向上だけを目的に匿名データを収集します。ノートの内容を含めた個人の情報は一切収集しません。", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote はアプリケーションの機能向上だけを目的に匿名データを収集します。ノートの内容を含めた個人の情報は一切収集しません。",

View File

@@ -76,7 +76,7 @@
"Website": "웹사이트", "Website": "웹사이트",
"Development": "개발", "Development": "개발",
" : Development configurations for Boostnote.": " : Boostnote 개발을 위한 설정들.", " : Development configurations for Boostnote.": " : Boostnote 개발을 위한 설정들.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "사용 통계/분석", "Analytics": "사용 통계/분석",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote는 서비스개선을 위해 익명으로 데이터를 수집하며 노트의 내용과같은 일체의 개인정보는 수집하지 않습니다.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote는 서비스개선을 위해 익명으로 데이터를 수집하며 노트의 내용과같은 일체의 개인정보는 수집하지 않습니다.",

View File

@@ -76,7 +76,7 @@
"Website": "Website", "Website": "Website",
"Development": "Development", "Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.", " : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "Analytics", "Analytics": "Analytics",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.",

View File

@@ -82,7 +82,7 @@
"Website": "Strona WWW", "Website": "Strona WWW",
"Development": "Development", "Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.", " : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licencja: GPL v3", "License: GPL v3": "Licencja: GPL v3",
"Analytics": "Statystyki", "Analytics": "Statystyki",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote zbiera anonimowe dane wyłącznie w celu poprawy działania aplikacji, lecz nigdy nie pobiera prywatnych danych z Twoich notatek.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote zbiera anonimowe dane wyłącznie w celu poprawy działania aplikacji, lecz nigdy nie pobiera prywatnych danych z Twoich notatek.",

View File

@@ -76,7 +76,7 @@
"Website": "Website", "Website": "Website",
"Development": "Desenvolvimento", "Development": "Desenvolvimento",
" : Development configurations for Boostnote.": " : Configurações de desenvolvimento para o Boostnote.", " : Development configurations for Boostnote.": " : Configurações de desenvolvimento para o Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Direitos Autorais (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Direitos Autorais (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licença: GPL v3", "License: GPL v3": "Licença: GPL v3",
"Analytics": "Técnicas analíticas", "Analytics": "Técnicas analíticas",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "O Boostnote coleta dados anônimos com o único propósito de melhorar o aplicativo e de modo algum coleta qualquer informação pessoal, bem como o conteúdo de suas anotações.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "O Boostnote coleta dados anônimos com o único propósito de melhorar o aplicativo e de modo algum coleta qualquer informação pessoal, bem como o conteúdo de suas anotações.",

View File

@@ -76,7 +76,7 @@
"Website": "Website", "Website": "Website",
"Development": "Desenvolvimento", "Development": "Desenvolvimento",
" : Development configurations for Boostnote.": " : Configurações de desenvolvimento para o Boostnote.", " : Development configurations for Boostnote.": " : Configurações de desenvolvimento para o Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Direitos de Autor (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Direitos de Autor (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licença: GPL v3", "License: GPL v3": "Licença: GPL v3",
"Analytics": "Analíse de Data", "Analytics": "Analíse de Data",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "O Boostnote coleta dados anônimos com o único propósito de melhorar a aplicação e não adquire informação pessoal ou conteúdo das tuas notas.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "O Boostnote coleta dados anônimos com o único propósito de melhorar a aplicação e não adquire informação pessoal ou conteúdo das tuas notas.",

View File

@@ -75,7 +75,7 @@
"Website": "Сайт", "Website": "Сайт",
"Development": "Разработка", "Development": "Разработка",
" : Development configurations for Boostnote.": " : Разработческие конфигурации для Boostnote.", " : Development configurations for Boostnote.": " : Разработческие конфигурации для Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "Аналитика", "Analytics": "Аналитика",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote собирает анонимные данные о пользовании приложением для того, чтобы улучшать пользовательский опыт. Мы не собираем личную информацию и содержание ваших записей.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote собирает анонимные данные о пользовании приложением для того, чтобы улучшать пользовательский опыт. Мы не собираем личную информацию и содержание ваших записей.",

View File

@@ -75,7 +75,7 @@
"Website": "Website", "Website": "Website",
"Development": "Development", "Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.", " : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "Analytics", "Analytics": "Analytics",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.",

View File

@@ -83,7 +83,7 @@
"Website": "เว็บไซต์", "Website": "เว็บไซต์",
"Development": "การพัฒนา", "Development": "การพัฒนา",
" : Development configurations for Boostnote.": " : การตั้งค่าต่างๆสำหรับการพัฒนา Boostnote.", " : Development configurations for Boostnote.": " : การตั้งค่าต่างๆสำหรับการพัฒนา Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "สงวนลิขสิทธิ์ (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "สงวนลิขสิทธิ์ (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "การวิเคราะห์", "Analytics": "การวิเคราะห์",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote จะเก็บข้อมูลแบบไม่ระบุตัวตนเพื่อนำไปใช้ในการปรับปรุงแอพพลิเคชันเท่านั้น, และจะไม่มีการเก็บข้อมูลส่วนตัวใดๆของคุณ เช่น ข้อมูลในโน๊ตของคุณอย่างเด็ดขาด.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote จะเก็บข้อมูลแบบไม่ระบุตัวตนเพื่อนำไปใช้ในการปรับปรุงแอพพลิเคชันเท่านั้น, และจะไม่มีการเก็บข้อมูลส่วนตัวใดๆของคุณ เช่น ข้อมูลในโน๊ตของคุณอย่างเด็ดขาด.",

View File

@@ -75,7 +75,7 @@
"Website": "Websitesi", "Website": "Websitesi",
"Development": "Geliştirme", "Development": "Geliştirme",
" : Development configurations for Boostnote.": " : Boostnote için geliştirme ayarları.", " : Development configurations for Boostnote.": " : Boostnote için geliştirme ayarları.",
"Copyright (C) 2017 - 2018 BoostIO": "Her hakkı saklıdır. (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Her hakkı saklıdır. (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Lisans: GPL v3", "License: GPL v3": "Lisans: GPL v3",
"Analytics": "Analizler", "Analytics": "Analizler",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote, uygulamanın geliştirilmesi amacıyla anonim veriler toplar. Notlarınızın içeriği gibi kişisel bilgiler kesinlikle toplanmaz.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote, uygulamanın geliştirilmesi amacıyla anonim veriler toplar. Notlarınızın içeriği gibi kişisel bilgiler kesinlikle toplanmaz.",

View File

@@ -77,7 +77,7 @@
"Website": "官网", "Website": "官网",
"Development": "开发", "Development": "开发",
" : Development configurations for Boostnote.": " : Boostnote 的开发配置", " : Development configurations for Boostnote.": " : Boostnote 的开发配置",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "分析", "Analytics": "分析",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote 收集匿名数据只为了提升软件使用体验,绝对不收集任何个人信息(包括笔记内容)", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote 收集匿名数据只为了提升软件使用体验,绝对不收集任何个人信息(包括笔记内容)",

View File

@@ -75,7 +75,7 @@
"Website": "官網", "Website": "官網",
"Development": "開發", "Development": "開發",
" : Development configurations for Boostnote.": " : Boostnote 的開發組態", " : Development configurations for Boostnote.": " : Boostnote 的開發組態",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "分析", "Analytics": "分析",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote 收集匿名資料單純只為了提升軟體使用體驗,絕對不收集任何個人資料(包括筆記內容)", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote 收集匿名資料單純只為了提升軟體使用體驗,絕對不收集任何個人資料(包括筆記內容)",

View File

@@ -50,6 +50,7 @@
"homepage": "https://boostnote.io", "homepage": "https://boostnote.io",
"dependencies": { "dependencies": {
"@enyaxu/markdown-it-anchor": "^5.0.2", "@enyaxu/markdown-it-anchor": "^5.0.2",
"@rokt33r/js-sequence-diagrams": "^2.0.6-2",
"@rokt33r/markdown-it-math": "^4.0.1", "@rokt33r/markdown-it-math": "^4.0.1",
"@rokt33r/season": "^5.3.0", "@rokt33r/season": "^5.3.0",
"@susisu/mte-kernel": "^2.0.0", "@susisu/mte-kernel": "^2.0.0",
@@ -72,7 +73,6 @@
"iconv-lite": "^0.4.19", "iconv-lite": "^0.4.19",
"immutable": "^3.8.1", "immutable": "^3.8.1",
"invert-color": "^2.0.0", "invert-color": "^2.0.0",
"js-sequence-diagrams": "^1000000.0.6",
"js-yaml": "^3.12.0", "js-yaml": "^3.12.0",
"katex": "^0.9.0", "katex": "^0.9.0",
"lodash": "^4.11.1", "lodash": "^4.11.1",
@@ -138,7 +138,7 @@
"devtron": "^1.1.0", "devtron": "^1.1.0",
"dom-storage": "^2.0.2", "dom-storage": "^2.0.2",
"electron": "3.0.8", "electron": "3.0.8",
"electron-packager": "^12.0.0", "electron-packager": "^12.2.0",
"eslint": "^3.13.1", "eslint": "^3.13.1",
"eslint-config-standard": "^6.2.1", "eslint-config-standard": "^6.2.1",
"eslint-config-standard-jsx": "^3.2.0", "eslint-config-standard-jsx": "^3.2.0",
@@ -154,6 +154,7 @@
"jest-localstorage-mock": "^2.2.0", "jest-localstorage-mock": "^2.2.0",
"jsdom": "^9.4.2", "jsdom": "^9.4.2",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"markdownlint": "^0.11.0",
"merge-stream": "^1.0.0", "merge-stream": "^1.0.0",
"mock-require": "^3.0.1", "mock-require": "^3.0.1",
"nib": "^1.1.0", "nib": "^1.1.0",

15
tests/lib/utils.test.js Normal file
View File

@@ -0,0 +1,15 @@
import { isMarkdownTitleURL } from '../../browser/lib/utils'
describe('isMarkdownTitleURL', () => {
it('returns true for valid Markdown title with url', () => {
expect(isMarkdownTitleURL('# https://validurl.com')).toBe(true)
expect(isMarkdownTitleURL('## https://validurl.com')).toBe(true)
expect(isMarkdownTitleURL('###### https://validurl.com')).toBe(true)
})
it('returns true for invalid Markdown title with url', () => {
expect(isMarkdownTitleURL('1 https://validurl.com')).toBe(false)
expect(isMarkdownTitleURL('24 https://validurl.com')).toBe(false)
expect(isMarkdownTitleURL('####### https://validurl.com')).toBe(false)
})
})

View File

@@ -71,6 +71,14 @@
pretty-ms "^0.2.1" pretty-ms "^0.2.1"
text-table "^0.2.0" text-table "^0.2.0"
"@rokt33r/js-sequence-diagrams@^2.0.6-2":
version "2.0.6-2"
resolved "https://registry.yarnpkg.com/@rokt33r/js-sequence-diagrams/-/js-sequence-diagrams-2.0.6-2.tgz#fe9c4ad8f70c356873739485d1eff5cf75008821"
integrity sha512-33oibMKJEqCyA83TBeRkc9ifBvoIi2pn/davZuW0PZNbgK7zBkZUdFz1yMPPksA0Rbrxapc9BOwU7xXIACmxhg==
dependencies:
raphael "~2.1.x"
underscore "~1.4.x"
"@rokt33r/markdown-it-math@^4.0.1": "@rokt33r/markdown-it-math@^4.0.1":
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/@rokt33r/markdown-it-math/-/markdown-it-math-4.0.2.tgz#87c7172f459833b05e406cfc846e0c0b7ebc24ef" resolved "https://registry.yarnpkg.com/@rokt33r/markdown-it-math/-/markdown-it-math-4.0.2.tgz#87c7172f459833b05e406cfc846e0c0b7ebc24ef"
@@ -2846,21 +2854,7 @@ electron-config@^1.0.0:
dependencies: dependencies:
conf "^1.0.0" conf "^1.0.0"
electron-download@^4.0.0: electron-download@^4.1.0, electron-download@^4.1.1:
version "4.1.0"
resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.0.tgz#bf932c746f2f87ffcc09d1dd472f2ff6b9187845"
dependencies:
debug "^2.2.0"
env-paths "^1.0.0"
fs-extra "^2.0.0"
minimist "^1.2.0"
nugget "^2.0.0"
path-exists "^3.0.0"
rc "^1.1.2"
semver "^5.3.0"
sumchecker "^2.0.1"
electron-download@^4.1.0:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8" resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8"
dependencies: dependencies:
@@ -2921,13 +2915,13 @@ electron-osx-sign@^0.4.1:
minimist "^1.2.0" minimist "^1.2.0"
plist "^2.1.0" plist "^2.1.0"
electron-packager@^12.0.0: electron-packager@^12.2.0:
version "12.1.0" version "12.2.0"
resolved "https://registry.yarnpkg.com/electron-packager/-/electron-packager-12.1.0.tgz#048dd4ff3848be19c5873c315b5b312df6215328" resolved "https://registry.yarnpkg.com/electron-packager/-/electron-packager-12.2.0.tgz#e38e0702a12e5f62a00a03aabd0b9ad28aebab4b"
dependencies: dependencies:
asar "^0.14.0" asar "^0.14.0"
debug "^3.0.0" debug "^3.0.0"
electron-download "^4.0.0" electron-download "^4.1.1"
electron-osx-sign "^0.4.1" electron-osx-sign "^0.4.1"
extract-zip "^1.0.3" extract-zip "^1.0.3"
fs-extra "^5.0.0" fs-extra "^5.0.0"
@@ -3824,13 +3818,6 @@ fs-extra@^1.0.0:
jsonfile "^2.1.0" jsonfile "^2.1.0"
klaw "^1.0.0" klaw "^1.0.0"
fs-extra@^2.0.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-2.1.2.tgz#046c70163cef9aad46b0e4a7fa467fb22d71de35"
dependencies:
graceful-fs "^4.1.2"
jsonfile "^2.1.0"
fs-extra@^4.0.0, fs-extra@^4.0.1: fs-extra@^4.0.0, fs-extra@^4.0.1:
version "4.0.3" version "4.0.3"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
@@ -5381,13 +5368,6 @@ js-queue@>=2.0.0:
dependencies: dependencies:
easy-stack "^1.0.0" easy-stack "^1.0.0"
js-sequence-diagrams@^1000000.0.6:
version "1000000.0.6"
resolved "https://registry.yarnpkg.com/js-sequence-diagrams/-/js-sequence-diagrams-1000000.0.6.tgz#e95db01420479c5ccbc12046af1da42fde649e5c"
dependencies:
raphael "~2.1.x"
underscore "~1.4.x"
js-string-escape@^1.0.1: js-string-escape@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef"
@@ -6563,7 +6543,7 @@ npmlog@^4.0.2:
gauge "~2.7.3" gauge "~2.7.3"
set-blocking "~2.0.0" set-blocking "~2.0.0"
nugget@^2.0.0, nugget@^2.0.1: nugget@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0" resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0"
dependencies: dependencies:
@@ -7459,6 +7439,7 @@ raphael@2.2.7, raphael@^2.2.7:
raphael@~2.1.x: raphael@~2.1.x:
version "2.1.4" version "2.1.4"
resolved "https://registry.yarnpkg.com/raphael/-/raphael-2.1.4.tgz#b09ca664ad048b814bb2ff5d4d1e75838cab9c97" resolved "https://registry.yarnpkg.com/raphael/-/raphael-2.1.4.tgz#b09ca664ad048b814bb2ff5d4d1e75838cab9c97"
integrity sha1-sJymZK0Ei4FLsv9dTR51g4yrnJc=
dependencies: dependencies:
eve "git://github.com/adobe-webplatform/eve.git#eef80ed" eve "git://github.com/adobe-webplatform/eve.git#eef80ed"
@@ -7471,7 +7452,7 @@ raw-body@2.3.2:
iconv-lite "0.4.19" iconv-lite "0.4.19"
unpipe "1.0.0" unpipe "1.0.0"
rc@^1.0.1, rc@^1.1.2, rc@^1.1.6, rc@^1.1.7, rc@^1.2.1: rc@^1.0.1, rc@^1.1.6, rc@^1.1.7, rc@^1.2.1:
version "1.2.8" version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
dependencies: dependencies:
@@ -8735,7 +8716,7 @@ stylus@^0.52.4:
sax "0.5.x" sax "0.5.x"
source-map "0.1.x" source-map "0.1.x"
sumchecker@^2.0.1, sumchecker@^2.0.2: sumchecker@^2.0.2:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e"
dependencies: dependencies:
@@ -9114,6 +9095,7 @@ underscore.string@~2.4.0:
underscore@~1.4.x: underscore@~1.4.x:
version "1.4.4" version "1.4.4"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604"
integrity sha1-YaajIBBiKvoHljvzJSA88SI51gQ=
underscore@~1.6.0: underscore@~1.6.0:
version "1.6.0" version "1.6.0"