1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-20 13:11:44 +00:00

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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