mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 18:26:26 +00:00
Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1a75a13e9 | ||
|
|
ee6f4de183 | ||
|
|
475885b3ef | ||
|
|
2d2b2d4c6c | ||
|
|
4d00454539 | ||
|
|
bf590b5614 | ||
|
|
3ef33c065c | ||
|
|
8e89fb8b92 | ||
|
|
8e81cfcf89 | ||
|
|
515736262d | ||
|
|
bd266dc602 | ||
|
|
9b3306157c | ||
|
|
f8e6a939ca | ||
|
|
8c48ee6fc1 | ||
|
|
5e476054d7 | ||
|
|
2fc8547384 | ||
|
|
2af2d71540 | ||
|
|
6aaf9d9eb2 | ||
|
|
42a9caf5a3 | ||
|
|
e6c1d7a383 | ||
|
|
02100bbc0a | ||
|
|
ce7c5f5d40 | ||
|
|
69f1ad6eb3 | ||
|
|
8320fb5024 | ||
|
|
4b79bca6bf | ||
|
|
4a5fd41249 | ||
|
|
4e90a93b30 | ||
|
|
9861fbf7c8 | ||
|
|
c762b9ae00 | ||
|
|
cc667a6edf | ||
|
|
419c57ed3f | ||
|
|
601f0b0de8 | ||
|
|
7b1c6c10b7 | ||
|
|
5a85c257cf | ||
|
|
beceb851c2 | ||
|
|
31485d3387 | ||
|
|
fc552e030a | ||
|
|
bf4c9f920a | ||
|
|
4ebd503664 | ||
|
|
0907bc80ef | ||
|
|
2cf46a3332 | ||
|
|
41868f28e6 | ||
|
|
964b7b62de | ||
|
|
0d34a03fe0 | ||
|
|
a62faa471c | ||
|
|
66f3ce2cb2 | ||
|
|
e313b5e59d | ||
|
|
bb26d9a0a8 | ||
|
|
0ca41fbdb4 | ||
|
|
2cfb883bad | ||
|
|
ec8c8bb669 | ||
|
|
0b54f01107 | ||
|
|
67be198bee | ||
|
|
32a4a1aae1 | ||
|
|
dac7372839 | ||
|
|
521c261a37 | ||
|
|
27367488c2 | ||
|
|
0d5c3b1be6 | ||
|
|
a67d5ffacb | ||
|
|
687440a7c7 | ||
|
|
bafdc24a6d | ||
|
|
047f9c93c5 | ||
|
|
116fafc117 | ||
|
|
060c92091c | ||
|
|
5802525b73 | ||
|
|
c3580caabc | ||
|
|
08c027acc5 | ||
|
|
4468792346 | ||
|
|
1b16c68cf9 | ||
|
|
b99c1e3b32 | ||
|
|
eb2994e3c2 | ||
|
|
d88dd26186 | ||
|
|
59fcc58e9c | ||
|
|
acba61f36a | ||
|
|
a3a55a8bb4 | ||
|
|
22929d84fc | ||
|
|
9ea9d30947 | ||
|
|
7f08428fe2 | ||
|
|
0d80a7d961 | ||
|
|
2899264b54 | ||
|
|
923de0aa0d | ||
|
|
2b729dad15 | ||
|
|
b9b5bae78a | ||
|
|
a9acde07d1 | ||
|
|
a46b8d3079 | ||
|
|
8b92e2cbb7 | ||
|
|
881f5a5110 | ||
|
|
4e986a6384 | ||
|
|
ce5e1babb7 | ||
|
|
b85790d2fa | ||
|
|
6bc3e7fcf1 | ||
|
|
1ca968201d | ||
|
|
f2a03e4cc7 | ||
|
|
a752730718 | ||
|
|
9d20fd91ec | ||
|
|
70a6a3acb8 | ||
|
|
7f52eed4d5 | ||
|
|
105119e1a4 | ||
|
|
169e30e029 | ||
|
|
a5fa3e9e7a | ||
|
|
8985062d34 | ||
|
|
56eb9c76ae | ||
|
|
e5b6762bf3 | ||
|
|
e8bccaef88 | ||
|
|
afdb038244 | ||
|
|
56942d55eb | ||
|
|
9d742c8435 | ||
|
|
6ee4e48de2 | ||
|
|
184f3dc04b |
10
.eslintrc
10
.eslintrc
@@ -1,10 +1,16 @@
|
||||
{
|
||||
"extends": ["standard", "standard-jsx"],
|
||||
"extends": ["standard", "standard-jsx", "plugin:react/recommended"],
|
||||
"plugins": ["react"],
|
||||
"rules": {
|
||||
"no-useless-escape": 0,
|
||||
"prefer-const": "warn",
|
||||
"no-unused-vars": "warn",
|
||||
"no-undef": "warn",
|
||||
"no-lone-blocks": "warn"
|
||||
"no-lone-blocks": "warn",
|
||||
"react/prop-types": 0,
|
||||
"react/no-string-refs": 0,
|
||||
"react/no-find-dom-node": "warn",
|
||||
"react/no-render-return-value": "warn",
|
||||
"react/no-deprecated": "warn"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,3 +3,7 @@ You can support Boostnote from $ 5 a month!
|
||||
|
||||
# Backers
|
||||
[Kazu Yokomizo](https://twitter.com/kazup_bot)
|
||||
|
||||
kolchan11
|
||||
|
||||
RonWalker22
|
||||
|
||||
@@ -3,6 +3,7 @@ import _ from 'lodash'
|
||||
import CodeMirror from 'codemirror'
|
||||
import path from 'path'
|
||||
import copyImage from 'browser/main/lib/dataApi/copyImage'
|
||||
import { findStorage } from 'browser/lib/findStorage'
|
||||
|
||||
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||
|
||||
@@ -39,6 +40,7 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
this.props.onBlur != null && this.props.onBlur(e)
|
||||
}
|
||||
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
||||
this.loadStyleHandler = (e) => {
|
||||
this.editor.refresh()
|
||||
}
|
||||
@@ -98,14 +100,25 @@ 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)
|
||||
|
||||
let editorTheme = document.getElementById('editorTheme')
|
||||
editorTheme.addEventListener('load', this.loadStyleHandler)
|
||||
|
||||
CodeMirror.Vim.defineEx('quit', 'q', this.quitEditor)
|
||||
CodeMirror.Vim.defineEx('q!', 'q!', this.quitEditor)
|
||||
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
|
||||
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
|
||||
}
|
||||
|
||||
quitEditor () {
|
||||
document.querySelector('textarea').blur()
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.editor.off('blur', this.blurHandler)
|
||||
this.editor.off('change', this.changeHandler)
|
||||
this.editor.off('paste', this.pasteHandler)
|
||||
let editorTheme = document.getElementById('editorTheme')
|
||||
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
||||
}
|
||||
@@ -201,7 +214,30 @@ export default class CodeEditor extends React.Component {
|
||||
insertImageMd (imageMd) {
|
||||
const textarea = this.editor.getInputField()
|
||||
const cm = this.editor
|
||||
textarea.value = `${textarea.value.substr(0, textarea.selectionStart)}${imageMd}${textarea.value.substr(textarea.selectionEnd)}`
|
||||
cm.replaceSelection(`${textarea.value.substr(0, textarea.selectionStart)}${imageMd}${textarea.value.substr(textarea.selectionEnd)}`)
|
||||
}
|
||||
|
||||
handlePaste (editor, e) {
|
||||
const dataTransferItem = e.clipboardData.items[0]
|
||||
if (!dataTransferItem.type.match('image')) return
|
||||
|
||||
const blob = dataTransferItem.getAsFile()
|
||||
let reader = new FileReader()
|
||||
let base64data
|
||||
|
||||
reader.readAsDataURL(blob)
|
||||
reader.onloadend = () => {
|
||||
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
|
||||
base64data += base64data.replace('+', ' ')
|
||||
const binaryData = new Buffer(base64data, 'base64').toString('binary')
|
||||
const imageName = Math.random().toString(36).slice(-16)
|
||||
const storagePath = findStorage(this.props.storageKey).path
|
||||
const imagePath = path.join(`${storagePath}`, 'images', `${imageName}.png`)
|
||||
|
||||
require('fs').writeFile(imagePath, binaryData, 'binary')
|
||||
const imageMd = `})`
|
||||
this.insertImageMd(imageMd)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
|
||||
@@ -51,12 +51,12 @@ code {
|
||||
color: rgba(147,147,149,0.8);;
|
||||
fill: rgba(147,147,149,1);;
|
||||
border-radius: 50%;
|
||||
margin: 7px;
|
||||
margin: 0px 10px;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -291,8 +291,9 @@ export default class MarkdownPreview extends React.Component {
|
||||
})
|
||||
|
||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('img'), (el) => {
|
||||
el.src = markdown.normalizeLinkText(el.src)
|
||||
if (!/\/:storage/.test(el.src)) return
|
||||
el.src = `file:///${path.join(storagePath, 'images', path.basename(el.src))}`
|
||||
el.src = `file:///${markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.src)))}`
|
||||
})
|
||||
|
||||
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
|
||||
|
||||
@@ -6,7 +6,7 @@ const ModalEscButton = ({
|
||||
handleEscButtonClick
|
||||
}) => (
|
||||
<button styleName='escButton' onClick={handleEscButtonClick}>
|
||||
<div styleName='esc-mark'>x</div>
|
||||
<div styleName='esc-mark'>×</div>
|
||||
<div styleName='esc-text'>esc</div>
|
||||
</button>
|
||||
)
|
||||
|
||||
@@ -11,4 +11,6 @@
|
||||
height top-bar-height
|
||||
|
||||
.esc-mark
|
||||
font-size 15px
|
||||
font-size 28px
|
||||
margin-top -5px
|
||||
margin-bottom -7px
|
||||
55
browser/components/RealtimeNotification.js
Normal file
55
browser/components/RealtimeNotification.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './RealtimeNotification.styl'
|
||||
|
||||
const electron = require('electron')
|
||||
const { shell } = electron
|
||||
|
||||
class RealtimeNotification extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
notifications: []
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.fetchNotifications()
|
||||
}
|
||||
|
||||
fetchNotifications () {
|
||||
const notificationsUrl = 'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
|
||||
fetch(notificationsUrl)
|
||||
.then(response => {
|
||||
return response.json()
|
||||
})
|
||||
.then(json => {
|
||||
this.setState({notifications: json.notifications})
|
||||
})
|
||||
}
|
||||
|
||||
handleLinkClick (e) {
|
||||
shell.openExternal(e.currentTarget.href)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
render () {
|
||||
const { notifications } = this.state
|
||||
const link = notifications.length > 0
|
||||
? <a styleName='notification-link' href={notifications[0].linkUrl}
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>
|
||||
{notifications[0].text}
|
||||
</a>
|
||||
: ''
|
||||
|
||||
return (
|
||||
<div styleName='notification-area'>{link}</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
RealtimeNotification.propTypes = {}
|
||||
|
||||
export default CSSModules(RealtimeNotification, styles)
|
||||
35
browser/components/RealtimeNotification.styl
Normal file
35
browser/components/RealtimeNotification.styl
Normal file
@@ -0,0 +1,35 @@
|
||||
.notification-area
|
||||
z-index 1000
|
||||
font-size 12px
|
||||
position absolute
|
||||
bottom 0px
|
||||
background-color #EBEBEB
|
||||
width 100vw
|
||||
height 30px
|
||||
text-align center
|
||||
|
||||
.notification-link
|
||||
text-decoration none
|
||||
color #282A36
|
||||
border 1px solid #6FA8E6
|
||||
background-color alpha(#6FA8E6, 0.2)
|
||||
padding 3px 9px
|
||||
border-radius 2px
|
||||
position absolute
|
||||
bottom 4px
|
||||
margin-left -10%
|
||||
transition 0.2s
|
||||
&:hover
|
||||
color #1378BD
|
||||
|
||||
body[data-theme="dark"]
|
||||
.notification-area
|
||||
background-color #1E2124
|
||||
|
||||
.notification-link
|
||||
color #fff
|
||||
border 1px solid alpha(#5CB85C, 0.6)
|
||||
background-color alpha(#5CB85C, 0.2)
|
||||
transition 0.2s
|
||||
&:hover
|
||||
color #5CB85C
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
.root
|
||||
absolute top bottom left right
|
||||
bottom 30px
|
||||
left $note-detail-left-margin
|
||||
right $note-detail-right-margin
|
||||
height 100%
|
||||
|
||||
@@ -59,6 +59,15 @@ md.use(math, {
|
||||
})
|
||||
md.use(require('markdown-it-imsize'))
|
||||
md.use(require('markdown-it-footnote'))
|
||||
md.use(require('markdown-it-multimd-table'))
|
||||
md.use(require('markdown-it-named-headers'), {
|
||||
slugify: (header) => {
|
||||
return encodeURI(header.trim()
|
||||
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
|
||||
.replace(/\s+/g, '-'))
|
||||
.replace(/\-+$/, '')
|
||||
}
|
||||
})
|
||||
// Override task item
|
||||
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
||||
let content, terminate, i, l, token
|
||||
@@ -156,12 +165,17 @@ function strip (input) {
|
||||
return output
|
||||
}
|
||||
|
||||
function normalizeLinkText (linkText) {
|
||||
return md.normalizeLinkText(linkText)
|
||||
}
|
||||
|
||||
const markdown = {
|
||||
render: function markdown (content) {
|
||||
if (!_.isString(content)) content = ''
|
||||
const renderedContent = md.render(content)
|
||||
return md.normalizeLinkText(renderedContent)
|
||||
return renderedContent
|
||||
},
|
||||
strip
|
||||
strip,
|
||||
normalizeLinkText
|
||||
}
|
||||
export default markdown
|
||||
|
||||
@@ -3,7 +3,7 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './InfoPanel.styl'
|
||||
|
||||
const InfoPanel = ({
|
||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt
|
||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, wordCount, letterCount, type
|
||||
}) => (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
||||
<div styleName='group-section'>
|
||||
@@ -46,6 +46,27 @@ const InfoPanel = ({
|
||||
<input value={noteLink} onClick={(e) => { e.target.select() }} />
|
||||
</div>
|
||||
</div>
|
||||
{type === 'SNIPPET_NOTE'
|
||||
? ''
|
||||
: <div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Words
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{wordCount}
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Letters
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{letterCount}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div id='export-wrap'>
|
||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
||||
@@ -73,7 +94,10 @@ InfoPanel.propTypes = {
|
||||
updatedAt: PropTypes.string.isRequired,
|
||||
createdAt: PropTypes.string.isRequired,
|
||||
exportAsMd: PropTypes.func.isRequired,
|
||||
exportAsTxt: PropTypes.func.isRequired
|
||||
exportAsTxt: PropTypes.func.isRequired,
|
||||
wordCount: PropTypes.number,
|
||||
letterCount: PropTypes.number,
|
||||
type: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(InfoPanel, styles)
|
||||
|
||||
@@ -20,6 +20,7 @@ import InfoPanel from './InfoPanel'
|
||||
import InfoPanelTrashed from './InfoPanelTrashed'
|
||||
import { formatDate } from 'browser/lib/date-formatter'
|
||||
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
||||
import striptags from 'striptags'
|
||||
|
||||
const electron = require('electron')
|
||||
const { remote } = electron
|
||||
@@ -76,7 +77,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
|
||||
note.content = this.refs.content.value
|
||||
if (this.refs.tags) note.tags = this.refs.tags.value
|
||||
note.title = markdown.strip(findNoteTitle(note.content))
|
||||
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
|
||||
note.updatedAt = new Date()
|
||||
|
||||
this.setState({
|
||||
@@ -340,7 +341,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
<button styleName='control-fullScreenButton'
|
||||
onMouseDown={(e) => this.handleFullScreenButton(e)}
|
||||
>
|
||||
<i className='fa fa-expand' styleName='fullScreen-button' />
|
||||
<i className='fa fa-window-maximize' styleName='fullScreen-button' />
|
||||
</button>
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
@@ -353,6 +354,9 @@ class MarkdownNoteDetail extends React.Component {
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
wordCount={note.content.split(' ').length}
|
||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||
type={note.type}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -271,7 +271,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
let syntax = CodeMirror.findModeByFileName(name.trim())
|
||||
let mode = syntax != null ? syntax.name : null
|
||||
if (mode != null) snippets[index].mode = mode
|
||||
this.state.note.snippets = snippets
|
||||
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
||||
|
||||
this.setState({
|
||||
note: this.state.note
|
||||
@@ -284,7 +284,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
return (e) => {
|
||||
let snippets = this.state.note.snippets.slice()
|
||||
snippets[index].mode = name
|
||||
this.state.note.snippets = snippets
|
||||
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
||||
|
||||
this.setState({
|
||||
note: this.state.note
|
||||
@@ -298,7 +298,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
return (e) => {
|
||||
let snippets = this.state.note.snippets.slice()
|
||||
snippets[index].content = this.refs['code-' + index].value
|
||||
this.state.note.snippets = snippets
|
||||
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
||||
this.setState({
|
||||
note: this.state.note
|
||||
}, () => {
|
||||
@@ -598,7 +598,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
<button styleName='control-fullScreenButton'
|
||||
onMouseDown={(e) => this.handleFullScreenButton(e)}
|
||||
>
|
||||
<i className='fa fa-expand' styleName='fullScreen-button' />
|
||||
<i className='fa fa-window-maximize' styleName='fullScreen-button' />
|
||||
</button>
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
@@ -611,6 +611,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.showWarning}
|
||||
exportAsTxt={this.showWarning}
|
||||
type={note.type}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
.body .description
|
||||
absolute top left right
|
||||
height 80px
|
||||
height 50px
|
||||
|
||||
.body .description textarea
|
||||
outline none
|
||||
@@ -27,14 +27,14 @@
|
||||
height 100%
|
||||
width 100%
|
||||
resize none
|
||||
border none
|
||||
padding 10px
|
||||
border 1px solid $ui-borderColor
|
||||
padding 2px 5px
|
||||
line-height 1.6
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
|
||||
.tabList
|
||||
absolute left right
|
||||
top 80px
|
||||
top 55px
|
||||
height 30px
|
||||
display flex
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
@@ -50,16 +50,17 @@
|
||||
|
||||
.tabView
|
||||
absolute left right bottom
|
||||
top 130px
|
||||
top 100px
|
||||
|
||||
.tabView-content
|
||||
absolute top left right bottom
|
||||
|
||||
.override
|
||||
absolute bottom left
|
||||
bottom 30px
|
||||
left 60px
|
||||
height 23px
|
||||
z-index 1
|
||||
z-index 101
|
||||
button
|
||||
navButtonColor()
|
||||
height 24px
|
||||
@@ -83,6 +84,7 @@ body[data-theme="dark"]
|
||||
.body .description textarea
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
color $ui-dark-text-color
|
||||
border 1px solid $ui-dark-borderColor
|
||||
|
||||
.tabList
|
||||
background-color $ui-button--active-backgroundColor
|
||||
|
||||
@@ -14,6 +14,7 @@ import InitModal from 'browser/main/modals/InitModal'
|
||||
import mixpanel from 'browser/main/lib/mixpanel'
|
||||
import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import RealtimeNotification from 'browser/components/RealtimeNotification'
|
||||
|
||||
function focused () {
|
||||
mixpanel.track('MAIN_FOCUSED')
|
||||
@@ -172,8 +173,8 @@ class Main extends React.Component {
|
||||
}
|
||||
|
||||
hideLeftLists (noteDetail, noteList, mainBody) {
|
||||
this.state.noteDetailWidth = noteDetail.style.left
|
||||
this.state.mainBodyWidth = mainBody.style.left
|
||||
this.setState({noteDetailWidth: noteDetail.style.left})
|
||||
this.setState({mainBodyWidth: mainBody.style.left})
|
||||
noteDetail.style.left = '0px'
|
||||
mainBody.style.left = '0px'
|
||||
noteList.style.display = 'none'
|
||||
@@ -255,6 +256,7 @@ class Main extends React.Component {
|
||||
ignorePreviewPointerEvents={this.state.isRightSliderFocused}
|
||||
/>
|
||||
</div>
|
||||
<RealtimeNotification />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
68
browser/main/NewNoteButton/NewNoteButton.styl
Normal file
68
browser/main/NewNoteButton/NewNoteButton.styl
Normal file
@@ -0,0 +1,68 @@
|
||||
.root
|
||||
position relative
|
||||
background-color $ui-noteList-backgroundColor
|
||||
height $topBar-height - 1
|
||||
margin-left: auto;
|
||||
width: 64px;
|
||||
margin-right: -15px;
|
||||
|
||||
.root--expanded
|
||||
@extend .root
|
||||
|
||||
$control-height = 34px
|
||||
|
||||
.control
|
||||
position absolute
|
||||
top 13px
|
||||
left 8px
|
||||
right 8px
|
||||
height $control-height
|
||||
overflow hidden
|
||||
display flex
|
||||
|
||||
.control-newNoteButton
|
||||
display block
|
||||
width 32px
|
||||
height $control-height - 2
|
||||
navButtonColor()
|
||||
font-size 16px
|
||||
line-height 28px
|
||||
padding 0
|
||||
&:active
|
||||
border-color $ui-button--active-backgroundColor
|
||||
&:hover .control-newNoteButton-tooltip
|
||||
opacity 1
|
||||
|
||||
.control-newNoteButton-tooltip
|
||||
tooltip()
|
||||
position fixed
|
||||
pointer-events none
|
||||
top 50px
|
||||
left 433px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root, .root--expanded
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
|
||||
.control
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
.control-newNoteButton
|
||||
color $ui-inactive-text-color
|
||||
border-color $ui-dark-borderColor
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
&:hover
|
||||
transition 0.15s
|
||||
color $ui-dark-text-color
|
||||
&:active
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
border-color $ui-dark-button--active-backgroundColor
|
||||
|
||||
.control-newNoteButton-tooltip
|
||||
darkTooltip()
|
||||
106
browser/main/NewNoteButton/index.js
Normal file
106
browser/main/NewNoteButton/index.js
Normal file
@@ -0,0 +1,106 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './NewNoteButton.styl'
|
||||
import _ from 'lodash'
|
||||
import modal from 'browser/main/lib/modal'
|
||||
import NewNoteModal from 'browser/main/modals/NewNoteModal'
|
||||
import { hashHistory } from 'react-router'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { dialog } = remote
|
||||
|
||||
const OSX = window.process.platform === 'darwin'
|
||||
|
||||
class NewNoteButton extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
}
|
||||
|
||||
this.newNoteHandler = () => {
|
||||
this.handleNewNoteButtonClick()
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
eventEmitter.on('top:new-note', this.newNoteHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
eventEmitter.off('top:new-note', this.newNoteHandler)
|
||||
}
|
||||
|
||||
handleNewNoteButtonClick (e) {
|
||||
const { config, location, dispatch } = this.props
|
||||
const { storage, folder } = this.resolveTargetFolder()
|
||||
|
||||
modal.open(NewNoteModal, {
|
||||
storage: storage.key,
|
||||
folder: folder.key,
|
||||
dispatch,
|
||||
location
|
||||
})
|
||||
}
|
||||
|
||||
resolveTargetFolder () {
|
||||
const { data, params } = this.props
|
||||
let storage = data.storageMap.get(params.storageKey)
|
||||
|
||||
// Find first storage
|
||||
if (storage == null) {
|
||||
for (let kv of data.storageMap) {
|
||||
storage = kv[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (storage == null) this.showMessageBox('No storage to create a note')
|
||||
const folder = _.find(storage.folders, {key: params.folderKey}) || storage.folders[0]
|
||||
if (folder == null) this.showMessageBox('No folder to create a note')
|
||||
|
||||
return {
|
||||
storage,
|
||||
folder
|
||||
}
|
||||
}
|
||||
|
||||
showMessageBox (message) {
|
||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: message,
|
||||
buttons: ['OK']
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
const { config, style } = this.props
|
||||
return (
|
||||
<div className='NewNoteButton'
|
||||
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
|
||||
style={style}
|
||||
>
|
||||
<div styleName='control'>
|
||||
<button styleName='control-newNoteButton'
|
||||
onClick={(e) => this.handleNewNoteButtonClick(e)}>
|
||||
<i className='fa fa-pencil-square-o' />
|
||||
<span styleName='control-newNoteButton-tooltip'>
|
||||
Make a Note {OSX ? '⌘' : '^'} + n
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
NewNoteButton.propTypes = {
|
||||
dispatch: PropTypes.func,
|
||||
config: PropTypes.shape({
|
||||
isSideNavFolded: PropTypes.bool
|
||||
})
|
||||
}
|
||||
|
||||
export default CSSModules(NewNoteButton, styles)
|
||||
@@ -2,6 +2,7 @@ $control-height = 30px
|
||||
|
||||
.root
|
||||
absolute left bottom
|
||||
bottom 30px
|
||||
top $topBar-height - 1
|
||||
background-color $ui-noteList-backgroundColor
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import fs from 'fs'
|
||||
import { hashHistory } from 'react-router'
|
||||
import markdown from 'browser/lib/markdown'
|
||||
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
||||
import stripgtags from 'striptags'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { Menu, MenuItem, dialog } = remote
|
||||
@@ -347,6 +348,22 @@ class NoteList extends React.Component {
|
||||
properties: ['openFile', 'multiSelections']
|
||||
}
|
||||
|
||||
dialog.showOpenDialog(remote.getCurrentWindow(), options, (filepaths) => {
|
||||
this.addNotesFromFiles(filepaths)
|
||||
})
|
||||
}
|
||||
|
||||
handleDrop (e) {
|
||||
e.preventDefault()
|
||||
const { location } = this.props
|
||||
const filepaths = Array.from(e.dataTransfer.files).map(file => { return file.path })
|
||||
if (!location.pathname.match(/\/trashed/)) this.addNotesFromFiles(filepaths)
|
||||
}
|
||||
|
||||
// Add notes to the current folder
|
||||
addNotesFromFiles (filepaths) {
|
||||
const { dispatch, location } = this.props
|
||||
|
||||
const targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return note !== null && `${note.storage}-${note.key}` === location.query.key
|
||||
})
|
||||
@@ -354,28 +371,26 @@ class NoteList extends React.Component {
|
||||
const storageKey = this.notes[targetIndex].storage
|
||||
const folderKey = this.notes[targetIndex].folder
|
||||
|
||||
dialog.showOpenDialog(remote.getCurrentWindow(), options, (filepaths) => {
|
||||
if (filepaths === undefined) return
|
||||
filepaths.forEach((filepath) => {
|
||||
fs.readFile(filepath, (err, data) => {
|
||||
if (err) throw Error('File reading error: ', err)
|
||||
const content = data.toString()
|
||||
const newNote = {
|
||||
content: content,
|
||||
folder: folderKey,
|
||||
title: markdown.strip(findNoteTitle(content)),
|
||||
type: 'MARKDOWN_NOTE'
|
||||
}
|
||||
dataApi.createNote(storageKey, newNote)
|
||||
.then((note) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: {key: `${note.storage}-${note.key}`}
|
||||
})
|
||||
if (filepaths === undefined) return
|
||||
filepaths.forEach((filepath) => {
|
||||
fs.readFile(filepath, (err, data) => {
|
||||
if (err) throw Error('File reading error: ', err)
|
||||
const content = data.toString()
|
||||
const newNote = {
|
||||
content: content,
|
||||
folder: folderKey,
|
||||
title: markdown.strip(findNoteTitle(content)),
|
||||
type: 'MARKDOWN_NOTE'
|
||||
}
|
||||
dataApi.createNote(storageKey, newNote)
|
||||
.then((note) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: {key: `${note.storage}-${note.key}`}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -438,6 +453,7 @@ class NoteList extends React.Component {
|
||||
<div className='NoteList'
|
||||
styleName='root'
|
||||
style={this.props.style}
|
||||
onDrop={(e) => this.handleDrop(e)}
|
||||
>
|
||||
<div styleName='control'>
|
||||
<div styleName='control-sortBy'>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
.root
|
||||
absolute bottom left right
|
||||
height $statusBar-height
|
||||
bottom 16px
|
||||
z-index 100
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
display flex
|
||||
|
||||
|
||||
@@ -59,8 +59,6 @@ class StatusBar extends React.Component {
|
||||
{Math.floor(config.zoom * 100)}%
|
||||
</button>
|
||||
|
||||
<div styleName='blank' />
|
||||
|
||||
{status.updateReady
|
||||
? <button onClick={this.updateApp} styleName='update'>
|
||||
<i styleName='update-icon' className='fa fa-cloud-download' /> Ready to Update!
|
||||
|
||||
@@ -2,12 +2,9 @@ import React, { PropTypes } from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './TopBar.styl'
|
||||
import _ from 'lodash'
|
||||
import modal from 'browser/main/lib/modal'
|
||||
import NewNoteModal from 'browser/main/modals/NewNoteModal'
|
||||
import { hashHistory } from 'react-router'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import NewNoteButton from 'browser/main/NewNoteButton'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { dialog } = remote
|
||||
@@ -24,81 +21,19 @@ class TopBar extends React.Component {
|
||||
isSearching: false
|
||||
}
|
||||
|
||||
this.newNoteHandler = () => {
|
||||
this.handleNewPostButtonClick()
|
||||
}
|
||||
|
||||
this.focusSearchHandler = () => {
|
||||
this.handleOnSearchFocus()
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
ee.on('top:new-note', this.newNoteHandler)
|
||||
ee.on('top:focus-search', this.focusSearchHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
ee.off('top:new-note', this.newNoteHandler)
|
||||
ee.off('top:focus-search', this.focusSearchHandler)
|
||||
}
|
||||
|
||||
handleNewPostButtonClick (e) {
|
||||
let { config, location } = this.props
|
||||
|
||||
if (location.pathname === '/trashed') {
|
||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Cannot create new note',
|
||||
detail: 'You cannot create new note in trash box.',
|
||||
buttons: ['OK']
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
switch (config.ui.defaultNote) {
|
||||
case 'MARKDOWN_NOTE':
|
||||
this.createNote('MARKDOWN_NOTE')
|
||||
break
|
||||
case 'SNIPPET_NOTE':
|
||||
this.createNote('SNIPPET_NOTE')
|
||||
break
|
||||
case 'ALWAYS_ASK':
|
||||
default:
|
||||
let { dispatch, location } = this.props
|
||||
let { storage, folder } = this.resolveTargetFolder()
|
||||
|
||||
modal.open(NewNoteModal, {
|
||||
storage: storage.key,
|
||||
folder: folder.key,
|
||||
dispatch,
|
||||
location
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
resolveTargetFolder () {
|
||||
let { data, params } = this.props
|
||||
let storage = data.storageMap.get(params.storageKey)
|
||||
|
||||
// Find first storage
|
||||
if (storage == null) {
|
||||
for (let kv of data.storageMap) {
|
||||
storage = kv[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
if (storage == null) window.alert('No storage to create a note')
|
||||
let folder = _.find(storage.folders, {key: params.folderKey})
|
||||
if (folder == null) folder = storage.folders[0]
|
||||
if (folder == null) window.alert('No folder to create a note')
|
||||
|
||||
return {
|
||||
storage,
|
||||
folder
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchChange (e) {
|
||||
let { router } = this.context
|
||||
router.push('/searched')
|
||||
@@ -107,22 +42,6 @@ class TopBar extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleOptionClick (uniqueKey) {
|
||||
return (e) => {
|
||||
this.setState({
|
||||
isSearching: false
|
||||
}, () => {
|
||||
let { location } = this.props
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
key: uniqueKey
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchFocus (e) {
|
||||
this.setState({
|
||||
isSearching: true
|
||||
@@ -147,60 +66,6 @@ class TopBar extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
createNote (noteType) {
|
||||
let { dispatch, location } = this.props
|
||||
if (noteType !== 'MARKDOWN_NOTE' && noteType !== 'SNIPPET_NOTE') throw new Error('Invalid note type.')
|
||||
|
||||
let { storage, folder } = this.resolveTargetFolder()
|
||||
|
||||
let newNote = noteType === 'MARKDOWN_NOTE'
|
||||
? {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
folder: folder.key,
|
||||
title: '',
|
||||
content: ''
|
||||
}
|
||||
: {
|
||||
type: 'SNIPPET_NOTE',
|
||||
folder: folder.key,
|
||||
title: '',
|
||||
description: '',
|
||||
snippets: [{
|
||||
name: '',
|
||||
mode: 'text',
|
||||
content: ''
|
||||
}]
|
||||
}
|
||||
|
||||
dataApi
|
||||
.createNote(storage.key, newNote)
|
||||
.then((note) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: {key: note.storage + '-' + note.key}
|
||||
})
|
||||
ee.emit('detail:focus')
|
||||
})
|
||||
}
|
||||
|
||||
setDefaultNote (defaultNote) {
|
||||
let { config, dispatch } = this.props
|
||||
let ui = Object.assign(config.ui)
|
||||
ui.defaultNote = defaultNote
|
||||
ConfigManager.set({
|
||||
ui
|
||||
})
|
||||
|
||||
dispatch({
|
||||
type: 'SET_UI',
|
||||
config: ConfigManager.get()
|
||||
})
|
||||
}
|
||||
|
||||
handleOnSearchFocus () {
|
||||
if (this.state.isSearching) {
|
||||
this.refs.search.childNodes[0].blur()
|
||||
@@ -210,7 +75,7 @@ class TopBar extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
let { config, style, data } = this.props
|
||||
let { config, style, data, location } = this.props
|
||||
return (
|
||||
<div className='TopBar'
|
||||
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
|
||||
@@ -242,14 +107,17 @@ class TopBar extends React.Component {
|
||||
}
|
||||
|
||||
</div>
|
||||
<button styleName='control-newPostButton'
|
||||
onClick={(e) => this.handleNewPostButtonClick(e)}>
|
||||
<i className='fa fa-pencil-square-o' />
|
||||
<span styleName='control-newPostButton-tooltip'>
|
||||
Make a Note {OSX ? '⌘' : '^'} + n
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
{location.pathname === '/trashed' ? ''
|
||||
: <NewNoteButton
|
||||
{..._.pick(this.props, [
|
||||
'dispatch',
|
||||
'data',
|
||||
'config',
|
||||
'params',
|
||||
'location'
|
||||
])}
|
||||
/>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ textarea.block-input
|
||||
fullsize()
|
||||
|
||||
modalZIndex= 1000
|
||||
modalBackColor = transparentify(white, 65%)
|
||||
modalBackColor = white
|
||||
.ace_focus
|
||||
outline-color rgb(59, 153, 252)
|
||||
outline-offset 0px
|
||||
@@ -86,12 +86,12 @@ modalBackColor = transparentify(white, 65%)
|
||||
body[data-theme="dark"]
|
||||
.ModalBase
|
||||
.modalBack
|
||||
background-color alpha(black, 60%)
|
||||
background-color $ui-dark-backgroundColor
|
||||
|
||||
.CodeMirror
|
||||
font-family inherit !important
|
||||
line-height 1.4em
|
||||
height 100%
|
||||
height 96%
|
||||
.CodeMirror > div > textarea
|
||||
margin-bottom -1em
|
||||
.CodeMirror-focused .CodeMirror-selected
|
||||
|
||||
@@ -18,9 +18,10 @@ function initAwsMobileAnalytics () {
|
||||
AWS.config.credentials.get((err) => {
|
||||
if (!err) {
|
||||
console.log('Cognito Identity ID: ' + AWS.config.credentials.identityId)
|
||||
recordDynamicCustomEvent('APP_STARTED')
|
||||
recordStaticCustomEvent()
|
||||
}
|
||||
})
|
||||
recordStaticCustomEvent()
|
||||
}
|
||||
|
||||
function recordDynamicCustomEvent (type) {
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
height 270px
|
||||
overflow hidden
|
||||
position relative
|
||||
padding 0 40px
|
||||
|
||||
.header
|
||||
height 70px
|
||||
height 80px
|
||||
margin-bottom 10px
|
||||
margin-top 20px
|
||||
font-size 18px
|
||||
@@ -15,23 +14,27 @@
|
||||
background-color $ui-backgroundColor
|
||||
color $ui-text-color
|
||||
|
||||
.title
|
||||
font-size 36px
|
||||
font-weight 600
|
||||
|
||||
.control-folder-label
|
||||
text-align left
|
||||
font-size 12px
|
||||
font-size 14px
|
||||
color $ui-text-color
|
||||
|
||||
.control-folder-input
|
||||
display block
|
||||
height 30px
|
||||
width 420px
|
||||
height 40px
|
||||
width 490px
|
||||
padding 0 5px
|
||||
margin 10px auto 15px
|
||||
margin 10px 0
|
||||
border 1px solid #C9C9C9 // TODO: use variable.
|
||||
border-radius 2px
|
||||
background-color transparent
|
||||
outline none
|
||||
vertical-align middle
|
||||
font-size 14px
|
||||
font-size 16px
|
||||
&:disabled
|
||||
background-color $ui-input--disabled-backgroundColor
|
||||
&:focus, &:active
|
||||
@@ -39,14 +42,13 @@
|
||||
|
||||
.control-confirmButton
|
||||
display block
|
||||
float right
|
||||
height 30px
|
||||
width 100px
|
||||
height 35px
|
||||
width 140px
|
||||
border none
|
||||
border-radius 2px
|
||||
padding 0 25px
|
||||
margin 20px auto
|
||||
font-size 12px
|
||||
font-size 14px
|
||||
colorPrimaryButton()
|
||||
|
||||
body[data-theme="dark"]
|
||||
@@ -56,7 +58,6 @@ body[data-theme="dark"]
|
||||
height 270px
|
||||
overflow hidden
|
||||
position relative
|
||||
padding 0 40px
|
||||
|
||||
.header
|
||||
background-color transparent
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
const electron = require('electron')
|
||||
const ipc = electron.ipcRenderer
|
||||
|
||||
export default class DeleteArticleModal extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.confirmHandler = (e) => this.handleYesButtonClick()
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
ReactDOM.findDOMNode(this.refs.no).focus()
|
||||
ipc.on('modal-confirm', this.confirmHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
ipc.removeListener('modal-confirm', this.confirmHandler)
|
||||
}
|
||||
|
||||
handleNoButtonClick (e) {
|
||||
this.props.close()
|
||||
}
|
||||
|
||||
handleYesButtonClick (e) {
|
||||
this.props.close()
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='DeleteArticleModal modal'>
|
||||
<div className='title'><i className='fa fa-fw fa-trash' /> Delete an article.</div>
|
||||
|
||||
<div className='message'>Do you really want to delete?</div>
|
||||
|
||||
<div className='control'>
|
||||
<button ref='no' onClick={(e) => this.handleNoButtonClick(e)}><i className='fa fa-fw fa-close' /> No</button>
|
||||
<button ref='yes' onClick={(e) => this.handleYesButtonClick(e)} className='danger'><i className='fa fa-fw fa-check' /> Yes</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DeleteArticleModal.propTypes = {
|
||||
action: PropTypes.object,
|
||||
articleKey: PropTypes.string,
|
||||
close: PropTypes.func
|
||||
}
|
||||
@@ -9,16 +9,19 @@
|
||||
font-size 18px
|
||||
line-height 50px
|
||||
padding 0 15px
|
||||
background-color $ui-backgroundColor
|
||||
border-bottom solid 1px $ui-borderColor
|
||||
color $ui-text-color
|
||||
margin-bottom 20px
|
||||
|
||||
.title
|
||||
font-size 36px
|
||||
font-weight 600
|
||||
|
||||
.control
|
||||
padding 25px 15px 15px
|
||||
padding 25px 0px
|
||||
text-align center
|
||||
|
||||
.control-button
|
||||
width 220px
|
||||
width 240px
|
||||
height 220px
|
||||
margin 0 15px
|
||||
border $ui-border
|
||||
@@ -30,8 +33,8 @@
|
||||
colorPrimaryButton()
|
||||
|
||||
.control-button-icon
|
||||
font-size 50px
|
||||
margin-bottom 15px
|
||||
font-size 48px
|
||||
margin-bottom 25px
|
||||
|
||||
.control-button-label
|
||||
font-size 18px
|
||||
@@ -49,8 +52,6 @@ body[data-theme="dark"]
|
||||
modalDark()
|
||||
|
||||
.header
|
||||
background-color $ui-dark-button--hover-backgroundColor
|
||||
border-color $ui-dark-borderColor
|
||||
color $ui-dark-text-color
|
||||
|
||||
.control-button
|
||||
|
||||
@@ -31,10 +31,15 @@
|
||||
|
||||
.group-section-control
|
||||
flex 1
|
||||
margin-left 5px
|
||||
|
||||
.group-section-control select
|
||||
outline none
|
||||
border 1px solid $ui-borderColor
|
||||
font-size 16px
|
||||
height 30px
|
||||
width 250px
|
||||
margin-bottom 5px
|
||||
background-color transparent
|
||||
|
||||
.group-section-control-input
|
||||
@@ -77,14 +82,16 @@
|
||||
margin-right 10px
|
||||
|
||||
.group-control-rightButton
|
||||
float right
|
||||
position absolute
|
||||
top 10px
|
||||
right 20px
|
||||
colorPrimaryButton()
|
||||
border none
|
||||
border-radius 2px
|
||||
font-size $tab--button-font-size
|
||||
height 35px
|
||||
width 100px
|
||||
margin-right 10px
|
||||
height 40px
|
||||
width 120px
|
||||
padding 0 15px
|
||||
|
||||
.group-hint
|
||||
border $ui-border
|
||||
|
||||
@@ -81,7 +81,7 @@ class InfoTab extends React.Component {
|
||||
<li>
|
||||
<a href='https://github.com/BoostIO/Boostnote/issues'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>GitHub Issues</a> : We'd love to hear your feedback 🙌
|
||||
>GitHub Issues</a> : We'd love to hear your feedback 🙌
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://github.com/BoostIO/Boostnote/blob/master/docs/build.md'
|
||||
@@ -97,9 +97,9 @@ class InfoTab extends React.Component {
|
||||
</ul>
|
||||
<hr />
|
||||
<div styleName='policy'>Data collection policy</div>
|
||||
<p>We collect only the number of DAU for Boostnote and DO NOT collect any detail information</p>
|
||||
<p>such as your note content. You can see how it works on <a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</p>
|
||||
<p>These data are only used for Boostnote improvements.</p>
|
||||
<div>We collect only the number of DAU for Boostnote and **DO NOT collect** any detail information such as your note content.</div>
|
||||
<div>You can see how it works on <a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</div>
|
||||
<div>These data are only used for Boostnote improvements.</div>
|
||||
<input onChange={(e) => this.handleConfigChange(e)}
|
||||
checked={this.state.config.amaEnabled}
|
||||
ref='amaEnabled'
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
text-decoration none
|
||||
|
||||
.policy
|
||||
width 100%
|
||||
font-size 20px
|
||||
margin-bottom 10px
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@ top-bar--height = 50px
|
||||
|
||||
.root
|
||||
modal()
|
||||
max-width 800px
|
||||
min-height 500px
|
||||
height 80%
|
||||
max-width 100vw
|
||||
min-height 100vh
|
||||
height 100vh
|
||||
width 100vw
|
||||
overflow hidden
|
||||
position relative
|
||||
|
||||
@@ -25,22 +26,22 @@ top-bar--height = 50px
|
||||
top top-bar--height
|
||||
left 0
|
||||
width 140px
|
||||
margin-left 30px
|
||||
margin-left 10px
|
||||
margin-top 20px
|
||||
background-color $ui-backgroundColor
|
||||
|
||||
.nav-button
|
||||
font-size 14px
|
||||
text-align left
|
||||
width 100px
|
||||
margin 4px 0
|
||||
padding 5px 0
|
||||
width 120px
|
||||
margin 5px 0
|
||||
padding 7px 0
|
||||
padding-left 10px
|
||||
border none
|
||||
border-radius 2px
|
||||
background-color transparent
|
||||
color $ui-text-color
|
||||
font-size 14px
|
||||
font-size 16px
|
||||
|
||||
.nav-button--active
|
||||
@extend .nav-button
|
||||
|
||||
@@ -5,7 +5,7 @@ import dataApi from 'browser/main/lib/dataApi'
|
||||
import StorageItem from './StorageItem'
|
||||
|
||||
const electron = require('electron')
|
||||
const remote = electron.remote
|
||||
const { shell, remote } = electron
|
||||
|
||||
function browseFolder () {
|
||||
let dialog = remote.dialog
|
||||
@@ -50,6 +50,11 @@ class StoragesTab extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleLinkClick (e) {
|
||||
shell.openExternal(e.currentTarget.href)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
renderList () {
|
||||
let { data, boundingBox } = this.props
|
||||
|
||||
@@ -161,7 +166,10 @@ class StoragesTab extends React.Component {
|
||||
<option value='FILESYSTEM'>File System</option>
|
||||
</select>
|
||||
<div styleName='addStorage-body-section-type-description'>
|
||||
3rd party cloud integration(such as Google Drive and Dropbox) will be available soon.
|
||||
3rd party cloud integration:
|
||||
<a href='https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Cloud-Syncing</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,8 +10,8 @@ $tab--button-font-size = 14px
|
||||
$tab--dark-text-color = #E5E5E5
|
||||
|
||||
.header
|
||||
font-size 24px
|
||||
margin-bottom 30px
|
||||
font-size 36px
|
||||
margin-bottom 60px
|
||||
|
||||
body[data-theme="dark"]
|
||||
.header
|
||||
|
||||
@@ -90,9 +90,8 @@ class UiTab extends React.Component {
|
||||
<div styleName='group'>
|
||||
<div styleName='group-header'>UI</div>
|
||||
|
||||
<div styleName='group-header2'>Theme</div>
|
||||
|
||||
<div styleName='group-section'>
|
||||
Color Theme
|
||||
<div styleName='group-section-control'>
|
||||
<select value={config.ui.theme}
|
||||
onChange={(e) => this.handleUIChange(e)}
|
||||
|
||||
@@ -5,7 +5,7 @@ $danger-color = #c9302c
|
||||
$danger-lighten-color = lighten(#c9302c, 5%)
|
||||
|
||||
// Layouts
|
||||
$statusBar-height = 24px
|
||||
$statusBar-height = 36px
|
||||
$sideNav-width = 200px
|
||||
$sideNav--folded-width = 44px
|
||||
$topBar-height = 60px
|
||||
@@ -150,10 +150,13 @@ modal()
|
||||
position relative
|
||||
z-index $modal-z-index
|
||||
width 100%
|
||||
margin-left 80px
|
||||
margin-right 80px
|
||||
margin-bottom 80px
|
||||
margin-top 100px
|
||||
background-color $modal-background
|
||||
overflow hidden
|
||||
border-radius $modal-border-radius
|
||||
box-shadow 0 0 1px rgba(76,86,103,.15), 0 2px 18px rgba(31,37,50,.22)
|
||||
|
||||
topBarButtonLight()
|
||||
width 34px
|
||||
@@ -260,4 +263,3 @@ modalDark()
|
||||
background-color $ui-dark-backgroundColor
|
||||
overflow hidden
|
||||
border-radius $modal-border-radius
|
||||
box-shadow 2px 2px 10px black
|
||||
|
||||
@@ -69,3 +69,21 @@ Pull requestをすることはその変化分のコードの著作権をMaisin&C
|
||||
これはいずれかBoostnoteが有料の商用アプリになる可能性がある話ではありません。
|
||||
もし、このアプリケーションに料金が発生する時は、Boostnote専用のCloud storageの提供やMobile appとの連動、何か特殊なプレミアム機能の提供など形になります。
|
||||
現在考えられているのは、GPL v3の場合、他のライセンスとの互換が不可能であるため、もしより自由なLicense(BSD, MIT)に変える時に改めて著作権者としてライセンスし直す選択肢を残すイメージです。
|
||||
|
||||
---
|
||||
|
||||
# Contributing to Boostnote (Simplified Chinese)
|
||||
|
||||
### 当您创建一个issue的时候
|
||||
我们对您的issue格式没有要求,但是我们有一个请求:
|
||||
|
||||
**如果可能,请在开发者模式打开的情况下,为我们提供屏幕截图**
|
||||
|
||||
(您可以通过`Ctrl+Shift+I`打开开发者模式)。
|
||||
感谢您对我们的支持。
|
||||
|
||||
### 关于您提供的Pull Request的著作权(版权)问题
|
||||
如果您提供了一个Pull Request,这表示您将您所修改的代码的著作权移交给Maisin&Co。
|
||||
|
||||
这并不表示Boostnote会成为一个需要付费的软件。如果我们想获得收益,我们会尝试一些其他的方法,比如说云存储、绑定手机软件等。
|
||||
因为GPLv3过于严格,不能和其他的一些协议兼容,所以我们有可能在将来会把BoostNote的协议改为一些较为宽松的协议,比如说BSD、MIT。
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Build
|
||||
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), and [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md).
|
||||
|
||||
## Environments
|
||||
* npm: 4.x
|
||||
@@ -43,6 +44,8 @@ You can build the program by using `grunt`. However, we don't recommend this bec
|
||||
|
||||
So, we've prepared a separate script which just makes an executable file.
|
||||
|
||||
This build doesn't work on npm v5.3.0. So you need to use v5.2.0 when you build it.
|
||||
|
||||
```
|
||||
grunt pre-build
|
||||
```
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# How to debug Boostnote (Electron app)
|
||||
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), and [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md)
|
||||
|
||||
Boostnote is an Electron app so it's based on Chromium; developers can use `Developer Tools` just like Google Chrome.
|
||||
|
||||
You can toggle the `Developer Tools` like this:
|
||||
|
||||
@@ -37,6 +37,8 @@ Gruntを使います。
|
||||
|
||||
それで、実行ファイルを作るスクリプトを用意しておきました。
|
||||
|
||||
このビルドはnpm v5.3.0では動かないのでv5.2.0で動かす必要があります。
|
||||
|
||||
```
|
||||
grunt pre-build
|
||||
```
|
||||
|
||||
@@ -37,6 +37,8 @@ yarn run hot
|
||||
|
||||
그래서, 실행파일만을 만드는 스크립트를 준비해 뒀습니다.
|
||||
|
||||
This build doesn't work on npm v5.3.0. So you need to use v5.2.0 when you build it.
|
||||
|
||||
```
|
||||
grunt pre-build
|
||||
```
|
||||
|
||||
@@ -43,6 +43,8 @@ $ yarn run dev-start
|
||||
|
||||
Мы подготовили отдельный скрипт, который просто создает исполняемый файл:
|
||||
|
||||
This build doesn't work on npm v5.3.0. So you need to use v5.2.0 when you build it.
|
||||
|
||||
```
|
||||
grunt pre-build
|
||||
```
|
||||
|
||||
56
docs/zh_CN/build.md
Normal file
56
docs/zh_CN/build.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# 构建Boostnote
|
||||
|
||||
## 环境
|
||||
* npm: 4.x
|
||||
* node: 7.x
|
||||
|
||||
因为`$ grand pre-build`的问题,您只能使用`npm v4.x`而不能使用`npm v5.x`。
|
||||
|
||||
## 开发
|
||||
|
||||
我们使用Webpack HMR来开发Boostnote。
|
||||
在代码根目录下运行下列指令可以以默认配置运行Boostnote。
|
||||
|
||||
### 首先使用yarn安装所需的依赖包。
|
||||
|
||||
```
|
||||
$ yarn
|
||||
```
|
||||
|
||||
### 接着编译并且运行Boostnote。
|
||||
|
||||
```
|
||||
$ yarn run dev-start
|
||||
```
|
||||
|
||||
这个指令相当于在两个终端内同时运行`yarn run webpack`和`yarn run hot`。
|
||||
|
||||
如果出现错误`Failed to load resource: net::ERR_CONNECTION_REFUSED`,请尝试重新运行Boostnote。
|
||||

|
||||
|
||||
### 然后您就可以进行开发了
|
||||
|
||||
当您对代码作出更改的时候,`webpack`会自动抓取并应用所有代码更改。
|
||||
|
||||
> ### 提示
|
||||
> 在如下情况中,您可能需要重新运行Boostnote才能应用代码更改
|
||||
> 1. 当您在修改了一个组件的构造函数的时候When editing a constructor method of a component
|
||||
> 2. 当您新建了一个CSS类的时候(其实这和第1项是相同的,因为每个CSS类都需在组件的构造函数中被重写)
|
||||
|
||||
## 部署
|
||||
|
||||
我们使用Grunt来自动部署Boostnote。
|
||||
因为部署需要协同设计(codesign)与验证码(authenticode),所以您可以但我们不建议通过`grunt`来部署。
|
||||
所以我们准备了一个脚本文件来生成执行文件。
|
||||
|
||||
```
|
||||
grunt pre-build
|
||||
```
|
||||
|
||||
您只能使用`npm v5.2.0`而不能使用`npm v5.3.0`。
|
||||
|
||||
接下来您就可以在`dist`目录中找到可执行文件。
|
||||
|
||||
> ### 提示
|
||||
> 因为此可执行文件并没有被注册,所以自动更新不可用。
|
||||
> 如果需要,您也可将协同设计(codesign)与验证码(authenticode)使用于这个可执行文件中。
|
||||
15
docs/zh_CN/debug.md
Normal file
15
docs/zh_CN/debug.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# 在Boostnote上Debug
|
||||
|
||||
Boostnote基于Electron,所以Boostnote上的开发者工具和Google Chrome相同。
|
||||
|
||||
您可以像这样或者按下快捷键`Ctrl+Shift+I`打开开发者工具:
|
||||

|
||||
|
||||
开发者工具大概形如这样:
|
||||

|
||||
|
||||
您可以在`console`选项卡中找到运行错误,
|
||||
也可以像这样在`debugger`选项卡中设置断点去分步Debug:
|
||||

|
||||
|
||||
关于具体如何使用开发者工具,详见[Chrome 官档](https://developer.chrome.com/devtools)。(如果您在中国大陆,您可能需要一个VPN才能正常访问)
|
||||
@@ -17,7 +17,8 @@ const mainWindow = new BrowserWindow({
|
||||
webPreferences: {
|
||||
zoomFactor: 1.0,
|
||||
blinkFeatures: 'OverlayScrollbars'
|
||||
}
|
||||
},
|
||||
icon: path.resolve(__dirname, '../resources/app.png')
|
||||
})
|
||||
|
||||
const url = path.resolve(__dirname, './main.html')
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "boost",
|
||||
"version": "0.8.13",
|
||||
"productName": "Boostnote",
|
||||
"version": "0.8.15",
|
||||
"main": "index.js",
|
||||
"description": "Boostnote",
|
||||
"license": "GPL-3.0",
|
||||
@@ -65,6 +66,8 @@
|
||||
"markdown-it-emoji": "^1.1.1",
|
||||
"markdown-it-footnote": "^3.0.0",
|
||||
"markdown-it-imsize": "^2.0.1",
|
||||
"markdown-it-multimd-table": "^2.0.1",
|
||||
"markdown-it-named-headers": "^0.0.4",
|
||||
"md5": "^2.0.0",
|
||||
"mixpanel": "^0.4.1",
|
||||
"moment": "^2.10.3",
|
||||
@@ -76,6 +79,7 @@
|
||||
"react-redux": "^4.4.5",
|
||||
"redux": "^3.5.2",
|
||||
"sander": "^0.5.1",
|
||||
"striptags": "^2.2.1",
|
||||
"superagent": "^1.2.0",
|
||||
"superagent-promise": "^1.0.3"
|
||||
},
|
||||
@@ -99,6 +103,7 @@
|
||||
"eslint": "^3.13.1",
|
||||
"eslint-config-standard": "^6.2.1",
|
||||
"eslint-config-standard-jsx": "^3.2.0",
|
||||
"eslint-plugin-react": "^7.2.0",
|
||||
"faker": "^3.1.0",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-electron-installer": "^1.2.0",
|
||||
|
||||
21
readme.md
21
readme.md
@@ -1,16 +1,13 @@
|
||||
<h1 align="center">
|
||||
<a href="https://github.com/BoostIO/Boostnote"><img src="./resources/app.png" alt="Boostnote" width="180"></a>
|
||||
<br>
|
||||
Boostnote
|
||||
<br>
|
||||
<br>
|
||||
</h1>
|
||||
<h4 align="center">Note-taking app for programmers. </h4>
|
||||
<h5 align="center">macOS, Windows and Linux. Android and iOS apps will be released soon!</h5>
|
||||
<h5 align="center">Built with Electron, React + Redux, Webpack and CSSModules</h5>
|
||||
New:zap:
|
||||
|
||||
[Android and iOS apps](https://github.com/BoostIO/Boostnote-mobile) are released!
|
||||
|
||||

|
||||
|
||||
<h4 align="center">Note-taking app for programmers. </h4>
|
||||
<h5 align="center">Apps available for Mac, Windows, Linux, Android and iOS!</h5>
|
||||
<h5 align="center">Built with Electron, React + Redux, Webpack and CSSModules</h5>
|
||||
|
||||
[](https://travis-ci.org/BoostIO/Boostnote)
|
||||
|
||||
## Authors & Maintainers
|
||||
@@ -25,11 +22,11 @@
|
||||
|
||||
## Slack Group
|
||||
Let's talk about Boostnote's great features, new feature requests and things like Japanese gourmet. 🍣 <br>
|
||||
[Join us](https://join.slack.com/t/boostnote-group/shared_invite/MjE3NDcwODQ3NTkwLTE1MDA4NjQ3ODktM2IxOTk4ZTNjYQ)
|
||||
[Join us](https://join.slack.com/t/boostnote-group/shared_invite/enQtMjQ0MjYxNTMxNjY5LTI0NWFkZDEyYWYxYWE0YTMyYjI4NzA2YjBlNmFmOGVhNGFjY2I1MTNmZWJjYmMxYTdkM2VjNWNjYTFiYWQ3ZmQ)
|
||||
|
||||
## More Information
|
||||
* [Website](https://boostnote.io)
|
||||
* [Boostnote Team](https://boostnote.io/team/) : Boostnote for the creative hacker teams. Share your markdown notes and snippets instantly with your team. **We will release it at August!** 🏃💨
|
||||
* [10hz](https://boostnote.io/team/) : Boostnote for the creative hacker teams. Share your markdown notes and snippets instantly with your team. **We will release it at October!** 🏃💨
|
||||
* [Support us via Bountysource](https://salt.bountysource.com/teams/boostnote) : Thank you for your support 🎉
|
||||
* [Development](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md) : Development configurations for Boostnote 🚀
|
||||
* Copyright (C) 2017 Maisin&Co.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 426 KiB After Width: | Height: | Size: 250 KiB |
Reference in New Issue
Block a user