mirror of
https://github.com/BoostIo/Boostnote
synced 2026-01-30 09:07:21 +00:00
Merge branch 'master' into blinking-markdown-crunch-fix
This commit is contained in:
@@ -4,12 +4,14 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './FullscreenButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
|
||||
const FullscreenButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
|
||||
<span styleName='tooltip'>{i18n.__('Fullscreen')}</span>
|
||||
<span styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
.control-infoButton-panel-trash
|
||||
z-index 200
|
||||
margin-top 0px
|
||||
top 50px
|
||||
right 0px
|
||||
position absolute
|
||||
padding 20px 25px 0 25px
|
||||
|
||||
@@ -32,7 +32,7 @@ import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||
|
||||
const electron = require('electron')
|
||||
const { remote } = electron
|
||||
const { Menu, MenuItem, dialog } = remote
|
||||
const { dialog } = remote
|
||||
|
||||
class SnippetNoteDetail extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -441,7 +441,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
const isSuper = global.process.platform === 'darwin'
|
||||
? e.metaKey
|
||||
: e.ctrlKey
|
||||
if (isSuper) {
|
||||
if (isSuper && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
this.addSnippet()
|
||||
}
|
||||
@@ -451,14 +451,14 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleModeButtonClick (e, index) {
|
||||
const menu = new Menu()
|
||||
const templetes = []
|
||||
CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => {
|
||||
menu.append(new MenuItem({
|
||||
templetes.push({
|
||||
label: mode.name,
|
||||
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
|
||||
}))
|
||||
})
|
||||
})
|
||||
menu.popup(remote.getCurrentWindow())
|
||||
context.popup(templetes)
|
||||
}
|
||||
|
||||
handleIndentTypeButtonClick (e) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import styles from './TagSelect.styl'
|
||||
import _ from 'lodash'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
|
||||
class TagSelect extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -13,16 +14,26 @@ class TagSelect extends React.Component {
|
||||
this.state = {
|
||||
newTag: ''
|
||||
}
|
||||
this.addtagHandler = this.handleAddTag.bind(this)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.value = this.props.value
|
||||
ee.on('editor:add-tag', this.addtagHandler)
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this.value = this.props.value
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
ee.off('editor:add-tag', this.addtagHandler)
|
||||
}
|
||||
|
||||
handleAddTag () {
|
||||
this.refs.newTag.focus()
|
||||
}
|
||||
|
||||
handleNewTagInputKeyDown (e) {
|
||||
switch (e.keyCode) {
|
||||
case 9:
|
||||
|
||||
@@ -9,6 +9,7 @@ import ee from 'browser/main/lib/eventEmitter'
|
||||
import StatusBar from '../StatusBar'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import debounceRender from 'react-debounce-render'
|
||||
import searchFromNotes from 'browser/lib/search'
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
|
||||
@@ -35,11 +36,38 @@ class Detail extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { location, data, config } = this.props
|
||||
const { location, data, params, config } = this.props
|
||||
let note = null
|
||||
|
||||
if (location.query.key != null) {
|
||||
const noteKey = location.query.key
|
||||
note = data.noteMap.get(noteKey)
|
||||
const allNotes = data.noteMap.map(note => note)
|
||||
const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
|
||||
let displayedNotes = allNotes
|
||||
|
||||
if (location.pathname.match(/\/searched/)) {
|
||||
const searchStr = params.searchword
|
||||
displayedNotes = searchStr === undefined || searchStr === '' ? allNotes
|
||||
: searchFromNotes(allNotes, searchStr)
|
||||
}
|
||||
|
||||
if (location.pathname.match(/\/tags/)) {
|
||||
const listOfTags = params.tagname.split(' ')
|
||||
displayedNotes = data.noteMap.map(note => note).filter(note =>
|
||||
listOfTags.every(tag => note.tags.includes(tag))
|
||||
)
|
||||
}
|
||||
|
||||
if (location.pathname.match(/\/trashed/)) {
|
||||
displayedNotes = trashedNotes
|
||||
} else {
|
||||
displayedNotes = _.differenceWith(displayedNotes, trashedNotes, (note, trashed) => note.key === trashed.key)
|
||||
}
|
||||
|
||||
const noteKeys = displayedNotes.map(note => note.key)
|
||||
if (noteKeys.includes(noteKey)) {
|
||||
note = data.noteMap.get(noteKey)
|
||||
}
|
||||
}
|
||||
|
||||
if (note == null) {
|
||||
|
||||
@@ -21,9 +21,10 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import Markdown from '../../lib/markdown'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||
import context from 'browser/lib/context'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { Menu, MenuItem, dialog } = remote
|
||||
const { dialog } = remote
|
||||
const WP_POST_PATH = '/wp/v2/posts'
|
||||
|
||||
function sortByCreatedAt (a, b) {
|
||||
@@ -491,55 +492,51 @@ class NoteList extends React.Component {
|
||||
const updateLabel = i18n.__('Update Blog')
|
||||
const openBlogLabel = i18n.__('Open Blog')
|
||||
|
||||
const menu = new Menu()
|
||||
const templates = []
|
||||
|
||||
if (location.pathname.match(/\/trash/)) {
|
||||
menu.append(new MenuItem({
|
||||
templates.push({
|
||||
label: restoreNote,
|
||||
click: this.restoreNote
|
||||
}))
|
||||
menu.append(new MenuItem({
|
||||
}, {
|
||||
label: deleteLabel,
|
||||
click: this.deleteNote
|
||||
}))
|
||||
})
|
||||
} else {
|
||||
if (!location.pathname.match(/\/starred/)) {
|
||||
menu.append(new MenuItem({
|
||||
templates.push({
|
||||
label: pinLabel,
|
||||
click: this.pinToTop
|
||||
}))
|
||||
})
|
||||
}
|
||||
menu.append(new MenuItem({
|
||||
templates.push({
|
||||
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({
|
||||
templates.push({
|
||||
label: updateLabel,
|
||||
click: this.publishMarkdown.bind(this)
|
||||
}))
|
||||
menu.append(new MenuItem({
|
||||
}, {
|
||||
label: openBlogLabel,
|
||||
click: () => this.openBlog.bind(this)(note)
|
||||
}))
|
||||
})
|
||||
} else {
|
||||
menu.append(new MenuItem({
|
||||
templates.push({
|
||||
label: publishLabel,
|
||||
click: this.publishMarkdown.bind(this)
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
menu.popup()
|
||||
context.popup(templates)
|
||||
}
|
||||
|
||||
updateSelectedNotes (updateFunc, cleanSelection = true) {
|
||||
|
||||
@@ -11,9 +11,10 @@ import StorageItemChild from 'browser/components/StorageItem'
|
||||
import _ from 'lodash'
|
||||
import { SortableElement } from 'react-sortable-hoc'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import context from 'browser/lib/context'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { Menu, dialog } = remote
|
||||
const { dialog } = remote
|
||||
const escapeStringRegexp = require('escape-string-regexp')
|
||||
const path = require('path')
|
||||
|
||||
@@ -21,13 +22,15 @@ class StorageItem extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
const { storage } = this.props
|
||||
|
||||
this.state = {
|
||||
isOpen: true
|
||||
isOpen: !!storage.isOpen
|
||||
}
|
||||
}
|
||||
|
||||
handleHeaderContextMenu (e) {
|
||||
const menu = Menu.buildFromTemplate([
|
||||
context.popup([
|
||||
{
|
||||
label: i18n.__('Add Folder'),
|
||||
click: (e) => this.handleAddFolderButtonClick(e)
|
||||
@@ -40,8 +43,6 @@ class StorageItem extends React.Component {
|
||||
click: (e) => this.handleUnlinkStorageClick(e)
|
||||
}
|
||||
])
|
||||
|
||||
menu.popup()
|
||||
}
|
||||
|
||||
handleUnlinkStorageClick (e) {
|
||||
@@ -68,8 +69,18 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleToggleButtonClick (e) {
|
||||
const { storage, dispatch } = this.props
|
||||
const isOpen = !this.state.isOpen
|
||||
dataApi.toggleStorage(storage.key, isOpen)
|
||||
.then((storage) => {
|
||||
dispatch({
|
||||
type: 'EXPAND_STORAGE',
|
||||
storage,
|
||||
isOpen
|
||||
})
|
||||
})
|
||||
this.setState({
|
||||
isOpen: !this.state.isOpen
|
||||
isOpen: isOpen
|
||||
})
|
||||
}
|
||||
|
||||
@@ -94,7 +105,7 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleFolderButtonContextMenu (e, folder) {
|
||||
const menu = Menu.buildFromTemplate([
|
||||
context.popup([
|
||||
{
|
||||
label: i18n.__('Rename Folder'),
|
||||
click: (e) => this.handleRenameFolderClick(e, folder)
|
||||
@@ -123,8 +134,6 @@ class StorageItem extends React.Component {
|
||||
click: (e) => this.handleFolderDeleteClick(e, folder)
|
||||
}
|
||||
])
|
||||
|
||||
menu.popup()
|
||||
}
|
||||
|
||||
handleRenameFolderClick (e, folder) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
const { remote } = require('electron')
|
||||
const { Menu } = remote
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import styles from './SideNav.styl'
|
||||
import { openModal } from 'browser/main/lib/modal'
|
||||
@@ -19,6 +17,7 @@ import ListButton from './ListButton'
|
||||
import TagButton from './TagButton'
|
||||
import {SortableContainer} from 'react-sortable-hoc'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import context from 'browser/lib/context'
|
||||
|
||||
class SideNav extends React.Component {
|
||||
// TODO: should not use electron stuff v0.7
|
||||
@@ -254,10 +253,9 @@ class SideNav extends React.Component {
|
||||
handleFilterButtonContextMenu (event) {
|
||||
const { data } = this.props
|
||||
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||
const menu = Menu.buildFromTemplate([
|
||||
context.popup([
|
||||
{ label: i18n.__('Empty Trash'), click: () => this.emptyTrash(trashedNotes) }
|
||||
])
|
||||
menu.popup()
|
||||
}
|
||||
|
||||
render () {
|
||||
|
||||
@@ -4,10 +4,11 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './StatusBar.styl'
|
||||
import ZoomManager from 'browser/main/lib/ZoomManager'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import context from 'browser/lib/context'
|
||||
|
||||
const electron = require('electron')
|
||||
const { remote, ipcRenderer } = electron
|
||||
const { Menu, MenuItem, dialog } = remote
|
||||
const { dialog } = remote
|
||||
|
||||
const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
|
||||
|
||||
@@ -26,16 +27,16 @@ class StatusBar extends React.Component {
|
||||
}
|
||||
|
||||
handleZoomButtonClick (e) {
|
||||
const menu = new Menu()
|
||||
const templates = []
|
||||
|
||||
zoomOptions.forEach((zoom) => {
|
||||
menu.append(new MenuItem({
|
||||
templates.push({
|
||||
label: Math.floor(zoom * 100) + '%',
|
||||
click: () => this.handleZoomMenuItemClick(zoom)
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
menu.popup(remote.getCurrentWindow())
|
||||
context.popup(templates)
|
||||
}
|
||||
|
||||
handleZoomMenuItemClick (zoomFactor) {
|
||||
|
||||
@@ -156,8 +156,7 @@ class TopBar extends React.Component {
|
||||
if (this.state.isSearching) {
|
||||
el.blur()
|
||||
} else {
|
||||
el.focus()
|
||||
el.setSelectionRange(0, el.value.length)
|
||||
el.select()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@ body
|
||||
font-weight 200
|
||||
-webkit-font-smoothing antialiased
|
||||
|
||||
::-webkit-scrollbar
|
||||
width 12px
|
||||
|
||||
::-webkit-scrollbar-thumb
|
||||
background-color rgba(0, 0, 0, 0.15)
|
||||
|
||||
button, input, select, textarea
|
||||
font-family DEFAULT_FONTS
|
||||
|
||||
@@ -85,9 +91,11 @@ modalBackColor = white
|
||||
absolute top left bottom right
|
||||
background-color modalBackColor
|
||||
z-index modalZIndex + 1
|
||||
|
||||
|
||||
|
||||
body[data-theme="dark"]
|
||||
::-webkit-scrollbar-thumb
|
||||
background-color rgba(0, 0, 0, 0.3)
|
||||
.ModalBase
|
||||
.modalBack
|
||||
background-color $ui-dark-backgroundColor
|
||||
@@ -128,6 +136,8 @@ body[data-theme="dark"]
|
||||
z-index modalZIndex + 5
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
::-webkit-scrollbar-thumb
|
||||
background-color rgba(0, 0, 0, 0.3)
|
||||
.ModalBase
|
||||
.modalBack
|
||||
background-color $ui-solarized-dark-backgroundColor
|
||||
@@ -135,9 +145,10 @@ body[data-theme="solarized-dark"]
|
||||
color: $ui-solarized-dark-text-color
|
||||
|
||||
body[data-theme="monokai"]
|
||||
::-webkit-scrollbar-thumb
|
||||
background-color rgba(0, 0, 0, 0.3)
|
||||
.ModalBase
|
||||
.modalBack
|
||||
background-color $ui-monokai-backgroundColor
|
||||
.sortableItemHelper
|
||||
color: $ui-monokai-text-color
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ function addStorage (input) {
|
||||
key,
|
||||
name: input.name,
|
||||
type: input.type,
|
||||
path: input.path
|
||||
path: input.path,
|
||||
isOpen: false
|
||||
}
|
||||
|
||||
return Promise.resolve(newStorage)
|
||||
@@ -48,7 +49,8 @@ function addStorage (input) {
|
||||
key: newStorage.key,
|
||||
type: newStorage.type,
|
||||
name: newStorage.name,
|
||||
path: newStorage.path
|
||||
path: newStorage.path,
|
||||
isOpen: false
|
||||
})
|
||||
|
||||
localStorage.setItem('storages', JSON.stringify(rawStorages))
|
||||
|
||||
@@ -10,6 +10,7 @@ import i18n from 'browser/lib/i18n'
|
||||
|
||||
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
|
||||
const DESTINATION_FOLDER = 'attachments'
|
||||
const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp(path.win32.sep)
|
||||
|
||||
/**
|
||||
* @description
|
||||
@@ -76,14 +77,14 @@ function createAttachmentDestinationFolder (destinationStoragePath, noteKey) {
|
||||
|
||||
/**
|
||||
* @description Moves attachments from the old location ('/images') to the new one ('/attachments/noteKey)
|
||||
* @param renderedHTML HTML of the current note
|
||||
* @param markdownContent of the current note
|
||||
* @param storagePath Storage path of the current note
|
||||
* @param noteKey Key of the current note
|
||||
*/
|
||||
function migrateAttachments (renderedHTML, storagePath, noteKey) {
|
||||
if (sander.existsSync(path.join(storagePath, 'images'))) {
|
||||
const attachments = getAttachmentsInContent(renderedHTML) || []
|
||||
if (attachments !== []) {
|
||||
function migrateAttachments (markdownContent, storagePath, noteKey) {
|
||||
if (noteKey !== undefined && sander.existsSync(path.join(storagePath, 'images'))) {
|
||||
const attachments = getAttachmentsInMarkdownContent(markdownContent) || []
|
||||
if (attachments.length) {
|
||||
createAttachmentDestinationFolder(storagePath, noteKey)
|
||||
}
|
||||
for (const attachment of attachments) {
|
||||
@@ -106,7 +107,10 @@ function migrateAttachments (renderedHTML, storagePath, noteKey) {
|
||||
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
|
||||
*/
|
||||
function fixLocalURLS (renderedHTML, storagePath) {
|
||||
return renderedHTML.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
|
||||
return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '.*?"', 'g'), function (match) {
|
||||
var encodedPathSeparators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g')
|
||||
return match.replace(encodedPathSeparators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,13 +190,13 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns all attachment paths of the given markdown
|
||||
* @param {String} markdownContent content in which the attachment paths should be found
|
||||
* @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown
|
||||
*/
|
||||
function getAttachmentsInContent (markdownContent) {
|
||||
const preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep)
|
||||
const regexp = new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + '|/)' + '?([a-zA-Z0-9]|-)*' + '(' + escapeStringRegexp(path.sep) + '|/)' + '([a-zA-Z0-9]|\\.)+(\\.[a-zA-Z0-9]+)?', 'g')
|
||||
* @description Returns all attachment paths of the given markdown
|
||||
* @param {String} markdownContent content in which the attachment paths should be found
|
||||
* @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown
|
||||
*/
|
||||
function getAttachmentsInMarkdownContent (markdownContent) {
|
||||
const preparedInput = markdownContent.replace(new RegExp('[' + PATH_SEPARATORS + ']', 'g'), path.sep)
|
||||
const regexp = new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + ')' + '?([a-zA-Z0-9]|-)*' + '(' + escapeStringRegexp(path.sep) + ')' + '([a-zA-Z0-9]|\\.)+(\\.[a-zA-Z0-9]+)?', 'g')
|
||||
return preparedInput.match(regexp)
|
||||
}
|
||||
|
||||
@@ -203,7 +207,7 @@ function getAttachmentsInContent (markdownContent) {
|
||||
* @returns {String[]} Absolute paths of the referenced attachments
|
||||
*/
|
||||
function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
|
||||
const temp = getAttachmentsInContent(markdownContent) || []
|
||||
const temp = getAttachmentsInMarkdownContent(markdownContent) || []
|
||||
const result = []
|
||||
for (const relativePath of temp) {
|
||||
result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER)))
|
||||
@@ -239,7 +243,8 @@ function moveAttachments (oldPath, newPath, noteKey, newNoteKey, noteContent) {
|
||||
*/
|
||||
function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) {
|
||||
if (noteContent) {
|
||||
return noteContent.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
|
||||
const preparedInput = noteContent.replace(new RegExp('[' + PATH_SEPARATORS + ']', 'g'), path.sep)
|
||||
return preparedInput.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
|
||||
}
|
||||
return noteContent
|
||||
}
|
||||
@@ -277,7 +282,7 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
|
||||
}
|
||||
const targetStorage = findStorage.findStorage(storageKey)
|
||||
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
||||
const attachmentsInNote = getAttachmentsInContent(markdownContent)
|
||||
const attachmentsInNote = getAttachmentsInMarkdownContent(markdownContent)
|
||||
const attachmentsInNoteOnlyFileNames = []
|
||||
if (attachmentsInNote) {
|
||||
for (let i = 0; i < attachmentsInNote.length; i++) {
|
||||
@@ -348,7 +353,7 @@ function generateFileNotFoundMarkdown () {
|
||||
*/
|
||||
function isAttachmentLink (text) {
|
||||
if (text) {
|
||||
return text.match(new RegExp('.*\\[.*\\]\\( *' + escapeStringRegexp(STORAGE_FOLDER_PLACEHOLDER) + escapeStringRegexp(path.sep) + '.*\\).*', 'gi')) != null
|
||||
return text.match(new RegExp('.*\\[.*\\]\\( *' + escapeStringRegexp(STORAGE_FOLDER_PLACEHOLDER) + '[' + PATH_SEPARATORS + ']' + '.*\\).*', 'gi')) != null
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -364,7 +369,7 @@ function isAttachmentLink (text) {
|
||||
function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
|
||||
if (storageKey != null && noteKey != null && linkText != null) {
|
||||
const storagePath = findStorage.findStorage(storageKey).path
|
||||
const attachments = getAttachmentsInContent(linkText) || []
|
||||
const attachments = getAttachmentsInMarkdownContent(linkText) || []
|
||||
const replaceInstructions = []
|
||||
const copies = []
|
||||
for (const attachment of attachments) {
|
||||
@@ -373,13 +378,13 @@ function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
|
||||
sander.exists(absPathOfAttachment)
|
||||
.then((fileExists) => {
|
||||
if (!fileExists) {
|
||||
const fileNotFoundRegexp = new RegExp('!?' + escapeStringRegexp('[') + '[\\w|\\d|\\s|\\.]*\\]\\(\\s*' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + escapeStringRegexp(path.sep) + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + escapeStringRegexp(')'))
|
||||
const fileNotFoundRegexp = new RegExp('!?' + escapeStringRegexp('[') + '[\\w|\\d|\\s|\\.]*\\]\\(\\s*' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + PATH_SEPARATORS + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + escapeStringRegexp(')'))
|
||||
replaceInstructions.push({regexp: fileNotFoundRegexp, replacement: this.generateFileNotFoundMarkdown()})
|
||||
return Promise.resolve()
|
||||
}
|
||||
return this.copyAttachment(absPathOfAttachment, storageKey, noteKey)
|
||||
.then((fileName) => {
|
||||
const replaceLinkRegExp = new RegExp(escapeStringRegexp('(') + ' *' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + escapeStringRegexp(path.sep) + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + ' *' + escapeStringRegexp(')'))
|
||||
const replaceLinkRegExp = new RegExp(escapeStringRegexp('(') + ' *' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + PATH_SEPARATORS + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + ' *' + escapeStringRegexp(')'))
|
||||
replaceInstructions.push({
|
||||
regexp: replaceLinkRegExp,
|
||||
replacement: '(' + path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName) + ')'
|
||||
@@ -408,7 +413,7 @@ module.exports = {
|
||||
generateAttachmentMarkdown,
|
||||
handleAttachmentDrop,
|
||||
handlePastImageEvent,
|
||||
getAttachmentsInContent,
|
||||
getAttachmentsInMarkdownContent,
|
||||
getAbsolutePathsOfAttachmentsInContent,
|
||||
removeStorageAndNoteReferences,
|
||||
deleteAttachmentFolder,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const dataApi = {
|
||||
init: require('./init'),
|
||||
toggleStorage: require('./toggleStorage'),
|
||||
addStorage: require('./addStorage'),
|
||||
renameStorage: require('./renameStorage'),
|
||||
removeStorage: require('./removeStorage'),
|
||||
|
||||
@@ -8,7 +8,8 @@ function resolveStorageData (storageCache) {
|
||||
key: storageCache.key,
|
||||
name: storageCache.name,
|
||||
type: storageCache.type,
|
||||
path: storageCache.path
|
||||
path: storageCache.path,
|
||||
isOpen: storageCache.isOpen
|
||||
}
|
||||
|
||||
const boostnoteJSONPath = path.join(storageCache.path, 'boostnote.json')
|
||||
|
||||
28
browser/main/lib/dataApi/toggleStorage.js
Normal file
28
browser/main/lib/dataApi/toggleStorage.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const _ = require('lodash')
|
||||
const resolveStorageData = require('./resolveStorageData')
|
||||
|
||||
/**
|
||||
* @param {String} key
|
||||
* @param {Boolean} isOpen
|
||||
* @return {Object} Storage meta data
|
||||
*/
|
||||
function toggleStorage (key, isOpen) {
|
||||
let cachedStorageList
|
||||
try {
|
||||
cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(cachedStorageList)) throw new Error('invalid storages')
|
||||
} catch (err) {
|
||||
console.log('error got')
|
||||
console.error(err)
|
||||
return Promise.reject(err)
|
||||
}
|
||||
const targetStorage = _.find(cachedStorageList, {key: key})
|
||||
if (targetStorage == null) return Promise.reject('Storage')
|
||||
|
||||
targetStorage.isOpen = isOpen
|
||||
localStorage.setItem('storages', JSON.stringify(cachedStorageList))
|
||||
|
||||
return resolveStorageData(targetStorage)
|
||||
}
|
||||
|
||||
module.exports = toggleStorage
|
||||
@@ -12,8 +12,7 @@ class NewNoteModal extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
}
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
@@ -35,19 +34,20 @@ class NewNoteModal extends React.Component {
|
||||
title: '',
|
||||
content: ''
|
||||
})
|
||||
.then((note) => {
|
||||
.then(note => {
|
||||
const noteHash = note.key
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: {key: noteHash}
|
||||
query: { key: noteHash }
|
||||
})
|
||||
ee.emit('list:jump', noteHash)
|
||||
ee.emit('detail:focus')
|
||||
this.props.close()
|
||||
setTimeout(this.props.close, 200)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -69,13 +69,15 @@ class NewNoteModal extends React.Component {
|
||||
folder: folder,
|
||||
title: '',
|
||||
description: '',
|
||||
snippets: [{
|
||||
name: '',
|
||||
mode: 'text',
|
||||
content: ''
|
||||
}]
|
||||
snippets: [
|
||||
{
|
||||
name: '',
|
||||
mode: 'text',
|
||||
content: ''
|
||||
}
|
||||
]
|
||||
})
|
||||
.then((note) => {
|
||||
.then(note => {
|
||||
const noteHash = note.key
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
@@ -83,11 +85,11 @@ class NewNoteModal extends React.Component {
|
||||
})
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: {key: noteHash}
|
||||
query: { key: noteHash }
|
||||
})
|
||||
ee.emit('list:jump', noteHash)
|
||||
ee.emit('detail:focus')
|
||||
this.props.close()
|
||||
setTimeout(this.props.close, 200)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -106,49 +108,65 @@ class NewNoteModal extends React.Component {
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div styleName='root'
|
||||
<div
|
||||
styleName='root'
|
||||
tabIndex='-1'
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onKeyDown={e => this.handleKeyDown(e)}
|
||||
>
|
||||
<div styleName='header'>
|
||||
<div styleName='title'>{i18n.__('Make a note')}</div>
|
||||
</div>
|
||||
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
|
||||
<ModalEscButton
|
||||
handleEscButtonClick={e => this.handleCloseButtonClick(e)}
|
||||
/>
|
||||
<div styleName='control'>
|
||||
<button styleName='control-button'
|
||||
onClick={(e) => this.handleMarkdownNoteButtonClick(e)}
|
||||
onKeyDown={(e) => this.handleMarkdownNoteButtonKeyDown(e)}
|
||||
<button
|
||||
styleName='control-button'
|
||||
onClick={e => this.handleMarkdownNoteButtonClick(e)}
|
||||
onKeyDown={e => this.handleMarkdownNoteButtonKeyDown(e)}
|
||||
ref='markdownButton'
|
||||
>
|
||||
<i styleName='control-button-icon'
|
||||
className='fa fa-file-text-o'
|
||||
/><br />
|
||||
<span styleName='control-button-label'>{i18n.__('Markdown Note')}</span><br />
|
||||
<span styleName='control-button-description'>{i18n.__('This format is for creating text documents. Checklists, code blocks and Latex blocks are available.')}</span>
|
||||
<i styleName='control-button-icon' className='fa fa-file-text-o' />
|
||||
<br />
|
||||
<span styleName='control-button-label'>
|
||||
{i18n.__('Markdown Note')}
|
||||
</span>
|
||||
<br />
|
||||
<span styleName='control-button-description'>
|
||||
{i18n.__(
|
||||
'This format is for creating text documents. Checklists, code blocks and Latex blocks are available.'
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button styleName='control-button'
|
||||
onClick={(e) => this.handleSnippetNoteButtonClick(e)}
|
||||
onKeyDown={(e) => this.handleSnippetNoteButtonKeyDown(e)}
|
||||
<button
|
||||
styleName='control-button'
|
||||
onClick={e => this.handleSnippetNoteButtonClick(e)}
|
||||
onKeyDown={e => this.handleSnippetNoteButtonKeyDown(e)}
|
||||
ref='snippetButton'
|
||||
>
|
||||
<i styleName='control-button-icon'
|
||||
className='fa fa-code'
|
||||
/><br />
|
||||
<span styleName='control-button-label'>{i18n.__('Snippet Note')}</span><br />
|
||||
<span styleName='control-button-description'>{i18n.__('This format is for creating code snippets. Multiple snippets can be grouped into a single note.')}
|
||||
<i styleName='control-button-icon' className='fa fa-code' /><br />
|
||||
<span styleName='control-button-label'>
|
||||
{i18n.__('Snippet Note')}
|
||||
</span>
|
||||
<br />
|
||||
<span styleName='control-button-description'>
|
||||
{i18n.__(
|
||||
'This format is for creating code snippets. Multiple snippets can be grouped into a single note.'
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div styleName='description'><i className='fa fa-arrows-h' />{i18n.__('Tab to switch format')}</div>
|
||||
<div styleName='description'>
|
||||
<i className='fa fa-arrows-h' />{i18n.__('Tab to switch format')}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
NewNoteModal.propTypes = {
|
||||
}
|
||||
NewNoteModal.propTypes = {}
|
||||
|
||||
export default CSSModules(NewNoteModal, styles)
|
||||
|
||||
@@ -27,7 +27,12 @@ class SnippetEditor extends React.Component {
|
||||
dragDrop: false,
|
||||
foldGutter: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
autoCloseBrackets: true,
|
||||
autoCloseBrackets: {
|
||||
pairs: '()[]{}\'\'""$$**``',
|
||||
triples: '```"""\'\'\'',
|
||||
explode: '[]{}``$$',
|
||||
override: true
|
||||
},
|
||||
mode: 'null'
|
||||
})
|
||||
this.cm.setSize('100%', '100%')
|
||||
|
||||
@@ -4,8 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
const { remote } = require('electron')
|
||||
const { Menu, MenuItem } = remote
|
||||
import context from 'browser/lib/context'
|
||||
|
||||
class SnippetList extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -21,18 +20,17 @@ class SnippetList extends React.Component {
|
||||
}
|
||||
|
||||
reloadSnippetList () {
|
||||
dataApi.fetchSnippet().then(snippets => this.setState({snippets}))
|
||||
dataApi.fetchSnippet().then(snippets => {
|
||||
this.setState({snippets})
|
||||
this.props.onSnippetSelect(snippets[0])
|
||||
})
|
||||
}
|
||||
|
||||
handleSnippetContextMenu (snippet) {
|
||||
const menu = new Menu()
|
||||
menu.append(new MenuItem({
|
||||
context.popup([{
|
||||
label: i18n.__('Delete snippet'),
|
||||
click: () => {
|
||||
this.deleteSnippet(snippet)
|
||||
}
|
||||
}))
|
||||
menu.popup()
|
||||
click: () => this.deleteSnippet(snippet)
|
||||
}])
|
||||
}
|
||||
|
||||
deleteSnippet (snippet) {
|
||||
@@ -43,7 +41,7 @@ class SnippetList extends React.Component {
|
||||
}
|
||||
|
||||
handleSnippetClick (snippet) {
|
||||
this.props.onSnippetClick(snippet)
|
||||
this.props.onSnippetSelect(snippet)
|
||||
}
|
||||
|
||||
createSnippet () {
|
||||
@@ -55,6 +53,16 @@ class SnippetList extends React.Component {
|
||||
}).catch(err => { throw err })
|
||||
}
|
||||
|
||||
defineSnippetStyleName (snippet) {
|
||||
const { currentSnippet } = this.props
|
||||
if (currentSnippet == null) return
|
||||
if (currentSnippet.id === snippet.id) {
|
||||
return 'snippet-item-selected'
|
||||
} else {
|
||||
return 'snippet-item'
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { snippets } = this.state
|
||||
return (
|
||||
@@ -70,7 +78,7 @@ class SnippetList extends React.Component {
|
||||
{
|
||||
snippets.map((snippet) => (
|
||||
<li
|
||||
styleName='snippet-item'
|
||||
styleName={this.defineSnippetStyleName(snippet)}
|
||||
key={snippet.id}
|
||||
onContextMenu={() => this.handleSnippetContextMenu(snippet)}
|
||||
onClick={() => this.handleSnippetClick(snippet)}>
|
||||
|
||||
@@ -25,7 +25,7 @@ class SnippetTab extends React.Component {
|
||||
}, 500)
|
||||
}
|
||||
|
||||
handleSnippetClick (snippet) {
|
||||
handleSnippetSelect (snippet) {
|
||||
const { currentSnippet } = this.state
|
||||
if (currentSnippet === null || currentSnippet.id !== snippet.id) {
|
||||
dataApi.fetchSnippet(snippet.id).then(changedSnippet => {
|
||||
@@ -66,8 +66,9 @@ class SnippetTab extends React.Component {
|
||||
<div styleName='root'>
|
||||
<div styleName='header'>{i18n.__('Snippets')}</div>
|
||||
<SnippetList
|
||||
onSnippetClick={this.handleSnippetClick.bind(this)}
|
||||
onSnippetDeleted={this.handleDeleteSnippet.bind(this)} />
|
||||
onSnippetSelect={this.handleSnippetSelect.bind(this)}
|
||||
onSnippetDeleted={this.handleDeleteSnippet.bind(this)}
|
||||
currentSnippet={currentSnippet} />
|
||||
<div styleName='snippet-detail' style={{visibility: currentSnippet ? 'visible' : 'hidden'}}>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Snippet name')}</div>
|
||||
|
||||
@@ -122,6 +122,10 @@
|
||||
&:hover
|
||||
background darken(#f5f5f5, 5)
|
||||
|
||||
.snippet-item-selected
|
||||
@extend .snippet-list .snippet-item
|
||||
background darken(#f5f5f5, 5)
|
||||
|
||||
.snippet-detail
|
||||
width 70%
|
||||
height calc(100% - 200px)
|
||||
@@ -142,6 +146,8 @@ body[data-theme="default"], body[data-theme="white"]
|
||||
background $ui-borderColor
|
||||
&:hover
|
||||
background darken($ui-backgroundColor, 5)
|
||||
.snippet-item-selected
|
||||
background darken($ui-backgroundColor, 5)
|
||||
|
||||
body[data-theme="dark"]
|
||||
.snippets
|
||||
@@ -152,8 +158,12 @@ body[data-theme="dark"]
|
||||
background $ui-dark-borderColor
|
||||
&:hover
|
||||
background darken($ui-dark-backgroundColor, 5)
|
||||
.snippet-item-selected
|
||||
background darken($ui-dark-backgroundColor, 5)
|
||||
.snippet-detail
|
||||
color white
|
||||
.group-control-button
|
||||
colorDarkPrimaryButton()
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.snippets
|
||||
@@ -164,8 +174,12 @@ body[data-theme="solarized-dark"]
|
||||
background $ui-solarized-dark-borderColor
|
||||
&:hover
|
||||
background darken($ui-solarized-dark-backgroundColor, 5)
|
||||
.snippet-item-selected
|
||||
background darken($ui-solarized-dark-backgroundColor, 5)
|
||||
.snippet-detail
|
||||
color white
|
||||
.group-control-button
|
||||
colorSolarizedDarkPrimaryButton()
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.snippets
|
||||
@@ -176,5 +190,9 @@ body[data-theme="monokai"]
|
||||
background $ui-monokai-borderColor
|
||||
&:hover
|
||||
background darken($ui-monokai-backgroundColor, 5)
|
||||
.snippet-item-selected
|
||||
background darken($ui-monokai-backgroundColor, 5)
|
||||
.snippet-detail
|
||||
color white
|
||||
.group-control-button
|
||||
colorMonokaiPrimaryButton()
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'codemirror-mode-elixir'
|
||||
import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import { getLanguages } from 'browser/lib/Languages'
|
||||
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
|
||||
@@ -164,7 +165,7 @@ class UiTab extends React.Component {
|
||||
const { config, codemirrorTheme } = this.state
|
||||
const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};'
|
||||
const enableEditRulersStyle = config.editor.enableRulers ? 'block' : 'none'
|
||||
const customCSS = config.preview.customCSS
|
||||
const fontFamily = normalizeEditorFontFamily(config.editor.fontFamily)
|
||||
return (
|
||||
<div styleName='root'>
|
||||
<div styleName='group'>
|
||||
@@ -262,8 +263,16 @@ class UiTab extends React.Component {
|
||||
})
|
||||
}
|
||||
</select>
|
||||
<div styleName='code-mirror'>
|
||||
<ReactCodeMirror ref={e => (this.codeMirrorInstance = e)} value={codemirrorSampleCode} options={{ lineNumbers: true, readOnly: true, mode: 'javascript', theme: codemirrorTheme }} />
|
||||
<div styleName='code-mirror' style={{fontFamily}}>
|
||||
<ReactCodeMirror
|
||||
ref={e => (this.codeMirrorInstance = e)}
|
||||
value={codemirrorSampleCode}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
readOnly: true,
|
||||
mode: 'javascript',
|
||||
theme: codemirrorTheme
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -596,7 +605,19 @@ class UiTab extends React.Component {
|
||||
type='checkbox'
|
||||
/>
|
||||
{i18n.__('Allow custom CSS for preview')}
|
||||
<ReactCodeMirror onChange={e => this.handleUIChange(e)} ref={e => (this.customCSSCM = e)} value={config.preview.customCSS} options={{ lineNumbers: true, mode: 'css', theme: codemirrorTheme }} />
|
||||
<div style={{fontFamily}}>
|
||||
<ReactCodeMirror
|
||||
width='400px'
|
||||
height='400px'
|
||||
onChange={e => this.handleUIChange(e)}
|
||||
ref={e => (this.customCSSCM = e)}
|
||||
value={config.preview.customCSS}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
mode: 'css',
|
||||
theme: codemirrorTheme
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -360,6 +360,12 @@ function data (state = defaultDataMap(), action) {
|
||||
state.storageMap = new Map(state.storageMap)
|
||||
state.storageMap.set(action.storage.key, action.storage)
|
||||
return state
|
||||
case 'EXPAND_STORAGE':
|
||||
state = Object.assign({}, state)
|
||||
state.storageMap = new Map(state.storageMap)
|
||||
action.storage.isOpen = action.isOpen
|
||||
state.storageMap.set(action.storage.key, action.storage)
|
||||
return state
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user