1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-14 10:16:26 +00:00

Compare commits

..

5 Commits

Author SHA1 Message Date
Masahide Morio
9d98f0cb03 for v0.9 2018-02-04 05:51:39 +09:00
Masahide Morio
3503233631 Merge remote-tracking branch 'upstream/master' 2018-02-04 05:40:24 +09:00
Masahide Morio
c39393c453 for Windows 32bit 2018-02-04 05:40:15 +09:00
Masahide Morio
564cc80ef7 for Windows 32bit 2018-01-17 01:01:45 +09:00
Masahide.MORIO
77f7144fbf test 2018-01-16 10:01:20 +09:00
40 changed files with 1280 additions and 863 deletions

View File

@@ -17,3 +17,4 @@ deploy:
script: if [ ${TRAVIS_NODE_VERSION} = "stable" ];then docker run -v $(pwd):$(pwd) -t snapcore/snapcraft sh -c "apt update -qq
&& cd $(pwd) && snapcraft && snapcraft push *.snap --release edge"; fi
skip_cleanup: true

View File

@@ -2,7 +2,7 @@ GPL-3.0
Boostnote - an open source note-taking app made for programmers just like you.
Copyright (C) 2017 - 2018 BoostIO
Copyright (C) 2017 Maisin&Co., Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -7,7 +7,6 @@ import path from 'path'
import copyImage from 'browser/main/lib/dataApi/copyImage'
import { findStorage } from 'browser/lib/findStorage'
import fs from 'fs'
import eventEmitter from 'browser/main/lib/eventEmitter'
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
@@ -48,40 +47,6 @@ export default class CodeEditor extends React.Component {
this.loadStyleHandler = (e) => {
this.editor.refresh()
}
this.searchHandler = (e, msg) => this.handleSearch(msg)
this.searchState = null
}
handleSearch (msg) {
const cm = this.editor
const component = this
if (component.searchState) cm.removeOverlay(component.searchState)
if (msg.length < 3) return
cm.operation(function () {
component.searchState = makeOverlay(msg, 'searching')
cm.addOverlay(component.searchState)
function makeOverlay (query, style) {
query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), 'gi')
return {
token: function (stream) {
query.lastIndex = stream.pos
var match = query.exec(stream.string)
if (match && match.index === stream.pos) {
stream.pos += match[0].length || 1
return style
} else if (match) {
stream.pos = match.index
} else {
stream.skipToEnd()
}
}
}
}
})
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
}
componentDidMount () {
@@ -127,7 +92,7 @@ export default class CodeEditor extends React.Component {
'Cmd-T': function (cm) {
// Do nothing
},
Enter: 'boostNewLineAndIndentContinueMarkdownList',
Enter: 'newlineAndIndentContinueMarkdownList',
'Ctrl-C': (cm) => {
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
document.execCommand('copy')
@@ -142,10 +107,6 @@ export default class CodeEditor extends React.Component {
this.editor.on('blur', this.blurHandler)
this.editor.on('change', this.changeHandler)
this.editor.on('paste', this.pasteHandler)
eventEmitter.on('top:search', this.searchHandler)
eventEmitter.emit('code:init')
this.editor.on('scroll', this.scrollHandler)
const editorTheme = document.getElementById('editorTheme')
editorTheme.addEventListener('load', this.loadStyleHandler)
@@ -165,8 +126,6 @@ export default class CodeEditor extends React.Component {
this.editor.off('blur', this.blurHandler)
this.editor.off('change', this.changeHandler)
this.editor.off('paste', this.pasteHandler)
eventEmitter.off('top:search', this.searchHandler)
this.editor.off('scroll', this.scrollHandler)
const editorTheme = document.getElementById('editorTheme')
editorTheme.removeEventListener('load', this.loadStyleHandler)
}
@@ -272,16 +231,11 @@ export default class CodeEditor extends React.Component {
}
handlePaste (editor, e) {
const clipboardData = e.clipboardData
const dataTransferItem = clipboardData.items[0]
const pastedTxt = clipboardData.getData('text')
const isURL = (str) => {
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
return matcher.test(str)
}
if (dataTransferItem.type.match('image')) {
const dataTransferItem = e.clipboardData.items[0]
if (!dataTransferItem.type.match('image')) return
const blob = dataTransferItem.getAsFile()
const reader = new FileReader()
const reader = new window.FileReader()
let base64data
reader.readAsDataURL(blob)
@@ -298,41 +252,6 @@ export default class CodeEditor extends React.Component {
const imageMd = `![${imageName}](${path.join('/:storage', `${imageName}.png`)})`
this.insertImageMd(imageMd)
}
} else if (this.props.fetchUrlTitle && isURL(pastedTxt)) {
this.handlePasteUrl(e, editor, pastedTxt)
}
}
handleScroll (e) {
if (this.props.onScroll) {
this.props.onScroll(e)
}
}
handlePasteUrl (e, editor, pastedTxt) {
e.preventDefault()
const taggedUrl = `<${pastedTxt}>`
editor.replaceSelection(taggedUrl)
fetch(pastedTxt, {
method: 'get'
}).then((response) => {
return (response.text())
}).then((response) => {
const parsedResponse = (new window.DOMParser()).parseFromString(response, 'text/html')
const value = editor.getValue()
const cursor = editor.getCursor()
const LinkWithTitle = `[${parsedResponse.title}](${pastedTxt})`
const newValue = value.replace(taggedUrl, LinkWithTitle)
editor.setValue(newValue)
editor.setCursor(cursor)
}).catch((e) => {
const value = editor.getValue()
const newValue = value.replace(taggedUrl, pastedTxt)
const cursor = editor.getCursor()
editor.setValue(newValue)
editor.setCursor(cursor)
})
}
render () {

View File

@@ -92,9 +92,7 @@ class MarkdownEditor extends React.Component {
if (this.state.isLocked) return
this.setState({ keyPressed: new Set() })
const { config } = this.props
if (config.editor.switchPreview === 'BLUR' ||
(config.editor.switchPreview === 'DBL_CLICK' && this.state.status === 'CODE')
) {
if (config.editor.switchPreview === 'BLUR') {
const cursorPosition = this.refs.code.editor.getCursor()
this.setState({
status: 'PREVIEW'
@@ -106,20 +104,6 @@ class MarkdownEditor extends React.Component {
}
}
handleDoubleClick (e) {
if (this.state.isLocked) return
this.setState({keyPressed: new Set()})
const { config } = this.props
if (config.editor.switchPreview === 'DBL_CLICK') {
this.setState({
status: 'CODE'
}, () => {
this.refs.code.focus()
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
})
}
}
handlePreviewMouseDown (e) {
this.previewMouseDownedAt = new Date()
}
@@ -261,7 +245,6 @@ class MarkdownEditor extends React.Component {
displayLineNumbers={config.editor.displayLineNumbers}
scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey}
fetchUrlTitle={config.editor.fetchUrlTitle}
onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)}
/>
@@ -281,7 +264,6 @@ class MarkdownEditor extends React.Component {
scrollPastEnd={config.preview.scrollPastEnd}
ref='preview'
onContextMenu={(e) => this.handleContextMenu(e)}
onDoubleClick={(e) => this.handleDoubleClick(e)}
tabIndex='0'
value={this.state.renderValue}
onMouseUp={(e) => this.handlePreviewMouseUp(e)}

95
browser/components/MarkdownPreview.js Executable file → Normal file
View File

@@ -9,10 +9,10 @@ import Raphael from 'raphael'
import flowchart from 'flowchart'
import SequenceDiagram from 'js-sequence-diagrams'
import eventEmitter from 'browser/main/lib/eventEmitter'
import fs from 'fs'
import htmlTextHelper from 'browser/lib/htmlTextHelper'
import copy from 'copy-to-clipboard'
import mdurl from 'mdurl'
import exportNote from 'browser/main/lib/dataApi/exportNote'
const { remote } = require('electron')
const { app } = remote
@@ -23,10 +23,6 @@ const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
const appPath = 'file://' + (process.env.NODE_ENV === 'production'
? app.getAppPath()
: path.resolve())
const CSS_FILES = [
`${appPath}/node_modules/katex/dist/katex.min.css`,
`${appPath}/node_modules/codemirror/lib/codemirror.css`
]
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd) {
return `
@@ -120,8 +116,6 @@ export default class MarkdownPreview extends React.Component {
this.contextMenuHandler = (e) => this.handleContextMenu(e)
this.mouseDownHandler = (e) => this.handleMouseDown(e)
this.mouseUpHandler = (e) => this.handleMouseUp(e)
this.DoubleClickHandler = (e) => this.handleDoubleClick(e)
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
this.saveAsTextHandler = () => this.handleSaveAsText()
@@ -152,21 +146,13 @@ export default class MarkdownPreview extends React.Component {
this.props.onCheckboxClick(e)
}
handleScroll (e) {
if (this.props.onScroll) {
this.props.onScroll(e)
}
}
handleContextMenu (e) {
if (!this.props.onContextMenu) return
this.props.onContextMenu(e)
}
handleDoubleClick (e) {
if (this.props.onDoubleClick != null) this.props.onDoubleClick(e)
}
handleMouseDown (e) {
if (!this.props.onMouseDown) return
if (e.target != null) {
switch (e.target.tagName) {
case 'A':
@@ -194,33 +180,8 @@ export default class MarkdownPreview extends React.Component {
}
handleSaveAsHtml () {
this.exportAsDocument('html', (noteContent, exportTasks) => {
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams()
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber)
const body = markdown.render(noteContent)
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
files.forEach((file) => {
file = file.replace('file://', '')
exportTasks.push({
src: file,
dst: 'css'
})
})
let styles = ''
files.forEach((file) => {
styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
})
return `<html>
<head>
<style id="style">${inlineStyles}</style>
${styles}
</head>
<body>${body}</body>
</html>`
this.exportAsDocument('html', (value) => {
return this.refs.root.contentWindow.document.documentElement.outerHTML
})
}
@@ -228,26 +189,20 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.contentWindow.print()
}
exportAsDocument (fileType, contentFormatter) {
exportAsDocument (fileType, formatter) {
const options = {
filters: [
{ name: 'Documents', extensions: [fileType] }
],
properties: ['openFile', 'createDirectory']
}
const value = formatter ? formatter.call(this, this.props.value) : this.props.value
dialog.showSaveDialog(remote.getCurrentWindow(), options,
(filename) => {
if (filename) {
const content = this.props.value
const storage = this.props.storagePath
exportNote(storage, content, filename, contentFormatter)
.then((res) => {
dialog.showMessageBox(remote.getCurrentWindow(), {type: 'info', message: `Exported to ${filename}`})
}).catch((err) => {
dialog.showErrorBox('Export error', err ? err.message || err : 'Unexpected error during export')
throw err
fs.writeFile(filename, value, (err) => {
if (err) throw err
})
}
})
@@ -267,26 +222,20 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.setAttribute('sandbox', 'allow-scripts')
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
let styles = `
this.refs.root.contentWindow.document.head.innerHTML = `
<style id='style'></style>
<link rel="stylesheet" href="${appPath}/node_modules/katex/dist/katex.min.css">
<link rel="stylesheet" href="${appPath}/node_modules/codemirror/lib/codemirror.css">
<link rel="stylesheet" id="codeTheme">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
`
CSS_FILES.forEach((file) => {
styles += `<link rel="stylesheet" href="${file}">`
})
this.refs.root.contentWindow.document.head.innerHTML = styles
this.rewriteIframe()
this.applyStyle()
this.refs.root.contentWindow.document.addEventListener('mousedown', this.mouseDownHandler)
this.refs.root.contentWindow.document.addEventListener('mouseup', this.mouseUpHandler)
this.refs.root.contentWindow.document.addEventListener('dblclick', this.DoubleClickHandler)
this.refs.root.contentWindow.document.addEventListener('drop', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.addEventListener('scroll', this.scrollHandler)
eventEmitter.on('export:save-text', this.saveAsTextHandler)
eventEmitter.on('export:save-md', this.saveAsMdHandler)
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
@@ -297,10 +246,8 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.contentWindow.document.body.removeEventListener('contextmenu', this.contextMenuHandler)
this.refs.root.contentWindow.document.removeEventListener('mousedown', this.mouseDownHandler)
this.refs.root.contentWindow.document.removeEventListener('mouseup', this.mouseUpHandler)
this.refs.root.contentWindow.document.removeEventListener('dblclick', this.DoubleClickHandler)
this.refs.root.contentWindow.document.removeEventListener('drop', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.removeEventListener('scroll', this.scrollHandler)
eventEmitter.off('export:save-text', this.saveAsTextHandler)
eventEmitter.off('export:save-md', this.saveAsMdHandler)
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
@@ -322,7 +269,7 @@ export default class MarkdownPreview extends React.Component {
}
}
getStyleParams () {
applyStyle () {
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd } = this.props
let { fontFamily, codeBlockFontFamily } = this.props
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
@@ -332,21 +279,15 @@ export default class MarkdownPreview extends React.Component {
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
: defaultCodeBlockFontFamily
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd}
}
applyStyle () {
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd} = this.getStyleParams()
this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme)
this.setCodeTheme(codeBlockTheme)
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd)
}
GetCodeThemeLink (theme) {
setCodeTheme (theme) {
theme = consts.THEMES.some((_theme) => _theme === theme) && theme !== 'default'
? theme
: 'elegant'
return theme.startsWith('solarized')
this.getWindow().document.getElementById('codeTheme').href = theme.startsWith('solarized')
? `${appPath}/node_modules/codemirror/theme/solarized.css`
: `${appPath}/node_modules/codemirror/theme/${theme}.css`
}
@@ -376,6 +317,10 @@ export default class MarkdownPreview extends React.Component {
}
this.refs.root.contentWindow.document.body.innerHTML = markdown.render(value)
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.taskListItem'), (el) => {
el.parentNode.parentNode.style.listStyleType = 'none'
})
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
this.fixDecodedURI(el)
el.addEventListener('click', this.anchorClickHandler)

View File

@@ -2,7 +2,6 @@ import React from 'react'
import CodeEditor from 'browser/components/CodeEditor'
import MarkdownPreview from 'browser/components/MarkdownPreview'
import { findStorage } from 'browser/lib/findStorage'
import _ from 'lodash'
import styles from './MarkdownSplitEditor.styl'
import CSSModules from 'browser/lib/CSSModules'
@@ -13,7 +12,6 @@ class MarkdownSplitEditor extends React.Component {
this.value = props.value
this.focus = () => this.refs.code.focus()
this.reload = () => this.refs.code.reload()
this.userScroll = true
}
handleOnChange () {
@@ -21,49 +19,6 @@ class MarkdownSplitEditor extends React.Component {
this.props.onChange()
}
handleScroll (e) {
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
const codeDoc = _.get(this, 'refs.code.editor.doc')
let srcTop, srcHeight, targetTop, targetHeight
if (this.userScroll) {
if (e.doc) {
srcTop = _.get(e, 'doc.scrollTop')
srcHeight = _.get(e, 'doc.height')
targetTop = _.get(previewDoc, 'body.scrollTop')
targetHeight = _.get(previewDoc, 'body.scrollHeight')
} else {
srcTop = _.get(previewDoc, 'body.scrollTop')
srcHeight = _.get(previewDoc, 'body.scrollHeight')
targetTop = _.get(codeDoc, 'scrollTop')
targetHeight = _.get(codeDoc, 'height')
}
const distance = (targetHeight * srcTop / srcHeight) - targetTop
const framerate = 1000 / 60
const frames = 20
const refractory = frames * framerate
this.userScroll = false
let frame = 0
let scrollPos, time
const timer = setInterval(() => {
time = frame / frames
scrollPos = time < 0.5
? 2 * time * time // ease in
: -1 + (4 - 2 * time) * time // ease out
if (e.doc) _.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
else _.get(this, 'refs.code.editor').scrollTo(0, targetTop + scrollPos * distance)
if (frame >= frames) {
clearInterval(timer)
setTimeout(() => { this.userScroll = true }, refractory)
}
frame++
}, framerate)
}
}
handleCheckboxClick (e) {
e.preventDefault()
e.stopPropagation()
@@ -111,10 +66,8 @@ class MarkdownSplitEditor extends React.Component {
indentType={config.editor.indentType}
indentSize={editorIndentSize}
scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
storageKey={storageKey}
onChange={this.handleOnChange.bind(this)}
onScroll={this.handleScroll.bind(this)}
/>
<MarkdownPreview
style={previewStyle}
@@ -131,7 +84,6 @@ class MarkdownSplitEditor extends React.Component {
tabInde='0'
value={value}
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
onScroll={this.handleScroll.bind(this)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
/>

View File

@@ -46,18 +46,7 @@ const TagElementList = (tags) => {
* @param {Function} handleDragStart
* @param {string} dateDisplay
*/
const NoteItem = ({
isActive,
note,
dateDisplay,
handleNoteClick,
handleNoteContextMenu,
handleDragStart,
pathname,
storageName,
folderName,
viewType
}) => (
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
<div styleName={isActive
? 'item--active'
: 'item'
@@ -79,24 +68,8 @@ const NoteItem = ({
: <span styleName='item-title-empty'>Empty</span>
}
</div>
{['ALL', 'STORAGE'].includes(viewType) && <div styleName='item-middle'>
<div styleName='item-middle-time'>{dateDisplay}</div>
<div styleName='item-middle-app-meta'>
<div title={viewType === 'ALL' ? storageName : viewType === 'STORAGE' ? folderName : null} styleName='item-middle-app-meta-label'>
{viewType === 'ALL' && storageName}
{viewType === 'STORAGE' && folderName}
</div>
</div>
</div>}
<div styleName='item-bottom'>
<div styleName='item-bottom-tagList'>
{note.tags.length > 0
? TagElementList(note.tags)
: <span style={{ fontStyle: 'italic', opacity: 0.5 }} styleName='item-bottom-tagList-empty'>No tags</span>
}
</div>
<div>
<div styleName='item-bottom-time'>{dateDisplay}</div>
{note.isStarred
? <img styleName='item-star' src='../resources/icon/icon-starred.svg' /> : ''
}
@@ -107,6 +80,12 @@ const NoteItem = ({
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
: ''
}
<div styleName='item-bottom'>
<div styleName='item-bottom-tagList'>
{note.tags.length > 0
? TagElementList(note.tags)
: <span styleName='item-bottom-tagList-empty' />
}
</div>
</div>
</div>

View File

@@ -90,26 +90,6 @@ $control-height = 30px
font-weight normal
color $ui-inactive-text-color
.item-middle
font-size 13px
padding-left 2px
padding-bottom 2px
.item-middle-time
color $ui-inactive-text-color
display inline-block
.item-middle-app-meta
float right
.item-middle-app-meta-label
opacity 0.4
color $ui-inactive-text-color
padding 0 3px
white-space nowrap
text-overflow ellipsis
overflow hidden
max-width 200px
.item-bottom
position relative
bottom 0px
@@ -144,7 +124,9 @@ $control-height = 30px
padding-bottom 2px
.item-star
position relative
position absolute
right -6px
bottom 23px
width 16px
height 16px
color alpha($ui-favorite-star-button-color, 60%)
@@ -153,7 +135,9 @@ $control-height = 30px
border-radius 17px
.item-pin
position relative
position absolute
right 0px
bottom 2px
width 34px
height 34px
color #E54D42

View File

@@ -14,16 +14,7 @@ import styles from './NoteItemSimple.styl'
* @param {Function} handleNoteContextMenu
* @param {Function} handleDragStart
*/
const NoteItemSimple = ({
isActive,
isAllNotesView,
note,
handleNoteClick,
handleNoteContextMenu,
handleDragStart,
pathname,
storage
}) => (
const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
<div styleName={isActive
? 'item-simple--active'
: 'item-simple'
@@ -39,7 +30,7 @@ const NoteItemSimple = ({
? <i styleName='item-simple-title-icon' className='fa fa-fw fa-code' />
: <i styleName='item-simple-title-icon' className='fa fa-fw fa-file-text-o' />
}
{note.isPinned && !pathname.match(/\/starred|\/trash/)
{note.isPinned && !pathname.match(/\/home|\/starred|\/trash/)
? <i styleName='item-pin' className='fa fa-thumb-tack' />
: ''
}
@@ -47,11 +38,6 @@ const NoteItemSimple = ({
? note.title
: <span styleName='item-simple-title-empty'>Empty</span>
}
{isAllNotesView && <div styleName='item-simple-right'>
<span styleName='item-simple-right-storageName'>
{storage.name}
</span>
</div>}
</div>
</div>
)

View File

@@ -207,8 +207,3 @@ body[data-theme="solarized-dark"]
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#fff, 20%)
.item-simple-right
float right
.item-simple-right-storageName
padding-left 4px
opacity 0.4

View File

@@ -17,7 +17,7 @@ import styles from './SideNavFilter.styl'
const SideNavFilter = ({
isFolded, isHomeActive, handleAllNotesButtonClick,
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote,
counterTotalNote, counterStarredNote, handleFilterButtonContextMenu
counterTotalNote, counterStarredNote
}) => (
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
@@ -59,7 +59,7 @@ const SideNavFilter = ({
}
/>
</div>
<span onContextMenu={handleFilterButtonContextMenu} styleName='menu-button-label'>Trash</span>
<span styleName='menu-button-label'>Trash</span>
<span styleName='counters'>{counterDelNote}</span>
</button>

View File

@@ -12,11 +12,10 @@ import CSSModules from 'browser/lib/CSSModules'
* @param {bool} isActive
*/
const TagListItem = ({name, handleClickTagListItem, isActive, count}) => (
const TagListItem = ({name, handleClickTagListItem, isActive}) => (
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
<span styleName='tagList-item-name'>
{`# ${name}`}
<span styleName='tagList-item-count'> {count}</span>
</span>
</button>
)

View File

@@ -48,9 +48,6 @@
overflow hidden
text-overflow ellipsis
.tagList-item-count
padding 0 3px
body[data-theme="white"]
.tagList-item
color $ui-inactive-text-color
@@ -66,8 +63,6 @@ body[data-theme="white"]
color $ui-text-color
&:hover
background-color alpha($ui-button--active-backgroundColor, 60%)
.tagList-item-count
color $ui-text-color
body[data-theme="dark"]
.tagList-item
@@ -87,5 +82,3 @@ body[data-theme="dark"]
&:hover
color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
.tagList-item-count
color $ui-dark-button--active-color

View File

@@ -76,7 +76,7 @@ body
justify-content left
li
label.taskListItem
margin-left -1.8em
margin-left -2em
&.checked
text-decoration line-through
opacity 0.5
@@ -178,8 +178,6 @@ ul
margin-bottom 1em
li
display list-item
&.taskListItem
list-style none
p
margin 0
&>li>ul, &>li>ol
@@ -220,7 +218,6 @@ pre
background-color white
&.CodeMirror
height initial
flex-wrap wrap
&>code
flex 1
overflow-x auto
@@ -230,13 +227,6 @@ pre
padding 0
border none
border-radius 0
&>span.filename
width 100%
border-radius: 5px 0px 0px 0px
margin -8px 100% 8px -8px
padding 0px 6px
background-color #777;
color white
&>span.lineNumber
display none
font-size 1em

View File

@@ -3,17 +3,15 @@ import emoji from 'markdown-it-emoji'
import math from '@rokt33r/markdown-it-math'
import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager'
import {lastFindInArray} from './utils'
// FIXME We should not depend on global variable.
const katex = window.katex
const config = ConfigManager.get()
function createGutter (str, firstLineNumber) {
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
const lastLineNumber = (str.match(/\n/g) || []).length + firstLineNumber - 1
function createGutter (str) {
const lc = (str.match(/\n/g) || []).length
const lines = []
for (let i = firstLineNumber; i <= lastLineNumber; i++) {
for (let i = 1; i <= lc; i++) {
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
}
return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
@@ -26,22 +24,15 @@ var md = markdownit({
xhtmlOut: true,
breaks: true,
highlight: function (str, lang) {
const delimiter = ':'
const langInfo = lang.split(delimiter)
const langType = langInfo[0]
const fileName = langInfo[1] || ''
const firstLineNumber = parseInt(langInfo[2], 10)
if (langType === 'flowchart') {
if (lang === 'flowchart') {
return `<pre class="flowchart">${str}</pre>`
}
if (langType === 'sequence') {
if (lang === 'sequence') {
return `<pre class="sequence">${str}</pre>`
}
return '<pre class="code">' +
'<span class="filename">' + fileName + '</span>' +
createGutter(str, firstLineNumber) +
'<code class="' + langType + '">' +
createGutter(str) +
'<code class="' + lang + '">' +
str +
'</code></pre>'
}
@@ -134,13 +125,6 @@ md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
if (state.parentType === 'list') {
const match = content.match(/^\[( |x)\] ?(.+)/i)
if (match) {
const liToken = lastFindInArray(state.tokens, token => token.type === 'list_item_open')
if (liToken) {
if (!liToken.attrs) {
liToken.attrs = []
}
liToken.attrs.push(['class', 'taskListItem'])
}
content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
}
}

View File

@@ -1,11 +0,0 @@
export function lastFindInArray (array, callback) {
for (let i = array.length - 1; i >= 0; --i) {
if (callback(array[i], i, array)) {
return array[i]
}
}
}
export default {
lastFindInArray
}

18
browser/main/Detail/MarkdownNoteDetail.js Executable file → Normal file
View File

@@ -19,7 +19,6 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import ConfigManager from 'browser/main/lib/ConfigManager'
import TrashButton from './TrashButton'
import FullscreenButton from './FullscreenButton'
import RestoreButton from './RestoreButton'
import PermanentDeleteButton from './PermanentDeleteButton'
import InfoButton from './InfoButton'
import ToggleModeButton from './ToggleModeButton'
@@ -69,10 +68,13 @@ class MarkdownNoteDetail extends React.Component {
}
componentWillUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
if (this.saveQueue != null) this.saveNow()
}
componentDidUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
}
handleUpdateTag () {
const { note } = this.state
if (this.refs.tags) note.tags = this.refs.tags.value
@@ -196,9 +198,8 @@ class MarkdownNoteDetail extends React.Component {
noteKey: data.noteKey
})
}
ee.once('list:next', dispatchHandler)
ee.once('list:moved', dispatchHandler)
})
.then(() => ee.emit('list:next'))
}
} else {
if (confirmDeletion()) {
@@ -322,7 +323,10 @@ class MarkdownNoteDetail extends React.Component {
const trashTopBar = <div styleName='info'>
<div styleName='info-left'>
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
<i styleName='undo-button'
className='fa fa-undo fa-fw'
onClick={(e) => this.handleUndoButtonClick(e)}
/>
</div>
<div styleName='info-right'>
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -357,10 +361,12 @@ class MarkdownNoteDetail extends React.Component {
value={this.state.note.tags}
onChange={this.handleUpdateTag.bind(this)}
/>
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
</div>
<div styleName='info-right'>
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
<StarButton
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}

View File

@@ -7,7 +7,6 @@
background-color $ui-noteDetail-backgroundColor
box-shadow none
padding 20px 40px
overflow hidden
.lock-button
padding-bottom 3px

View File

@@ -1,21 +0,0 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './RestoreButton.styl'
const RestoreButton = ({
onClick
}) => (
<button styleName='control-restoreButton'
onClick={onClick}
>
<i className='fa fa-undo fa-fw' styleName='iconRestore' />
<span styleName='tooltip'>Restore</span>
</button>
)
RestoreButton.propTypes = {
onClick: PropTypes.func.isRequired
}
export default CSSModules(RestoreButton, styles)

View File

@@ -1,22 +0,0 @@
.control-restoreButton
top 115px
topBarButtonRight()
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 50px
left 25px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
body[data-theme="dark"]
.control-restoreButton
topBarButtonDark()

View File

@@ -20,7 +20,6 @@ import _ from 'lodash'
import { findNoteTitle } from 'browser/lib/findNoteTitle'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import TrashButton from './TrashButton'
import RestoreButton from './RestoreButton'
import PermanentDeleteButton from './PermanentDeleteButton'
import InfoButton from './InfoButton'
import InfoPanel from './InfoPanel'
@@ -192,9 +191,8 @@ class SnippetNoteDetail extends React.Component {
noteKey: data.noteKey
})
}
ee.once('list:next', dispatchHandler)
ee.once('list:moved', dispatchHandler)
})
.then(() => ee.emit('list:next'))
}
} else {
if (confirmDeletion()) {
@@ -569,7 +567,6 @@ class SnippetNoteDetail extends React.Component {
displayLineNumbers={config.editor.displayLineNumbers}
keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index}
/>
@@ -590,7 +587,10 @@ class SnippetNoteDetail extends React.Component {
const trashTopBar = <div styleName='info'>
<div styleName='info-left'>
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
<i styleName='undo-button'
className='fa fa-undo fa-fw'
onClick={(e) => this.handleUndoButtonClick(e)}
/>
</div>
<div styleName='info-right'>
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />

View File

@@ -5,8 +5,8 @@
width 52px
display flex
align-items center
position: relative
top 2px
position absolute
right 165px
.active
background-color #1EC38B
width 33px

View File

@@ -66,10 +66,6 @@ class NoteList extends React.Component {
this.deleteNote = this.deleteNote.bind(this)
this.focusNote = this.focusNote.bind(this)
this.pinToTop = this.pinToTop.bind(this)
this.getNoteStorage = this.getNoteStorage.bind(this)
this.getNoteFolder = this.getNoteFolder.bind(this)
this.getViewType = this.getViewType.bind(this)
this.restoreNote = this.restoreNote.bind(this)
// TODO: not Selected noteKeys but SelectedNote(for reusing)
this.state = {
@@ -113,27 +109,14 @@ class NoteList extends React.Component {
componentDidUpdate (prevProps) {
const { location } = this.props
const { selectedNoteKeys } = this.state
const visibleNoteKeys = this.notes.map(note => `${note.storage}-${note.key}`)
const note = this.notes[0]
const prevKey = prevProps.location.query.key
const noteKey = visibleNoteKeys.includes(prevKey) ? prevKey : note && `${note.storage}-${note.key}`
if (note && location.query.key == null) {
if (this.notes.length > 0 && location.query.key == null) {
const { router } = this.context
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
// A visible note is an active note
if (!selectedNoteKeys.includes(noteKey)) {
if (selectedNoteKeys.length === 1) selectedNoteKeys.pop()
selectedNoteKeys.push(noteKey)
ee.emit('list:moved')
}
router.replace({
pathname: location.pathname,
query: {
key: noteKey
key: this.notes[0].storage + '-' + this.notes[0].key
}
})
return
@@ -457,23 +440,14 @@ class NoteList extends React.Component {
const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top'
const deleteLabel = 'Delete Note'
const cloneNote = 'Clone Note'
const restoreNote = 'Restore Note'
const menu = new Menu()
if (!location.pathname.match(/\/starred|\/trash/)) {
if (!location.pathname.match(/\/home|\/starred|\/trash/)) {
menu.append(new MenuItem({
label: pinLabel,
click: this.pinToTop
}))
}
if (location.pathname.match(/\/trash/)) {
menu.append(new MenuItem({
label: restoreNote,
click: this.restoreNote
}))
}
menu.append(new MenuItem({
label: deleteLabel,
click: this.deleteNote
@@ -485,20 +459,15 @@ class NoteList extends React.Component {
menu.popup()
}
updateSelectedNotes (updateFunc, cleanSelection = true) {
pinToTop () {
const { selectedNoteKeys } = this.state
const { dispatch } = this.props
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
if (!_.isFunction(updateFunc)) {
console.warn('Update function is not defined. No update will happen')
updateFunc = (note) => { return note }
}
Promise.all(
selectedNotes.map((note) => {
note = updateFunc(note)
note.isPinned = !note.isPinned
return dataApi
.updateNote(note.storage, note.key, note)
})
@@ -511,24 +480,7 @@ class NoteList extends React.Component {
})
})
})
if (cleanSelection) {
this.selectNextNote()
}
}
pinToTop () {
this.updateSelectedNotes((note) => {
note.isPinned = !note.isPinned
return note
})
}
restoreNote () {
this.updateSelectedNotes((note) => {
note.isTrashed = false
return note
})
this.setState({ selectedNoteKeys: [] })
}
deleteNote () {
@@ -726,24 +678,6 @@ class NoteList extends React.Component {
})
}
getNoteStorage (note) { // note.storage = storage key
return this.props.data.storageMap.toJS()[note.storage]
}
getNoteFolder (note) { // note.folder = folder key
return _.find(this.getNoteStorage(note).folders, ({ key }) => key === note.folder)
}
getViewType () {
const { pathname } = this.props.location
const folder = /\/folders\/[a-zA-Z0-9]+/.test(pathname)
const storage = /\/storages\/[a-zA-Z0-9]+/.test(pathname) && !folder
const allNotes = pathname === '/home'
if (allNotes) return 'ALL'
if (folder) return 'FOLDER'
if (storage) return 'STORAGE'
}
render () {
const { location, config } = this.props
let { notes } = this.props
@@ -753,7 +687,7 @@ class NoteList extends React.Component {
: config.sortBy === 'ALPHABETICAL'
? sortByAlphabetical
: sortByUpdatedAt
const sortedNotes = location.pathname.match(/\/starred|\/trash/)
const sortedNotes = location.pathname.match(/\/home|\/starred|\/trash/)
? this.getNotes().sort(sortFunc)
: this.sortByPin(this.getNotes().sort(sortFunc))
this.notes = notes = sortedNotes.filter((note) => {
@@ -780,8 +714,6 @@ class NoteList extends React.Component {
}
})
const viewType = this.getViewType()
const noteList = notes
.map(note => {
if (note == null) {
@@ -807,9 +739,6 @@ class NoteList extends React.Component {
handleNoteClick={this.handleNoteClick.bind(this)}
handleDragStart={this.handleDragStart.bind(this)}
pathname={location.pathname}
folderName={this.getNoteFolder(note).name}
storageName={this.getNoteStorage(note).name}
viewType={viewType}
/>
)
}
@@ -823,9 +752,6 @@ class NoteList extends React.Component {
handleNoteClick={this.handleNoteClick.bind(this)}
handleDragStart={this.handleDragStart.bind(this)}
pathname={location.pathname}
folderName={this.getNoteFolder(note).name}
storageName={this.getNoteStorage(note).name}
viewType={viewType}
/>
)
})
@@ -840,17 +766,16 @@ class NoteList extends React.Component {
<div styleName='control-sortBy'>
<i className='fa fa-angle-down' />
<select styleName='control-sortBy-select'
title='Select filter mode'
value={config.sortBy}
onChange={(e) => this.handleSortByChange(e)}
>
<option title='Sort by update time' value='UPDATED_AT'>Updated</option>
<option title='Sort by create time' value='CREATED_AT'>Created</option>
<option title='Sort alphabetically' value='ALPHABETICAL'>Alphabetically</option>
<option value='UPDATED_AT'>Updated</option>
<option value='CREATED_AT'>Created</option>
<option value='ALPHABETICAL'>Alphabetically</option>
</select>
</div>
<div styleName='control-button-area'>
<button title='Default View' styleName={config.listStyle === 'DEFAULT'
<button styleName={config.listStyle === 'DEFAULT'
? 'control-button--active'
: 'control-button'
}
@@ -858,7 +783,7 @@ class NoteList extends React.Component {
>
<img styleName='iconTag' src='../resources/icon/icon-column.svg' />
</button>
<button title='Compressed View' styleName={config.listStyle === 'SMALL'
<button styleName={config.listStyle === 'SMALL'
? 'control-button--active'
: 'control-button'
}

View File

@@ -1,9 +1,6 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
const { remote } = require('electron')
const { Menu } = remote
import dataApi from 'browser/main/lib/dataApi'
import styles from './SideNav.styl'
import { openModal } from 'browser/main/lib/modal'
import PreferencesModal from '../modals/PreferencesModal'
@@ -89,10 +86,9 @@ class SideNav extends React.Component {
isTrashedActive={isTrashedActive}
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
counterTotalNote={data.noteMap._map.size - data.trashedSet._set.size}
counterTotalNote={data.noteMap._map.size}
counterStarredNote={data.starredSet._set.size}
counterDelNote={data.trashedSet._set.size}
handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(this)}
/>
<StorageList storageList={storageList} />
@@ -117,21 +113,18 @@ class SideNav extends React.Component {
tagListComponent () {
const { data, location } = this.props
const tagList = data.tagNoteMap.map((tag, name) => {
return { name, size: tag.size }
const tagList = data.tagNoteMap.map((tag, key) => {
return key
})
return (
tagList.map(tag => {
return (
tagList.map(tag => (
<TagListItem
name={tag.name}
name={tag}
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
isActive={this.getTagActive(location.pathname, tag)}
key={tag.name}
count={tag.size}
key={tag}
/>
)
})
))
)
}
@@ -146,34 +139,6 @@ class SideNav extends React.Component {
router.push(`/tags/${name}`)
}
emptyTrash (entries) {
const { dispatch } = this.props
const deletionPromises = entries.map((storageAndNoteKey) => {
const storageKey = storageAndNoteKey.split('-')[0]
const noteKey = storageAndNoteKey.split('-')[1]
return dataApi.deleteNote(storageKey, noteKey)
})
Promise.all(deletionPromises)
.then((arrayOfStorageAndNoteKeys) => {
arrayOfStorageAndNoteKeys.forEach(({ storageKey, noteKey }) => {
dispatch({ type: 'DELETE_NOTE', storageKey, noteKey })
})
})
.catch((err) => {
console.error('Cannot Delete note: ' + err)
})
console.log('Trash emptied')
}
handleFilterButtonContextMenu (event) {
const { data } = this.props
const entries = data.trashedSet.toJS()
const menu = Menu.buildFromTemplate([
{ label: 'Empty Trash', click: () => this.emptyTrash(entries) }
])
menu.popup()
}
render () {
const { data, location, config, dispatch } = this.props

View File

@@ -22,18 +22,14 @@ class TopBar extends React.Component {
this.focusSearchHandler = () => {
this.handleOnSearchFocus()
}
this.codeInitHandler = this.handleCodeInit.bind(this)
}
componentDidMount () {
ee.on('top:focus-search', this.focusSearchHandler)
ee.on('code:init', this.codeInitHandler)
}
componentWillUnmount () {
ee.off('top:focus-search', this.focusSearchHandler)
ee.off('code:init', this.codeInitHandler)
}
handleKeyDown (e) {
@@ -77,16 +73,14 @@ class TopBar extends React.Component {
handleSearchChange (e) {
const { router } = this.context
const keyword = this.refs.searchInput.value
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
router.push('/searched')
} else {
e.preventDefault()
}
this.setState({
search: keyword
search: this.refs.searchInput.value
})
ee.emit('top:search', keyword)
}
handleSearchFocus (e) {
@@ -121,10 +115,6 @@ class TopBar extends React.Component {
}
}
handleCodeInit () {
ee.emit('top:search', this.refs.searchInput.value)
}
render () {
const { config, style, location } = this.props
return (

View File

@@ -36,8 +36,7 @@ export const DEFAULT_CONFIG = {
displayLineNumbers: true,
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
scrollPastEnd: false,
type: 'SPLIT',
fetchUrlTitle: true
type: 'SPLIT'
},
preview: {
fontSize: '14',

View File

@@ -1,31 +0,0 @@
const fs = require('fs')
const path = require('path')
/**
* @description Copy a file from source to destination
* @param {String} srcPath
* @param {String} dstPath
* @return {Promise} an image path
*/
function copyFile (srcPath, dstPath) {
if (!path.extname(dstPath)) {
dstPath = path.join(dstPath, path.basename(srcPath))
}
return new Promise((resolve, reject) => {
const dstFolder = path.dirname(dstPath)
if (!fs.existsSync(dstFolder)) fs.mkdirSync(dstFolder)
const input = fs.createReadStream(srcPath)
const output = fs.createWriteStream(dstPath)
output.on('error', reject)
input.on('error', reject)
input.on('end', () => {
resolve(dstPath)
})
input.pipe(output)
})
}
module.exports = copyFile

View File

@@ -1,110 +0,0 @@
import copyFile from 'browser/main/lib/dataApi/copyFile'
import {findStorage} from 'browser/lib/findStorage'
const fs = require('fs')
const path = require('path')
const LOCAL_STORED_REGEX = /!\[(.*?)\]\(\s*?\/:storage\/(.*\.\S*?)\)/gi
const IMAGES_FOLDER_NAME = 'images'
/**
* Export note together with images
*
* If images is stored in the storage, creates 'images' subfolder in target directory
* and copies images to it. Changes links to images in the content of the note
*
* @param {String} storageKey or storage path
* @param {String} noteContent Content to export
* @param {String} targetPath Path to exported file
* @param {function} outputFormatter
* @return {Promise.<*[]>}
*/
function exportNote (storageKey, noteContent, targetPath, outputFormatter) {
const storagePath = path.isAbsolute(storageKey) ? storageKey : findStorage(storageKey).path
const exportTasks = []
if (!storagePath) {
throw new Error('Storage path is not found')
}
let exportedData = noteContent.replace(LOCAL_STORED_REGEX, (match, dstFilename, srcFilename) => {
if (!path.extname(dstFilename)) {
dstFilename += path.extname(srcFilename)
}
const dstRelativePath = path.join(IMAGES_FOLDER_NAME, dstFilename)
exportTasks.push({
src: path.join(IMAGES_FOLDER_NAME, srcFilename),
dst: dstRelativePath
})
return `![${dstFilename}](${dstRelativePath})`
})
if (outputFormatter) {
exportedData = outputFormatter(exportedData, exportTasks)
}
const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath))
return Promise.all(tasks.map((task) => copyFile(task.src, task.dst)))
.then(() => {
return saveToFile(exportedData, targetPath)
}).catch((err) => {
rollbackExport(tasks)
throw err
})
}
function prepareTasks (tasks, storagePath, targetPath) {
return tasks.map((task) => {
if (!path.isAbsolute(task.src)) {
task.src = path.join(storagePath, task.src)
}
if (!path.isAbsolute(task.dst)) {
task.dst = path.join(targetPath, task.dst)
}
return task
})
}
function saveToFile (data, filename) {
return new Promise((resolve, reject) => {
fs.writeFile(filename, data, (err) => {
if (err) return reject(err)
resolve(filename)
})
})
}
/**
* Remove exported files
* @param tasks Array of copy task objects. Object consists of two mandatory fields `src` and `dst`
*/
function rollbackExport (tasks) {
const folders = new Set()
tasks.forEach((task) => {
let fullpath = task.dst
if (!path.extname(task.dst)) {
fullpath = path.join(task.dst, path.basename(task.src))
}
if (fs.existsSync(fullpath)) {
fs.unlink(fullpath)
folders.add(path.dirname(fullpath))
}
})
folders.forEach((folder) => {
if (fs.readdirSync(folder).length === 0) {
fs.rmdir(folder)
}
})
}
export default exportNote

View File

@@ -18,7 +18,7 @@ nodeIpc.connectTo(
console.log(err)
})
nodeIpc.of.node.on('connect', function () {
console.log('Connected successfully')
console.log('Conncted successfully')
ipcRenderer.send('config-renew', {config: ConfigManager.get()})
})
nodeIpc.of.node.on('disconnect', function () {

View File

@@ -35,16 +35,14 @@ class NewNoteModal extends React.Component {
content: ''
})
.then((note) => {
const noteHash = `${note.storage}-${note.key}`
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: {key: noteHash}
query: {key: note.storage + '-' + note.key}
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
this.props.close()
})
@@ -75,16 +73,14 @@ class NewNoteModal extends React.Component {
}]
})
.then((note) => {
const noteHash = `${note.storage}-${note.key}`
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: {key: noteHash}
query: {key: note.storage + '-' + note.key}
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
this.props.close()
})

View File

@@ -83,7 +83,7 @@ class InfoTab extends React.Component {
>GitHub</a>
</li>
<li>
<a href='https://boostlog.io/@junp1234'
<a href='https://medium.com/boostnote'
onClick={(e) => this.handleLinkClick(e)}
>Blog</a>
</li>
@@ -128,7 +128,7 @@ class InfoTab extends React.Component {
>Development</a> : Development configurations for Boostnote.
</li>
<li styleName='cc'>
Copyright (C) 2017 - 2018 BoostIO
Copyright (C) 2017 Maisin&Co.
</li>
<li styleName='cc'>
License: GPL v3

View File

@@ -76,8 +76,7 @@ class UiTab extends React.Component {
displayLineNumbers: this.refs.editorDisplayLineNumbers.checked,
switchPreview: this.refs.editorSwitchPreview.value,
keyMap: this.refs.editorKeyMap.value,
scrollPastEnd: this.refs.scrollPastEnd.checked,
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked
scrollPastEnd: this.refs.scrollPastEnd.checked
},
preview: {
fontSize: this.refs.previewFontSize.value,
@@ -284,7 +283,6 @@ class UiTab extends React.Component {
onChange={(e) => this.handleUIChange(e)}
>
<option value='BLUR'>When Editor Blurred</option>
<option value='DBL_CLICK'>When Editor Blurred, Edit On Double Click</option>
<option value='RIGHTCLICK'>On Right Click</option>
</select>
</div>
@@ -329,17 +327,6 @@ class UiTab extends React.Component {
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.fetchUrlTitle}
ref='editorFetchUrlTitle'
type='checkbox'
/>&nbsp;
Bring in web page title when pasting URL on editor
</label>
</div>
<div styleName='group-header2'>Preview</div>
<div styleName='group-section'>
<div styleName='group-section-label'>

View File

@@ -9,7 +9,7 @@ Thank you for your help in advance.
### About copyright of Pull Request
If you make a pull request, It means you agree to transfer the copyright of the code changes to BoostIO.
If you make a pull request, It means you agree to transfer the copyright of the code changes to Maisin&Co.
It doesn't mean Boostnote will become a paid app. If we want to earn some money, We will try other way, which is some kind of cloud storage, Mobile app integration or some SPECIAL features.
Because GPL v3 is too strict to be compatible with any other License, We thought this is needed to replace the license with much freer one(like BSD, MIT) somewhen.
@@ -27,7 +27,7 @@ Because GPL v3 is too strict to be compatible with any other License, We thought
### Об авторских правах Pull Request
Если вы делаете pull request, значит вы согласны передать авторские права на изменения кода в BoostIO.
Если вы делаете pull request, значит вы согласны передать авторские права на изменения кода в Maisin&Co.
Это не означает, что Boostnote станет платным приложением. Если мы захотим заработать немного денег, мы найдем другой способ. Например, использование облачного хранилища, интеграцией мобильных приложений или другими специальными функциями.
Так как лицензия GPL v3 слишком строгая, чтобы быть совместимой с любой другой лицензией, мы думаем, что нужно заменить лицензию на более свободную (например, BSD, MIT).
@@ -45,7 +45,7 @@ Because GPL v3 is too strict to be compatible with any other License, We thought
### Pull Request의 저작권에 관하여
당신이 pull request를 요청하면, 코드 변경에 대한 저작권을 BoostIO에 양도한다는 것에 동의한다는 의미입니다.
당신이 pull request를 요청하면, 코드 변경에 대한 저작권을 Maisin&Co에 양도한다는 것에 동의한다는 의미입니다.
이것은 Boostnote가 유료화가 되는 것을 의미하는 건 아닙니다. 만약 우리가 자금이 필요하다면, 우리는 클라우드 연동, 모바일 앱 통합 혹은 특수한 기능 같은 것을 사용해 수입 창출을 시도할 것입니다.
GPL v3 라이센스는 다른 라이센스와 혼합해 사용하기엔 너무 엄격하므로, 우리는 BSD, MIT 라이센스와 같은 더 자유로운 라이센스로 교체하는 것을 생각하고 있습니다.
@@ -63,7 +63,7 @@ GPL v3 라이센스는 다른 라이센스와 혼합해 사용하기엔 너무
### Pull requestの著作権について
Pull requestをすることはその変化分のコードの著作権をBoostIOに譲渡することに同意することになります。
Pull requestをすることはその変化分のコードの著作権をMaisin&Co.に譲渡することに同意することになります。
アプリケーションのLicenseをいつでも変える選択肢を残したいと思うからです。
これはいずれかBoostnoteが有料の商用アプリになる可能性がある話ではありません。
@@ -83,7 +83,7 @@ Pull requestをすることはその変化分のコードの著作権をBoostIO
感谢您对我们的支持。
### 关于您提供的Pull Request的著作权版权问题
如果您提供了一个Pull Request这表示您将您所修改的代码的著作权移交给BoostIO
如果您提供了一个Pull Request这表示您将您所修改的代码的著作权移交给Maisin&Co
这并不表示Boostnote会成为一个需要付费的软件。如果我们想获得收益我们会尝试一些其他的方法比如说云存储、绑定手机软件等。
因为GPLv3过于严格不能和其他的一些协议兼容所以我们有可能在将来会把BoostNote的协议改为一些较为宽松的协议比如说BSD、MIT。

View File

@@ -19,7 +19,7 @@ module.exports = function (grunt) {
var initConfig = {
pkg: grunt.file.readJSON('package.json'),
'create-windows-installer': {
x64: {
ia32: {
appDirectory: path.join(__dirname, 'dist', 'Boostnote-win32-x64'),
outputDirectory: path.join(__dirname, 'dist'),
authors: 'MAISIN&CO., Inc.',
@@ -109,7 +109,7 @@ module.exports = function (grunt) {
var done = this.async()
var opts = {
name: 'Boostnote',
arch: 'x64',
arch: 'ia32',
dir: __dirname,
version: grunt.config.get('pkg.config.electron-version'),
'app-version': grunt.config.get('pkg.version'),

View File

@@ -78,7 +78,7 @@
<script src="../node_modules/codemirror/keymap/emacs.js"></script>
<script src="../node_modules/codemirror/addon/runmode/runmode.js"></script>
<script src="../node_modules/boost/boostNewLineIndentContinueMarkdownList.js"></script>
<script src="../node_modules/codemirror/addon/edit/continuelist.js"></script>
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>

View File

@@ -1,46 +0,0 @@
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../codemirror/lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../codemirror/lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var listRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/,
emptyListRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/,
unorderedListRE = /[*+-]\s/;
CodeMirror.commands.boostNewLineAndIndentContinueMarkdownList = function(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), replacements = [];
for (var i = 0; i < ranges.length; i++) {
var pos = ranges[i].head;
var eolState = cm.getStateAfter(pos.line);
var inList = eolState.list !== false;
var inQuote = eolState.quote !== 0;
var line = cm.getLine(pos.line), match = listRE.exec(line);
if (!ranges[i].empty() || (!inList && !inQuote) || !match || pos.ch < match[2].length - 1) {
cm.execCommand("newlineAndIndent");
return;
}
if (emptyListRE.test(line)) {
if (!/>\s*$/.test(line)) cm.replaceRange("", {
line: pos.line, ch: 0
}, {
line: pos.line, ch: pos.ch + 1
});
replacements[i] = "\n";
} else {
var indent = match[1], after = match[5];
var bullet = unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0
? match[2].replace("x", " ")
: (parseInt(match[3], 10) + 1) + match[4];
replacements[i] = "\n" + indent + bullet + after;
}
}
cm.replaceSelections(replacements);
};
});

View File

@@ -1,7 +1,7 @@
{
"name": "boost",
"productName": "Boostnote",
"version": "0.10.0",
"version": "0.9.0",
"main": "index.js",
"description": "Boostnote",
"license": "GPL-3.0",
@@ -56,6 +56,7 @@
"codemirror-mode-elixir": "^1.1.1",
"electron-config": "^0.2.1",
"electron-gh-releases": "^2.0.2",
"electron-windows-installer": "^1.7.8",
"flowchart.js": "^1.6.5",
"font-awesome": "^4.3.0",
"immutable": "^3.8.1",
@@ -113,6 +114,7 @@
"faker": "^3.1.0",
"grunt": "^0.4.5",
"grunt-electron-installer": "2.1.0",
"gulp": "^3.9.1",
"history": "^1.17.0",
"jsdom": "^9.4.2",
"json-loader": "^0.5.4",

View File

@@ -26,7 +26,7 @@ Boostnote is an open source project. It's an independent project with its ongoin
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
- [Twitter](https://twitter.com/boostnoteapp)
- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzAzMjI1MTIyNTQ3LTc2MjNiYWU3NTc1YjZlMTk3NzFmOWE1ZWU1MGRhMzBkMGIwMWFjOWMxMDRiM2I2NzkzYzc4OGZhNmVhZjYzZTM)
- [Blog](https://boostlog.io/@junp1234)
- [Blog](https://medium.com/boostnote)
- [Reddit](https://www.reddit.com/r/Boostnote/)

1116
yarn.lock

File diff suppressed because it is too large Load Diff