1
0
mirror of https://github.com/BoostIo/Boostnote synced 2026-02-21 19:58:49 +00:00

Merge branch 'master' into export-note-with-images

# Conflicts:
#	browser/components/MarkdownPreview.js
This commit is contained in:
Nikolay Lopin
2018-02-05 12:59:45 +03:00
87 changed files with 695 additions and 2445 deletions

View File

@@ -20,11 +20,13 @@
body[data-theme="dark"]
.root
background-color $ui-dark-backgroundColor
border-left 1px solid $ui-dark-borderColor
.empty-message
color $ui-dark-inactive-text-color
body[data-theme="solarized-dark"]
.root
background-color $ui-solarized-dark-noteDetail-backgroundColor
border-left 1px solid $ui-solarized-dark-borderColor
.empty-message
color $ui-solarized-dark-text-color

View File

@@ -3,20 +3,14 @@
border solid 1px transparent
vertical-align middle
border-radius 2px
height 30px
transition 0.15s
user-select none
margin-right 10px
&:hover
background-color $ui-button--hover-backgroundColor
.root--search, .root--focus
@extend .root
background-color $ui-noteDetail-backgroundColor = #fff
border-color $ui-input--focus-borderColor
width 154px
height 30px
&:hover
border-color $ui-input--focus-borderColor = #fff
.idle
position relative

View File

@@ -8,8 +8,8 @@
tooltip()
position absolute
pointer-events none
top 26px
right 0
top 50px
right 70px
z-index 200
padding 5px
line-height normal

View File

@@ -8,8 +8,8 @@
tooltip()
position absolute
pointer-events none
top 26px
right 0
top 50px
right 20px
z-index 200
padding 5px
line-height normal

View File

@@ -2,82 +2,97 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoPanel.styl'
import copy from 'copy-to-clipboard'
const InfoPanel = ({
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, wordCount, letterCount, type, print
}) => (
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
<div>
<p styleName='modification-date'>{updatedAt}</p>
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
</div>
class InfoPanel extends React.Component {
copyNoteLink () {
const {noteLink} = this.props
this.refs.noteLink.select()
copy(noteLink)
}
<hr />
{type === 'SNIPPET_NOTE'
? ''
: <div styleName='count-wrap'>
<div styleName='count-number'>
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
<p styleName='infoPanel-sub-count'>Words</p>
render () {
const {
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, wordCount, letterCount, type, print
} = this.props
return (
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
<div>
<p styleName='modification-date'>{updatedAt}</p>
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
</div>
<div styleName='count-number'>
<p styleName='infoPanel-defaul-count'>{letterCount}</p>
<p styleName='infoPanel-sub-count'>Letters</p>
<hr />
{type === 'SNIPPET_NOTE'
? ''
: <div styleName='count-wrap'>
<div styleName='count-number'>
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
<p styleName='infoPanel-sub-count'>Words</p>
</div>
<div styleName='count-number'>
<p styleName='infoPanel-defaul-count'>{letterCount}</p>
<p styleName='infoPanel-sub-count'>Letters</p>
</div>
</div>
}
{type === 'SNIPPET_NOTE'
? ''
: <hr />
}
<div>
<p styleName='infoPanel-default'>{storageName}</p>
<p styleName='infoPanel-sub'>STORAGE</p>
</div>
<div>
<p styleName='infoPanel-default'>{folderName}</p>
<p styleName='infoPanel-sub'>FOLDER</p>
</div>
<div>
<p styleName='infoPanel-default'>{createdAt}</p>
<p styleName='infoPanel-sub'>CREATION DATE</p>
</div>
<div>
<input styleName='infoPanel-noteLink' ref='noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
<i className='fa fa-clipboard' />
</button>
<p styleName='infoPanel-sub'>NOTE LINK</p>
</div>
<hr />
<div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
<i className='fa fa-file-code-o' />
<p>.md</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
<i className='fa fa-file-text-o' />
<p>.txt</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
<i className='fa fa-html5' />
<p>.html</p>
</button>
<button styleName='export--enable' onClick={(e) => print(e)}>
<i className='fa fa-print' />
<p>Print</p>
</button>
</div>
</div>
}
{type === 'SNIPPET_NOTE'
? ''
: <hr />
}
<div>
<p styleName='infoPanel-default'>{storageName}</p>
<p styleName='infoPanel-sub'>STORAGE</p>
</div>
<div>
<p styleName='infoPanel-default'>{folderName}</p>
<p styleName='infoPanel-sub'>FOLDER</p>
</div>
<div>
<p styleName='infoPanel-default'>{createdAt}</p>
<p styleName='infoPanel-sub'>CREATION DATE</p>
</div>
<div>
<input styleName='infoPanel-noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
<p styleName='infoPanel-sub'>NOTE LINK</p>
</div>
<hr />
<div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
<i className='fa fa-file-code-o' />
<p>.md</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
<i className='fa fa-file-text-o' />
<p>.txt</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
<i className='fa fa-html5' />
<p>.html</p>
</button>
<button styleName='export--enable' onClick={(e) => print(e)}>
<i className='fa fa-print' />
<p>Print</p>
</button>
</div>
</div>
)
)
}
}
InfoPanel.propTypes = {
storageName: PropTypes.string.isRequired,

View File

@@ -11,11 +11,10 @@
.control-infoButton-panel
z-index 200
margin-top 0px
right 0
right 25px
position absolute
padding 20px 25px 0 25px
width 300px
height 350px
overflow auto
background-color $ui-noteList-backgroundColor
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
@@ -70,15 +69,30 @@
color $ui-text-color
.infoPanel-sub
font-size 14px
font-size 12px
font-weight 600
color $ui-inactive-text-color
padding-bottom 8px
.infoPanel-noteLink
padding-right 5px
width 200px
width 210px
height 25px
margin-bottom 6px
margin 6px 0
.infoPanel-copyButton
outline none
font-size 16px
color #A0A0A0
background-color transparent
border none
margin 0 5px
border-radius 5px
cursor pointer
&:hover
transition 0.2s
background-color alpha($ui-button--hover-backgroundColor, 30%)
color $ui-inactive-text-color
.infoPanel-trash
color #EA4447

View File

@@ -28,10 +28,6 @@ import { formatDate } from 'browser/lib/date-formatter'
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
import striptags from 'striptags'
const electron = require('electron')
const { remote } = electron
const { dialog } = remote
class MarkdownNoteDetail extends React.Component {
constructor (props) {
super(props)
@@ -187,38 +183,37 @@ class MarkdownNoteDetail extends React.Component {
handleTrashButtonClick (e) {
const { note } = this.state
const { isTrashed } = note
const { confirmDeletion } = this.props
if (isTrashed) {
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Confirm note deletion',
detail: 'This will permanently remove this note.',
buttons: ['Confirm', 'Cancel']
})
if (dialogueButtonIndex === 1) return
const { note, dispatch } = this.props
dataApi
.deleteNote(note.storage, note.key)
.then((data) => {
const dispatchHandler = () => {
dispatch({
type: 'DELETE_NOTE',
storageKey: data.storageKey,
noteKey: data.noteKey
})
}
ee.once('list:moved', dispatchHandler)
})
if (confirmDeletion(true)) {
const {note, dispatch} = this.props
dataApi
.deleteNote(note.storage, note.key)
.then((data) => {
const dispatchHandler = () => {
dispatch({
type: 'DELETE_NOTE',
storageKey: data.storageKey,
noteKey: data.noteKey
})
}
ee.once('list:moved', dispatchHandler)
})
}
} else {
note.isTrashed = true
if (confirmDeletion()) {
note.isTrashed = true
this.setState({
note
}, () => {
this.save()
})
this.setState({
note
}, () => {
this.save()
})
ee.emit('list:next')
}
}
ee.emit('list:next')
}
handleUndoButtonClick (e) {
@@ -372,10 +367,6 @@ class MarkdownNoteDetail extends React.Component {
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
</div>
<div styleName='info-right'>
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<StarButton
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
@@ -389,6 +380,7 @@ class MarkdownNoteDetail extends React.Component {
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
>
<img styleName='iconInfo' src={imgSrc} />
{this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
</button>
return (
@@ -400,6 +392,10 @@ class MarkdownNoteDetail extends React.Component {
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<InfoPanel
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
@@ -447,7 +443,8 @@ MarkdownNoteDetail.propTypes = {
style: PropTypes.shape({
left: PropTypes.number
}),
ignorePreviewPointerEvents: PropTypes.bool
ignorePreviewPointerEvents: PropTypes.bool,
confirmDeletion: PropTypes.bool.isRequired
}
export default CSSModules(MarkdownNoteDetail, styles)

View File

@@ -12,11 +12,27 @@
padding-bottom 3px
.control-lockButton
top 150px
topBarButtonRight()
position absolute
right 225px
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 35px
right -10px
width 50px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.trashed-infopanel
top 40px
position relative
.body
@@ -25,7 +41,7 @@
right 0
top $info-height + $info-margin-under-border
bottom $statusBar-height
margin 0 45px
margin 0 30px
.body-noteEditor
absolute top bottom left right

View File

@@ -1,6 +1,6 @@
@import('DetailVars')
$info-height = 50px
$info-height = 60px
$info-margin-under-border = 30px
.info
@@ -8,11 +8,11 @@ $info-margin-under-border = 30px
left 0
right 0
height $info-height
border-bottom 1px solid #eee
background-color $ui-noteDetail-backgroundColor
width 100%
display flex
align-items center
padding 0 20px
.info-left
padding 0 10px
@@ -20,7 +20,6 @@ $info-margin-under-border = 30px
display flex
align-items center
.info-left-top-folderSelect
display flex
align-items center
@@ -45,12 +44,9 @@ $info-margin-under-border = 30px
color $ui-button--color
.info-right
position absolute
right 40px
top 60px
bottom 1px
padding-left 30px
z-index 101
display inline-flex
margin-top 3px
.undo-button
width 34px

View File

@@ -176,38 +176,37 @@ class SnippetNoteDetail extends React.Component {
handleTrashButtonClick (e) {
const { note } = this.state
const { isTrashed } = note
const { confirmDeletion } = this.props
if (isTrashed) {
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Confirm note deletion',
detail: 'This will permanently remove this note.',
buttons: ['Confirm', 'Cancel']
})
if (dialogueButtonIndex === 1) return
const { note, dispatch } = this.props
dataApi
.deleteNote(note.storage, note.key)
.then((data) => {
const dispatchHandler = () => {
dispatch({
type: 'DELETE_NOTE',
storageKey: data.storageKey,
noteKey: data.noteKey
})
}
ee.once('list:moved', dispatchHandler)
})
if (confirmDeletion(true)) {
const {note, dispatch} = this.props
dataApi
.deleteNote(note.storage, note.key)
.then((data) => {
const dispatchHandler = () => {
dispatch({
type: 'DELETE_NOTE',
storageKey: data.storageKey,
noteKey: data.noteKey
})
}
ee.once('list:moved', dispatchHandler)
})
}
} else {
note.isTrashed = true
if (confirmDeletion()) {
note.isTrashed = true
this.setState({
note
}, () => {
this.save()
})
this.setState({
note
}, () => {
this.save()
})
ee.emit('list:next')
}
}
ee.emit('list:next')
}
handleUndoButtonClick (e) {
@@ -565,6 +564,7 @@ class SnippetNoteDetail extends React.Component {
fontSize={editorFontSize}
indentType={config.editor.indentType}
indentSize={editorIndentSize}
displayLineNumbers={config.editor.displayLineNumbers}
keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd}
onChange={(e) => this.handleCodeChange(index)(e)}
@@ -627,10 +627,6 @@ class SnippetNoteDetail extends React.Component {
/>
</div>
<div styleName='info-right'>
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<StarButton
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
@@ -638,10 +634,16 @@ class SnippetNoteDetail extends React.Component {
<button styleName='control-fullScreenButton' title='Fullscreen'
onMouseDown={(e) => this.handleFullScreenButton(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-sidebar.svg' />
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>Fullscreen</span>
</button>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<InfoPanel
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
@@ -731,7 +733,8 @@ SnippetNoteDetail.propTypes = {
style: PropTypes.shape({
left: PropTypes.number
}),
ignorePreviewPointerEvents: PropTypes.bool
ignorePreviewPointerEvents: PropTypes.bool,
confirmDeletion: PropTypes.bool.isRequired
}
export default CSSModules(SnippetNoteDetail, styles)

View File

@@ -9,8 +9,7 @@
.body
absolute left right
left $snippet-note-detail-left-margin
right $snippet-note-detail-right-margin
margin 0 30px
top $info-height + $info-margin-under-border
bottom $statusBar-height
background-color $ui-noteDetail-backgroundColor
@@ -70,6 +69,21 @@
top 80px
margin-bottom 10px
topBarButtonRight()
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 50px
right 70px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
body[data-theme="white"]
.root

View File

@@ -11,9 +11,9 @@
tooltip()
position absolute
pointer-events none
top 26px
right 0
width 100%
top 50px
right 115px
width 40px
z-index 200
padding 5px
line-height normal

View File

@@ -64,7 +64,8 @@ class TagSelect extends React.Component {
submitTag () {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
let { value } = this.props
const newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
newTag = newTag.charAt(0) === '#' ? newTag.substring(1) : newTag
if (newTag.length <= 0) {
this.setState({

View File

@@ -6,7 +6,8 @@
width 100%
overflow-x scroll
white-space nowrap
margin-right 10px
margin-top 31px
position absolute
.root::-webkit-scrollbar
display none

View File

@@ -8,10 +8,10 @@ const ToggleModeButton = ({
}) => (
<div styleName='control-toggleModeButton'>
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => onClick('SPLIT')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-split-on.svg' : '../resources/icon/icon-mode-split-on-active.svg'} />
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} />
</div>
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : '../resources/icon/icon-mode-markdown-off.svg'} />
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
</div>
<span styleName='tooltip'>Toggle Mode</span>
</div>

View File

@@ -1,24 +1,28 @@
.control-toggleModeButton
border 1px solid #eee
height 34px
height 25px
border-radius 50px
background-color #F4F4F4
width 52px
display flex
align-items center
position absolute
right 165px
.active
background-color #1EC38B
width 33px
height 24px
box-shadow 2px 0px 7px #eee
z-index 1
div
width 40px
height 100%
background-color #f9f9f9
border-radius 50%
display flex
align-items center
justify-content center
cursor pointer
&:first-child
border-right 1px solid #eee
.active
background-color #fff
box-shadow 2px 0px 7px #eee
z-index 1
&:hover .tooltip
opacity 1
@@ -26,9 +30,10 @@
tooltip()
position absolute
pointer-events none
top 47px
right 11px
top 33px
left -10px
z-index 200
width 80px
padding 5px
line-height normal
border-radius 2px
@@ -40,22 +45,14 @@ body[data-theme="dark"]
topBarButtonDark()
.control-toggleModeButton
border 1px solid #444444
div
background-color $ui-dark-noteDetail-backgroundColor
&:first-child
border-right 1px solid #444444
.active
background-color #3A404C
.active
background-color #1EC38B
box-shadow 2px 0px 7px #444444
body[data-theme="solarized-dark"]
.control-toggleModeButton
border 1px solid #586E75
div
background-color $ui-solarized-dark-noteDetail-backgroundColor
&:first-child
border-right 1px solid #586E75
.active
background-color #002B36
.active
background-color #1EC38B
box-shadow 2px 0px 7px #222222

View File

@@ -8,8 +8,8 @@
tooltip()
position absolute
pointer-events none
top 26px
right 0
top 50px
right 50px
z-index 200
padding 5px
line-height normal

View File

@@ -32,6 +32,26 @@ class Detail extends React.Component {
ee.off('detail:delete', this.deleteHandler)
}
confirmDeletion (permanent) {
if (this.props.config.ui.confirmDeletion || permanent) {
const electron = require('electron')
const { remote } = electron
const { dialog } = remote
const alertConfig = {
type: 'warning',
message: 'Confirm note deletion',
detail: 'This will permanently remove this note.',
buttons: ['Confirm', 'Cancel']
}
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), alertConfig)
return dialogueButtonIndex === 0
}
return true
}
render () {
const { location, data, config } = this.props
let note = null
@@ -64,6 +84,7 @@ class Detail extends React.Component {
<SnippetNoteDetail
note={note}
config={config}
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
ref='root'
{..._.pick(this.props, [
'dispatch',
@@ -80,6 +101,7 @@ class Detail extends React.Component {
<MarkdownNoteDetail
note={note}
config={config}
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
ref='root'
{..._.pick(this.props, [
'dispatch',