1
0
mirror of https://github.com/BoostIo/Boostnote synced 2026-05-17 04:51:52 +00:00

Merge branch 'master' into delete_dialog

This commit is contained in:
William Grant
2018-04-14 20:20:17 +02:00
37 changed files with 2321 additions and 653 deletions

View File

@@ -109,6 +109,8 @@ export default class CodeEditor extends React.Component {
scrollPastEnd: this.props.scrollPastEnd,
inputStyle: 'textarea',
dragDrop: false,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
autoCloseBrackets: true,
extraKeys: {
Tab: function (cm) {
@@ -275,11 +277,16 @@ export default class CodeEditor extends React.Component {
handleDropImage (e) {
e.preventDefault()
const imagePath = e.dataTransfer.files[0].path
const filename = path.basename(imagePath)
const ValidImageTypes = ['image/gif', 'image/jpeg', 'image/png']
copyImage(imagePath, this.props.storageKey).then((imagePath) => {
const imageMd = `![${filename}](${path.join('/:storage', imagePath)})`
const file = e.dataTransfer.files[0]
const filePath = file.path
const filename = path.basename(filePath)
const fileType = file['type']
copyImage(filePath, this.props.storageKey).then((imagePath) => {
var showPreview = ValidImageTypes.indexOf(fileType) > 0
const imageMd = `${showPreview ? '!' : ''}[${filename}](${path.join('/:storage', imagePath)})`
this.insertImageMd(imageMd)
})
}

View File

@@ -13,6 +13,7 @@ import htmlTextHelper from 'browser/lib/htmlTextHelper'
import copy from 'copy-to-clipboard'
import mdurl from 'mdurl'
import exportNote from 'browser/main/lib/dataApi/exportNote'
import {escapeHtmlCharacters} from 'browser/lib/utils'
const { remote } = require('electron')
const { app } = remote
@@ -208,7 +209,7 @@ export default class MarkdownPreview extends React.Component {
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams()
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber)
const body = this.markdown.render(noteContent)
const body = this.markdown.render(escapeHtmlCharacters(noteContent))
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
files.forEach((file) => {
@@ -394,6 +395,9 @@ export default class MarkdownPreview extends React.Component {
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
this.fixDecodedURI(el)
el.href = this.markdown.normalizeLinkText(el.href)
if (!/\/:storage/.test(el.href)) return
el.href = `file:///${this.markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.href)))}`
el.addEventListener('click', this.anchorClickHandler)
})

View File

@@ -10,8 +10,8 @@ import CSSModules from 'browser/lib/CSSModules'
* @param {Array} storgaeList
*/
const StorageList = ({storageList}) => (
<div styleName='storageList'>
const StorageList = ({storageList, isFolded}) => (
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
{storageList.length > 0 ? storageList : (
<div styleName='storgaeList-empty'>No storage mount.</div>
)}

View File

@@ -4,6 +4,10 @@
top 180px
overflow-y auto
.storageList-folded
@extend .storageList
width 44px
.storageList-empty
padding 0 10px
margin-top 15px

View File

@@ -1,8 +1,15 @@
const path = require('path')
const { remote } = require('electron')
const { app } = remote
// load package for localization
const i18n = new (require('i18n-2'))({
// setup some locales - other locales default to the first locale
locales: ['en', 'sq', 'zh-CN', 'zh-TW', 'da', 'fr', 'de', 'hu', 'ja', 'ko', 'no', 'pl', 'pt', 'es'],
locales: ['en', 'sq', 'zh-CN', 'zh-TW', 'da', 'fr', 'de', 'hu', 'ja', 'ko', 'no', 'pl', 'pt', 'es-ES'],
extension: '.json',
directory: process.env.NODE_ENV === 'production'
? path.join(app.getAppPath(), './locales')
: path.resolve('./locales'),
devMode: false
})

View File

@@ -4,39 +4,28 @@ export default function searchFromNotes (notes, search) {
if (search.trim().length === 0) return []
const searchBlocks = search.split(' ').filter(block => { return block !== '' })
let foundNotes = findByWord(notes, searchBlocks[0])
let foundNotes = notes
searchBlocks.forEach((block) => {
foundNotes = findByWord(foundNotes, block)
if (block.match(/^#.+/)) {
foundNotes = foundNotes.concat(findByTag(notes, block))
}
foundNotes = findByWordOrTag(foundNotes, block)
})
return foundNotes
}
function findByTag (notes, block) {
const tag = block.match(/#(.+)/)[1]
const regExp = new RegExp(_.escapeRegExp(tag), 'i')
function findByWordOrTag (notes, block) {
let tag = block
if (tag.match(/^#.+/)) {
tag = tag.match(/#(.+)/)[1]
}
const tagRegExp = new RegExp(_.escapeRegExp(tag), 'i')
const wordRegExp = new RegExp(_.escapeRegExp(block), 'i')
return notes.filter((note) => {
if (!_.isArray(note.tags)) return false
return note.tags.some((_tag) => {
return _tag.match(regExp)
})
})
}
function findByWord (notes, block) {
const regExp = new RegExp(_.escapeRegExp(block), 'i')
return notes.filter((note) => {
if (_.isArray(note.tags) && note.tags.some((_tag) => {
return _tag.match(regExp)
})) {
if (_.isArray(note.tags) && note.tags.some((_tag) => _tag.match(tagRegExp))) {
return true
}
if (note.type === 'SNIPPET_NOTE') {
return note.description.match(regExp)
return note.description.match(wordRegExp)
} else if (note.type === 'MARKDOWN_NOTE') {
return note.content.match(regExp)
return note.content.match(wordRegExp)
}
return false
})

View File

@@ -6,6 +6,55 @@ export function lastFindInArray (array, callback) {
}
}
export default {
lastFindInArray
export function escapeHtmlCharacters (text) {
const matchHtmlRegExp = /["'&<>]/
const str = '' + text
const match = matchHtmlRegExp.exec(str)
if (!match) {
return str
}
let escape
let html = ''
let index = 0
let lastIndex = 0
for (index = match.index; index < str.length; index++) {
switch (str.charCodeAt(index)) {
case 34: // "
escape = '&quot;'
break
case 38: // &
escape = '&amp;'
break
case 39: // '
escape = '&#39;'
break
case 60: // <
escape = '&lt;'
break
case 62: // >
escape = '&gt;'
break
default:
continue
}
if (lastIndex !== index) {
html += str.substring(lastIndex, index)
}
lastIndex = index + 1
html += escape
}
return lastIndex !== index
? html + str.substring(lastIndex, index)
: html
}
export default {
lastFindInArray,
escapeHtmlCharacters
}

View File

@@ -140,43 +140,37 @@ class Main extends React.Component {
componentDidMount () {
const { dispatch, config } = this.props
if (config.ui.theme === 'dark') {
document.body.setAttribute('data-theme', 'dark')
} else if (config.ui.theme === 'white') {
document.body.setAttribute('data-theme', 'white')
} else if (config.ui.theme === 'solarized-dark') {
document.body.setAttribute('data-theme', 'solarized-dark')
const supportedThemes = [
'dark',
'white',
'solarized-dark'
]
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
document.body.setAttribute('data-theme', config.ui.theme)
} else {
document.body.setAttribute('data-theme', 'default')
}
if (config.ui.language === 'sq') {
i18n.setLocale('sq')
} else if (config.ui.language === 'zh-CN') {
i18n.setLocale('zh-CN')
} else if (config.ui.language === 'zh-TW') {
i18n.setLocale('zh-TW')
} else if (config.ui.language === 'da') {
i18n.setLocale('da')
} else if (config.ui.language === 'fr') {
i18n.setLocale('fr')
} else if (config.ui.language === 'de') {
i18n.setLocale('de')
} else if (config.ui.language === 'hu') {
i18n.setLocale('hu')
} else if (config.ui.language === 'ja') {
i18n.setLocale('ja')
} else if (config.ui.language === 'ko') {
i18n.setLocale('ko')
} else if (config.ui.language === 'no') {
i18n.setLocale('no')
} else if (config.ui.language === 'pl') {
i18n.setLocale('pl')
} else if (config.ui.language === 'pt') {
i18n.setLocale('pt')
} else if (config.ui.language === 'ru') {
i18n.setLocale('ru')
} else if (config.ui.language === 'es') {
i18n.setLocale('es')
const supportedLanguages = [
'sq',
'zh-CN',
'zh-TW',
'da',
'fr',
'de',
'hu',
'ja',
'ko',
'no',
'pl',
'pt',
'ru',
'es-ES'
]
if (supportedLanguages.indexOf(config.ui.language) !== -1) {
i18n.setLocale(config.ui.language)
} else {
i18n.setLocale('en')
}

View File

@@ -327,8 +327,10 @@ class NoteList extends React.Component {
}
if (location.pathname.match(/\/searched/)) {
const searchInputText = document.getElementsByClassName('searchInput')[0].value
if (searchInputText === '') {
const searchInputText = params.searchword
const allNotes = data.noteMap.map((note) => note)
this.contextNotes = allNotes
if (searchInputText === undefined || searchInputText === '') {
return this.sortByPin(this.contextNotes)
}
return searchFromNotes(this.contextNotes, searchInputText)
@@ -482,50 +484,53 @@ class NoteList extends React.Component {
const openBlogLabel = i18n.__('Open Blog')
const menu = new Menu()
if (!location.pathname.match(/\/starred|\/trash/)) {
menu.append(new MenuItem({
label: pinLabel,
click: this.pinToTop
}))
}
if (location.pathname.match(/\/trash/)) {
menu.append(new MenuItem({
label: restoreNote,
click: this.restoreNote
}))
}
menu.append(new MenuItem({
label: deleteLabel,
click: this.deleteNote
}))
menu.append(new MenuItem({
label: cloneNote,
click: this.cloneNote.bind(this)
}))
menu.append(new MenuItem({
label: copyNoteLink,
click: this.copyNoteLink(note)
}))
if (note.type === 'MARKDOWN_NOTE') {
if (note.blog && note.blog.blogLink && note.blog.blogId) {
menu.append(new MenuItem({
label: deleteLabel,
click: this.deleteNote
}))
} else {
if (!location.pathname.match(/\/starred/)) {
menu.append(new MenuItem({
label: updateLabel,
click: this.publishMarkdown.bind(this)
}))
menu.append(new MenuItem({
label: openBlogLabel,
click: () => this.openBlog.bind(this)(note)
}))
} else {
menu.append(new MenuItem({
label: publishLabel,
click: this.publishMarkdown.bind(this)
label: pinLabel,
click: this.pinToTop
}))
}
menu.append(new MenuItem({
label: deleteLabel,
click: this.deleteNote
}))
menu.append(new MenuItem({
label: cloneNote,
click: this.cloneNote.bind(this)
}))
menu.append(new MenuItem({
label: copyNoteLink,
click: this.copyNoteLink(note)
}))
if (note.type === 'MARKDOWN_NOTE') {
if (note.blog && note.blog.blogLink && note.blog.blogId) {
menu.append(new MenuItem({
label: updateLabel,
click: this.publishMarkdown.bind(this)
}))
menu.append(new MenuItem({
label: openBlogLabel,
click: () => this.openBlog.bind(this)(note)
}))
} else {
menu.append(new MenuItem({
label: publishLabel,
click: this.publishMarkdown.bind(this)
}))
}
}
}
menu.popup()
}

View File

@@ -30,11 +30,33 @@
display flex
flex-direction column
.tag-title
padding-left 15px
padding-bottom 13px
p
color $ui-button-default-color
.tag-control
display flex
height 30px
line-height 25px
overflow hidden
.tag-control-title
padding-left 15px
padding-bottom 13px
flex 1
p
color $ui-button-default-color
.tag-control-sortTagsBy
user-select none
font-size 12px
color $ui-inactive-text-color
margin-left 12px
margin-right 12px
.tag-control-sortTagsBy-select
appearance: none;
margin-left 5px
color $ui-inactive-text-color
padding 0
border none
background-color transparent
outline none
cursor pointer
font-size 12px
.tagList
overflow-y auto

View File

@@ -82,7 +82,7 @@ class SideNav extends React.Component {
}
SideNavComponent (isFolded, storageList) {
const { location, data } = this.props
const { location, data, config } = this.props
const isHomeActive = !!location.pathname.match(/^\/home$/)
const isStarredActive = !!location.pathname.match(/^\/starred$/)
@@ -108,15 +108,30 @@ class SideNav extends React.Component {
handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(this)}
/>
<StorageList storageList={storageList} />
<StorageList storageList={storageList} isFolded={isFolded} />
<NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} />
</div>
)
} else {
component = (
<div styleName='tabBody'>
<div styleName='tag-title'>
<p>{i18n.__('Tags')}</p>
<div styleName='tag-control'>
<div styleName='tag-control-title'>
<p>{i18n.__('Tags')}</p>
</div>
<div styleName='tag-control-sortTagsBy'>
<i className='fa fa-angle-down' />
<select styleName='tag-control-sortTagsBy-select'
title={i18n.__('Select filter mode')}
value={config.sortTagsBy}
onChange={(e) => this.handleSortTagsByChange(e)}
>
<option title='Sort alphabetically'
value='ALPHABETICAL'>{i18n.__('Alphabetically')}</option>
<option title='Sort by update time'
value='COUNTER'>{i18n.__('Counter')}</option>
</select>
</div>
</div>
<div styleName='tagList'>
{this.tagListComponent(data)}
@@ -129,17 +144,21 @@ class SideNav extends React.Component {
}
tagListComponent () {
const { data, location } = this.props
const tagList = _.sortBy(data.tagNoteMap.map((tag, name) => {
return { name, size: tag.size }
}), ['name'])
const { data, location, config } = this.props
let tagList = _.sortBy(data.tagNoteMap.map(
(tag, name) => ({name, size: tag.size})),
['name']
)
if (config.sortTagsBy === 'COUNTER') {
tagList = _.sortBy(tagList, item => (0 - item.size))
}
return (
tagList.map(tag => {
return (
<TagListItem
name={tag.name}
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
isActive={this.getTagActive(location.pathname, tag)}
isActive={this.getTagActive(location.pathname, tag.name)}
key={tag.name}
count={tag.size}
/>
@@ -159,6 +178,20 @@ class SideNav extends React.Component {
router.push(`/tags/${name}`)
}
handleSortTagsByChange (e) {
const { dispatch } = this.props
const config = {
sortTagsBy: e.target.value
}
ConfigManager.set(config)
dispatch({
type: 'SET_CONFIG',
config
})
}
emptyTrash (entries) {
const { dispatch } = this.props
const deletionPromises = entries.map((note) => {

View File

@@ -28,6 +28,14 @@ class TopBar extends React.Component {
}
componentDidMount () {
const { params } = this.props
const searchWord = params.searchword
if (searchWord !== undefined) {
this.setState({
search: searchWord,
isSearching: true
})
}
ee.on('top:focus-search', this.focusSearchHandler)
ee.on('code:init', this.codeInitHandler)
}
@@ -97,9 +105,10 @@ class TopBar extends React.Component {
this.setState({
isConfirmTranslation: true
})
router.push('/searched')
const keyword = this.refs.searchInput.value
router.push(`/searched/${encodeURIComponent(keyword)}`)
this.setState({
search: this.refs.searchInput.value
search: keyword
})
}
}
@@ -108,7 +117,7 @@ class TopBar extends React.Component {
const { router } = this.context
const keyword = this.refs.searchInput.value
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
router.push('/searched')
router.push(`/searched/${encodeURIComponent(keyword)}`)
} else {
e.preventDefault()
}

View File

@@ -108,6 +108,21 @@ body[data-theme="dark"]
background #B1D7FE
::selection
background #B1D7FE
.CodeMirror-foldmarker
font-family: arial
.CodeMirror-foldgutter
width: .7em
.CodeMirror-foldgutter-open,
.CodeMirror-foldgutter-folded
cursor: pointer
.CodeMirror-foldgutter-open:after
content: "\25BE"
.CodeMirror-foldgutter-folded:after
content: "\25B8"
.sortableItemHelper
z-index modalZIndex + 5

View File

@@ -64,7 +64,9 @@ ReactDOM.render((
<IndexRedirect to='/home' />
<Route path='home' />
<Route path='starred' />
<Route path='searched' />
<Route path='searched'>
<Route path=':searchword' />
</Route>
<Route path='trashed' />
<Route path='alltags' />
<Route path='tags'>

View File

@@ -16,6 +16,7 @@ export const DEFAULT_CONFIG = {
listWidth: 280,
navWidth: 200,
sortBy: 'UPDATED_AT', // 'CREATED_AT', 'UPDATED_AT', 'APLHABETICAL'
sortTagsBy: 'ALPHABETICAL', // 'ALPHABETICAL', 'COUNTER'
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
amaEnabled: true,
hotkey: {
@@ -138,37 +139,7 @@ function set (updates) {
document.body.setAttribute('data-theme', 'default')
}
if (newConfig.ui.language === 'sq') {
i18n.setLocale('sq')
} else if (newConfig.ui.language === 'zh-CN') {
i18n.setLocale('zh-CN')
} else if (newConfig.ui.language === 'zh-TW') {
i18n.setLocale('zh-TW')
} else if (newConfig.ui.language === 'da') {
i18n.setLocale('da')
} else if (newConfig.ui.language === 'fr') {
i18n.setLocale('fr')
} else if (newConfig.ui.language === 'de') {
i18n.setLocale('de')
} else if (newConfig.ui.language === 'hu') {
i18n.setLocale('hu')
} else if (newConfig.ui.language === 'ja') {
i18n.setLocale('ja')
} else if (newConfig.ui.language === 'ko') {
i18n.setLocale('ko')
} else if (newConfig.ui.language === 'no') {
i18n.setLocale('no')
} else if (newConfig.ui.language === 'pl') {
i18n.setLocale('pl')
} else if (newConfig.ui.language === 'pt') {
i18n.setLocale('pt')
} else if (newConfig.ui.language === 'ru') {
i18n.setLocale('ru')
} else if (newConfig.ui.language === 'es') {
i18n.setLocale('es')
} else {
i18n.setLocale('en')
}
i18n.setLocale(newConfig.ui.language)
let editorTheme = document.getElementById('editorTheme')
if (editorTheme == null) {

View File

@@ -27,9 +27,12 @@ function resolveStorageNotes (storage) {
data.storage = storage.key
return data
} catch (err) {
console.error(notePath)
console.error(`error on note path: ${notePath}, error: ${err}`)
}
})
.filter(function filterOnlyNoteObject (noteObj) {
return typeof noteObj === 'object'
})
return Promise.resolve(notes)
}