1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 09:46:22 +00:00

Merge branch 'master' into sean/improve-english

This commit is contained in:
Sean Baines
2017-10-05 07:46:02 +01:00
committed by GitHub
73 changed files with 1467 additions and 635 deletions

View File

@@ -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 = `![${imageName}](${path.join('/:storage', `${imageName}.png`)})`
this.insertImageMd(imageMd)
}
}
render () {

View File

@@ -266,6 +266,7 @@ class MarkdownEditor extends React.Component {
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
/>
</div>

View File

@@ -40,7 +40,6 @@ body {
code {
font-family: ${codeBlockFontFamily.join(', ')};
background-color: rgba(0,0,0,0.04);
color: #CC305F;
}
.lineNumber {
${lineNumber && 'display: block !important;'}
@@ -51,12 +50,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;
}
@@ -224,6 +223,7 @@ export default class MarkdownPreview extends React.Component {
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
prevProps.lineNumber !== this.props.lineNumber ||
prevProps.showCopyNotification !== this.props.showCopyNotification ||
prevProps.theme !== this.props.theme) {
this.applyStyle()
this.rewriteIframe()
@@ -262,7 +262,7 @@ export default class MarkdownPreview extends React.Component {
el.removeEventListener('click', this.linkClickHandler)
})
let { value, theme, indentSize, codeBlockTheme, storagePath } = this.props
let { value, theme, indentSize, codeBlockTheme, showCopyNotification, storagePath } = this.props
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
@@ -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)
@@ -308,10 +309,12 @@ export default class MarkdownPreview extends React.Component {
copyIcon.innerHTML = '<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
copyIcon.onclick = (e) => {
copy(content)
this.notify('Saved to Clipboard!', {
body: 'Paste it wherever you want!',
silent: true
})
if (showCopyNotification) {
this.notify('Saved to Clipboard!', {
body: 'Paste it wherever you want!',
silent: true
})
}
}
el.parentNode.appendChild(copyIcon)
el.innerHTML = ''
@@ -425,5 +428,6 @@ MarkdownPreview.propTypes = {
onMouseDown: PropTypes.func,
className: PropTypes.string,
value: PropTypes.string,
showCopyNotification: PropTypes.bool,
storagePath: PropTypes.string
}

View File

@@ -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>
)

View File

@@ -11,4 +11,6 @@
height top-bar-height
.esc-mark
font-size 15px
font-size 28px
margin-top -5px
margin-bottom -7px

View File

@@ -4,7 +4,9 @@
import React, { PropTypes } from 'react'
import { isArray } from 'lodash'
import CSSModules from 'browser/lib/CSSModules'
import { getTodoStatus } from 'browser/lib/getTodoStatus'
import styles from './NoteItem.styl'
import TodoProcess from './TodoProcess'
/**
* @description Tag element component.
@@ -68,6 +70,10 @@ const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleDragStar
{note.isStarred
? <i styleName='item-star' className='fa fa-star' /> : ''
}
{note.type === 'MARKDOWN_NOTE'
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
: ''
}
<div styleName='item-bottom'>
<div styleName='item-bottom-tagList'>
{note.tags.length > 0

View File

@@ -39,6 +39,7 @@ $control-height = 30px
.item-wrapper
padding 15px 0
border-bottom $ui-border
position relative
.item--active
@extend .item
@@ -116,8 +117,8 @@ $control-height = 30px
.item-star
position absolute
right 5px
bottom 0px
right -20px
bottom 2px
width 34px
height 34px
color alpha($ui-favorite-star-button-color, 60%)

View File

@@ -48,6 +48,7 @@ $control-height = 30px
overflow ellipsis
color $ui-inactive-text-color
border-bottom $ui-border
position relative
.item-simple-title-icon
font-size 12px

View 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' style={this.props.style}>{link}</div>
)
}
}
RealtimeNotification.propTypes = {}
export default CSSModules(RealtimeNotification, styles)

View File

@@ -0,0 +1,34 @@
.notification-area
z-index 1000
font-size 12px
position absolute
bottom 0px
right 0px
background-color #EBEBEB
height 30px
.notification-link
position absolute
right 5px
top 5px
text-decoration none
color #282A36
border 1px solid #6FA8E6
background-color alpha(#6FA8E6, 0.2)
padding 3px 9px
border-radius 2px
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

View File

@@ -0,0 +1,33 @@
/**
* @fileoverview Percentage of todo achievement.
*/
import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './TodoProcess.styl'
const TodoProcess = ({
todoStatus: {
total: totalTodo,
completed: completedTodo
}
}) => (
<div styleName='todo-process' style={{display: totalTodo > 0 ? '' : 'none'}}>
<div styleName='todo-process-text'>
<i className='fa fa-fw fa-check-square-o' />
{completedTodo} of {totalTodo}
</div>
<div styleName='todo-process-bar'>
<div styleName='todo-process-bar--inner' style={{width: parseInt(completedTodo / totalTodo * 100) + '%'}} />
</div>
</div>
)
TodoProcess.propTypes = {
todoStatus: {
total: PropTypes.number.isRequired,
completed: PropTypes.number.isRequired
}
}
export default CSSModules(TodoProcess, styles)

View File

@@ -0,0 +1,45 @@
.todo-process
font-size 12px
display flex
padding-top 15px
width 85%
.todo-process-text
display inline-block
padding-right 10px
white-space nowrap
text-overflow ellipsis
color $ui-inactive-text-color
i
color $ui-inactive-text-color
padding-right 5px
.todo-process-bar
display inline-block
margin auto
height 4px
border-radius 10px
background-color #DADFE1
border-radius 2px
flex-grow 1
border 1px solid alpha(#6C7A89, 10%)
.todo-process-bar--inner
height 100%
border-radius 5px
background-color #6C7A89
transition 0.3s
body[data-theme="dark"]
.todo-process
color $ui-dark-text-color
.todo-process-bar
background-color #363A3D
.todo-process-text
color $ui-inactive-text-color
.todo-process-bar--inner
background-color: alpha(#939395, 50%)

View File

@@ -193,6 +193,7 @@ ol
&>li>ul, &>li>ol
margin 0
code
color #CC305F
padding 0.2em 0.4em
background-color #f7f7f7
border-radius 3px

View File

@@ -64,7 +64,7 @@ $list-width = 250px
.result-nav-storageList
absolute bottom left right
top 80px + 32px + 10px + 10px
top 110px + 32px + 10px + 10px
overflow-y auto
.result-list

View File

@@ -146,6 +146,7 @@ class NoteDetail extends React.Component {
config={config}
value={snippet.content}
ref={'code-' + index}
storageKey={note.storage}
/>
: <CodeEditor styleName='tabView-content'
mode={snippet.mode}
@@ -195,6 +196,7 @@ class NoteDetail extends React.Component {
lineNumber={config.preview.lineNumber}
indentSize={editorIndentSize}
value={note.content}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
/>
)

View File

@@ -2,6 +2,7 @@
.root
absolute top bottom left right
bottom 30px
left $note-detail-left-margin
right $note-detail-right-margin
height 100%

21
browser/lib/RcParser.js Normal file
View File

@@ -0,0 +1,21 @@
import path from 'path'
import sander from 'sander'
const BOOSTNOTERC = '.boostnoterc'
const homePath = global.process.env.HOME || global.process.env.USERPROFILE
const _boostnotercPath = path.join(homePath, BOOSTNOTERC)
export function parse (boostnotercPath = _boostnotercPath) {
if (!sander.existsSync(boostnotercPath)) return {}
try {
return JSON.parse(sander.readFileSync(boostnotercPath).toString())
} catch (e) {
console.warn(e)
console.warn('Your .boostnoterc is broken so it\'s not used.')
return {}
}
}
export default {
parse
}

View File

@@ -0,0 +1,25 @@
export function getTodoStatus (content) {
let splitted = content.split('\n')
let numberOfTodo = 0
let numberOfCompletedTodo = 0
splitted.forEach((line) => {
let trimmedLine = line.trim()
if (trimmedLine.match(/^[\+\-\*] \[\s|x\] ./)) {
numberOfTodo++
}
if (trimmedLine.match(/^[\+\-\*] \[x\] ./)) {
numberOfCompletedTodo++
}
})
return {
total: numberOfTodo,
completed: numberOfCompletedTodo
}
}
export function getTodoPercentageOfCompleted (content) {
const state = getTodoStatus(content)
return Math.floor(state.completed / state.total * 100)
}

View File

@@ -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

View File

@@ -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'>
@@ -24,7 +24,7 @@ const InfoPanel = ({
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Created at
Created
</div>
<div styleName='group-section-control'>
{createdAt}
@@ -32,7 +32,7 @@ const InfoPanel = ({
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Updated at
Updated
</div>
<div styleName='group-section-control'>
{updatedAt}
@@ -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)

View File

@@ -18,6 +18,16 @@
background-color $ui-noteList-backgroundColor
border 1px solid $border-color
.control-infoButton-panel-trash
z-index 200
margin-top 45px
margin-left -230px
position absolute
padding 20px 20px 0 20px
width 320px
background-color $ui-noteList-backgroundColor
border 1px solid $border-color
.group-section
display flex
line-height 30px
@@ -40,6 +50,19 @@
width 160px
height 25px
.group-section-control text
color #EA4447
font-weight 600
font-size 14px
width 70px
height 25px
background-color rgba(226,33,113,0.1)
border none
outline none
border-radius 2px
margin-right 5px
padding 2px 5px
[id=export-wrap]
height 90px
display flex
@@ -75,6 +98,10 @@ body[data-theme="dark"]
background-color $ui-dark-noteList-backgroundColor
border 1px solid $ui-dark-borderColor
.control-infoButton-panel-trash
background-color $ui-dark-noteList-backgroundColor
border 1px solid $ui-dark-borderColor
.group-section-label
color $ui-inactive-text-color

View File

@@ -0,0 +1,70 @@
import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoPanel.styl'
const InfoPanelTrashed = ({
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt
}) => (
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
<div styleName='group-section'>
<div styleName='group-section-label'>
Storage
</div>
<div styleName='group-section-control'>
{storageName}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Folder
</div>
<div styleName='group-section-control'>
<text>Trash</text>{folderName}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Created
</div>
<div styleName='group-section-control'>
{createdAt}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Updated
</div>
<div styleName='group-section-control'>
{updatedAt}
</div>
</div>
<div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
<i className='fa fa-file-code-o fa-fw' />
<p>.md</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
<i className='fa fa-file-text-o fa-fw' />
<p>.txt</p>
</button>
<button styleName='export--unable'>
<i className='fa fa-file-pdf-o fa-fw' />
<p>.pdf</p>
</button>
</div>
</div>
)
InfoPanelTrashed.propTypes = {
storageName: PropTypes.string.isRequired,
folderName: PropTypes.string.isRequired,
updatedAt: PropTypes.string.isRequired,
createdAt: PropTypes.string.isRequired,
exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired
}
export default CSSModules(InfoPanelTrashed, styles)

View File

@@ -17,7 +17,10 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import TrashButton from './TrashButton'
import InfoButton from './InfoButton'
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
@@ -69,30 +72,12 @@ class MarkdownNoteDetail extends React.Component {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
}
getPercentageOfCompleteTodo (noteContent) {
let splitted = noteContent.split('\n')
let numberOfTodo = 0
let numberOfCompletedTodo = 0
splitted.forEach((line) => {
let trimmedLine = line.trim()
if (trimmedLine.match(/^[\+\-\*] \[\s|x\] ./)) {
numberOfTodo++
}
if (trimmedLine.match(/^[\+\-\*] \[x\] ./)) {
numberOfCompletedTodo++
}
})
return Math.floor(numberOfCompletedTodo / numberOfTodo * 100)
}
handleChange (e) {
let { note } = this.state
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({
@@ -190,8 +175,8 @@ class MarkdownNoteDetail extends React.Component {
if (isTrashed) {
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete a note',
detail: 'This work cannot be undone.',
message: 'Confirm note deletion',
detail: 'This will permanently remove this note.',
buttons: ['Confirm', 'Cancel']
})
if (dialogueButtonIndex === 1) return
@@ -300,10 +285,9 @@ class MarkdownNoteDetail extends React.Component {
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<InfoPanel
<InfoPanelTrashed
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
noteLink={`[${note.title}](${location.query.key})`}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
exportAsMd={this.exportAsMd}
@@ -333,7 +317,7 @@ class MarkdownNoteDetail extends React.Component {
onChange={(e) => this.handleChange(e)}
/>
<TodoListPercentage
percentageOfTodo={this.getPercentageOfCompleteTodo(note.content)}
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
/>
</div>
<div styleName='info-right'>
@@ -357,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)}
@@ -370,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>

View File

@@ -20,6 +20,7 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import TrashButton from './TrashButton'
import InfoButton from './InfoButton'
import InfoPanel from './InfoPanel'
import InfoPanelTrashed from './InfoPanelTrashed'
import { formatDate } from 'browser/lib/date-formatter'
function pass (name) {
@@ -176,8 +177,8 @@ class SnippetNoteDetail extends React.Component {
if (isTrashed) {
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete a note',
detail: 'This work cannot be undone.',
message: 'Confirm note deletion',
detail: 'This will permanently remove this note.',
buttons: ['Confirm', 'Cancel']
})
if (dialogueButtonIndex === 1) return
@@ -270,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
@@ -283,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
@@ -297,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
}, () => {
@@ -519,6 +520,7 @@ class SnippetNoteDetail extends React.Component {
onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index}
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
storageKey={storageKey}
/>
: <CodeEditor styleName='tabView-content'
mode={snippet.mode}
@@ -559,10 +561,9 @@ class SnippetNoteDetail extends React.Component {
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<InfoPanel
<InfoPanelTrashed
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
noteLink={`[${note.title}](${location.query.key})`}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning}
@@ -597,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)}
@@ -610,6 +611,7 @@ class SnippetNoteDetail extends React.Component {
createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning}
exportAsTxt={this.showWarning}
type={note.type}
/>
</div>
</div>

View File

@@ -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

View File

@@ -51,7 +51,7 @@
margin 2px 0 15px 2px
vertical-align middle
height 18px
box-sizing borde-box
box-sizing border-box
border none
background-color transparent
outline none

View File

@@ -49,7 +49,7 @@ class Detail extends React.Component {
tabIndex='0'
>
<div styleName='empty'>
<div styleName='empty-message'>{OSX ? 'Command(⌘)' : 'Ctrl(^)'} + N<br />to create a new post</div>
<div styleName='empty-message'>{OSX ? 'Command(⌘)' : 'Ctrl(^)'} + N<br />to create a new note</div>
</div>
<StatusBar
{..._.pick(this.props, ['config', 'location', 'dispatch'])}

View File

@@ -14,12 +14,14 @@ 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')
}
class Main extends React.Component {
constructor (props) {
super(props)
@@ -172,8 +174,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'
@@ -188,6 +190,17 @@ class Main extends React.Component {
render () {
let { config } = this.props
// the width of the navigation bar when it is folded/collapsed
const foldedNavigationWidth = 44
let notificationBarOffsetLeft
if (this.state.fullScreen) {
notificationBarOffsetLeft = 0
} else if (config.isSideNavFolded) {
notificationBarOffsetLeft = foldedNavigationWidth
} else {
notificationBarOffsetLeft = this.state.navWidth
}
return (
<div
className='Main'
@@ -216,7 +229,7 @@ class Main extends React.Component {
<div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
id='main-body'
ref='body'
style={{left: config.isSideNavFolded ? 44 : this.state.navWidth}}
style={{left: config.isSideNavFolded ? foldedNavigationWidth : this.state.navWidth}}
>
<TopBar style={{width: this.state.listWidth}}
{..._.pick(this.props, [
@@ -255,6 +268,9 @@ class Main extends React.Component {
ignorePreviewPointerEvents={this.state.isRightSliderFocused}
/>
</div>
<RealtimeNotification
style={{left: notificationBarOffsetLeft}}
/>
</div>
)
}

View 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()

View 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)

View File

@@ -2,6 +2,7 @@ $control-height = 30px
.root
absolute left bottom
bottom 30px
top $topBar-height - 1
background-color $ui-noteList-backgroundColor

View File

@@ -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'>
@@ -446,9 +462,9 @@ class NoteList extends React.Component {
value={config.sortBy}
onChange={(e) => this.handleSortByChange(e)}
>
<option value='UPDATED_AT'>Updated Time</option>
<option value='CREATED_AT'>Created Time</option>
<option value='ALPHABETICAL'>Alphabetical</option>
<option value='UPDATED_AT'>Last Updated</option>
<option value='CREATED_AT'>Creation Time</option>
<option value='ALPHABETICAL'>Alphabetically</option>
</select>
</div>
<div styleName='control-button-area'>

View File

@@ -114,7 +114,7 @@ class StorageItem extends React.Component {
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete Folder',
detail: 'This work will deletes all notes in the folder and can not be undone.',
detail: 'This will delete all notes in the folder and can not be undone.',
buttons: ['Confirm', 'Cancel']
})

View File

@@ -3,6 +3,8 @@
.root
absolute bottom left right
height $statusBar-height
bottom 16px
z-index 100
background-color $ui-noteDetail-backgroundColor
display flex

View File

@@ -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!

View File

@@ -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>
)
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -1,10 +1,13 @@
import _ from 'lodash'
import RcParser from 'browser/lib/RcParser'
const OSX = global.process.platform === 'darwin'
const win = global.process.platform === 'win32'
const electron = require('electron')
const { ipcRenderer } = electron
const consts = require('browser/lib/consts')
const path = require('path')
const fs = require('fs')
let isInitialized = false
@@ -22,6 +25,7 @@ export const DEFAULT_CONFIG = {
},
ui: {
theme: 'default',
showCopyNotification: true,
disableDirectWrite: false,
defaultNote: 'ALWAYS_ASK' // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
},
@@ -57,17 +61,17 @@ function _save (config) {
}
function get () {
let config = window.localStorage.getItem('config')
const rawStoredConfig = window.localStorage.getItem('config')
const storedConfig = Object.assign({}, DEFAULT_CONFIG, JSON.parse(rawStoredConfig))
let config = storedConfig
try {
config = Object.assign({}, DEFAULT_CONFIG, JSON.parse(config))
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, config.hotkey)
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, config.ui)
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, config.editor)
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, config.preview)
const boostnotercConfig = RcParser.parse()
config = assignConfigValues(storedConfig, boostnotercConfig)
if (!validate(config)) throw new Error('INVALID CONFIG')
} catch (err) {
console.warn('Boostnote resets the malformed configuration.')
console.warn('Boostnote resets the invalid configuration.')
config = DEFAULT_CONFIG
_save(config)
}
@@ -126,6 +130,15 @@ function set (updates) {
})
}
function assignConfigValues (originalConfig, rcConfig) {
let config = Object.assign({}, DEFAULT_CONFIG, originalConfig, rcConfig)
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, originalConfig.hotkey, rcConfig.hotkey)
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview)
return config
}
export default {
get,
set,

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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&apos;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>This data is only used for Boostnote improvements.</div>
<input onChange={(e) => this.handleConfigChange(e)}
checked={this.state.config.amaEnabled}
ref='amaEnabled'

View File

@@ -43,6 +43,7 @@
text-decoration none
.policy
width 100%
font-size 20px
margin-bottom 10px

View File

@@ -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

View File

@@ -298,8 +298,8 @@ class StorageItem extends React.Component {
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Unlink Storage',
detail: 'This work just detatches a storage from Boostnote. (Any data won\'t be deleted.)',
buttons: ['Confirm', 'Cancel']
detail: 'Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.',
buttons: ['Unlink', 'Cancel']
})
if (index === 0) {

View File

@@ -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>

View File

@@ -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

View File

@@ -36,6 +36,7 @@ class UiTab extends React.Component {
const newConfig = {
ui: {
theme: this.refs.uiTheme.value,
showCopyNotification: this.refs.showCopyNotification.checked,
disableDirectWrite: this.refs.uiD2w != null
? this.refs.uiD2w.checked
: false
@@ -90,9 +91,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)}
@@ -103,6 +103,16 @@ class UiTab extends React.Component {
</select>
</div>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.ui.showCopyNotification}
ref='showCopyNotification'
type='checkbox'
/>&nbsp;
Show &quot;Saved to Clipboard&quot; notification when copying
</label>
</div>
{
global.process.platform === 'win32'
? <div styleName='group-checkBoxSection'>
@@ -192,7 +202,7 @@ class UiTab extends React.Component {
<div styleName='group-section'>
<div styleName='group-section-label'>
Switching Preview
Switch to Preview
</div>
<div styleName='group-section-control'>
<select value={config.editor.switchPreview}
@@ -200,7 +210,7 @@ class UiTab extends React.Component {
onChange={(e) => this.handleUIChange(e)}
>
<option value='BLUR'>When Editor Blurred</option>
<option value='RIGHTCLICK'>When Right Clicking</option>
<option value='RIGHTCLICK'>On Right Click</option>
</select>
</div>
</div>
@@ -218,7 +228,7 @@ class UiTab extends React.Component {
<option value='vim'>vim</option>
<option value='emacs'>emacs</option>
</select>
<span styleName='note-for-keymap'>Please reload boostnote after you change the keymap</span>
<span styleName='note-for-keymap'>Please restart boostnote after you change the keymap</span>
</div>
</div>
@@ -271,7 +281,7 @@ class UiTab extends React.Component {
ref='previewLineNumber'
type='checkbox'
/>&nbsp;
Code block line numbering
Show line numbers for preview code blocks
</label>
</div>

View File

@@ -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