1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-24 23:23:09 +00:00

Merge branch 'master' into tags

This commit is contained in:
Baptiste Augrain
2018-09-15 10:05:38 +02:00
38 changed files with 1890 additions and 349 deletions

View File

@@ -604,7 +604,10 @@ export default class CodeEditor extends React.Component {
body,
'text/html'
)
const linkWithTitle = `[${parsedBody.title}](${pastedTxt})`
const escapePipe = (str) => {
return str.replace('|', '\\|')
}
const linkWithTitle = `[${escapePipe(parsedBody.title)}](${pastedTxt})`
resolve(linkWithTitle)
} catch (e) {
reject(e)

View File

@@ -3,6 +3,17 @@ export function findNoteTitle (value) {
let title = null
let isInsideCodeBlock = false
if (splitted[0] === '---') {
let line = 0
while (++line < splitted.length) {
if (splitted[line] === '---') {
splitted.splice(0, line + 1)
break
}
}
}
splitted.some((line, index) => {
const trimmedLine = line.trim()
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()

View File

@@ -0,0 +1,24 @@
'use strict'
module.exports = function frontMatterPlugin (md) {
function frontmatter (state, startLine, endLine, silent) {
if (startLine !== 0 || state.src.substr(startLine, state.eMarks[0]) !== '---') {
return false
}
let line = 0
while (++line < state.lineMax) {
if (state.src.substring(state.bMarks[line], state.eMarks[line]) === '---') {
state.line = line + 1
return true
}
}
return false
}
md.block.ruler.before('table', 'frontmatter', frontmatter, {
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
})
}

View File

@@ -0,0 +1,100 @@
/**
* @fileoverview Markdown table of contents generator
*/
import toc from 'markdown-toc'
import diacritics from 'diacritics-map'
import stripColor from 'strip-color'
const EOL = require('os').EOL
/**
* @caseSensitiveSlugify Custom slugify function
* Same implementation that the original used by markdown-toc (node_modules/markdown-toc/lib/utils.js),
* but keeps original case to properly handle https://github.com/BoostIO/Boostnote/issues/2067
*/
function caseSensitiveSlugify (str) {
function replaceDiacritics (str) {
return str.replace(/[À-ž]/g, function (ch) {
return diacritics[ch] || ch
})
}
function getTitle (str) {
if (/^\[[^\]]+\]\(/.test(str)) {
var m = /^\[([^\]]+)\]/.exec(str)
if (m) return m[1]
}
return str
}
str = getTitle(str)
str = stripColor(str)
// str = str.toLowerCase() //let's be case sensitive
// `.split()` is often (but not always) faster than `.replace()`
str = str.split(' ').join('-')
str = str.split(/\t/).join('--')
str = str.split(/<\/?[^>]+>/).join('')
str = str.split(/[|$&`~=\\\/@+*!?({[\]})<>=.,;:'"^]/).join('')
str = str.split(/[。?!,、;:“”【】()〔〕[]﹃﹄“ ”‘’﹁﹂—…-~《》〈〉「」]/).join('')
str = replaceDiacritics(str)
return str
}
const TOC_MARKER_START = '<!-- toc -->'
const TOC_MARKER_END = '<!-- tocstop -->'
/**
* Takes care of proper updating given editor with TOC.
* If TOC doesn't exit in the editor, it's inserted at current caret position.
* Otherwise,TOC is updated in place.
* @param editor CodeMirror editor to be updated with TOC
*/
export function generateInEditor (editor) {
const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
function tocExistsInEditor () {
return tocRegex.test(editor.getValue())
}
function updateExistingToc () {
const toc = generate(editor.getValue())
const search = editor.getSearchCursor(tocRegex)
while (search.findNext()) {
search.replace(toc)
}
}
function addTocAtCursorPosition () {
const toc = generate(editor.getRange(editor.getCursor(), {line: Infinity}))
editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor())
}
if (tocExistsInEditor()) {
updateExistingToc()
} else {
addTocAtCursorPosition()
}
}
/**
* Generates MD TOC based on MD document passed as string.
* @param markdownText MD document
* @returns generatedTOC String containing generated TOC
*/
export function generate (markdownText) {
const generatedToc = toc(markdownText, {slugify: caseSensitiveSlugify})
return TOC_MARKER_START + EOL + EOL + generatedToc.content + EOL + EOL + TOC_MARKER_END
}
function wrapTocWithEol (toc, editor) {
const leftWrap = editor.getCursor().ch === 0 ? '' : EOL
const rightWrap = editor.getLine(editor.getCursor().line).length === editor.getCursor().ch ? '' : EOL
return leftWrap + toc + rightWrap
}
export default {
generate,
generateInEditor
}

View File

@@ -149,6 +149,7 @@ class Markdown {
})
this.md.use(require('markdown-it-kbd'))
this.md.use(require('markdown-it-admonition'))
this.md.use(require('./markdown-it-frontmatter'))
const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), '', {

62
browser/lib/newNote.js Normal file
View File

@@ -0,0 +1,62 @@
import { hashHistory } from 'react-router'
import dataApi from 'browser/main/lib/dataApi'
import ee from 'browser/main/lib/eventEmitter'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
export function createMarkdownNote (storage, folder, dispatch, location) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
return dataApi
.createNote(storage, {
type: 'MARKDOWN_NOTE',
folder: folder,
title: '',
content: ''
})
.then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: { key: noteHash }
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
})
}
export function createSnippetNote (storage, folder, dispatch, location, config) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
return dataApi
.createNote(storage, {
type: 'SNIPPET_NOTE',
folder: folder,
title: '',
description: '',
snippets: [
{
name: '',
mode: config.editor.snippetDefaultLanguage || 'text',
content: ''
}
]
})
.then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: { key: noteHash }
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
})
}

View File

@@ -29,6 +29,7 @@ import { formatDate } from 'browser/lib/date-formatter'
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
import striptags from 'striptags'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import markdownToc from 'browser/lib/markdown-toc-generator'
class MarkdownNoteDetail extends React.Component {
constructor (props) {
@@ -47,6 +48,7 @@ class MarkdownNoteDetail extends React.Component {
this.dispatchTimer = null
this.toggleLockButton = this.handleToggleLockButton.bind(this)
this.generateToc = () => this.handleGenerateToc()
}
focus () {
@@ -59,6 +61,7 @@ class MarkdownNoteDetail extends React.Component {
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
this.handleSwitchMode(reversedType)
})
ee.on('code:generate-toc', this.generateToc)
}
componentWillReceiveProps (nextProps) {
@@ -75,6 +78,7 @@ class MarkdownNoteDetail extends React.Component {
componentWillUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
ee.off('code:generate-toc', this.generateToc)
if (this.saveQueue != null) this.saveNow()
}
@@ -262,6 +266,11 @@ class MarkdownNoteDetail extends React.Component {
}
}
handleGenerateToc () {
const editor = this.refs.content.refs.code.editor
markdownToc.generateInEditor(editor)
}
handleFocus (e) {
this.focus()
}
@@ -365,6 +374,7 @@ class MarkdownNoteDetail extends React.Component {
value={this.state.note.tags}
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
showTagsAlphabetically={config.ui.showTagsAlphabetically}
data={data}
onChange={this.handleUpdateTag.bind(this)}
/>
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />

View File

@@ -13,6 +13,7 @@ $info-margin-under-border = 30px
display flex
align-items center
padding 0 20px
z-index 99
.info-left
padding 0 10px

View File

@@ -29,6 +29,7 @@ import InfoPanelTrashed from './InfoPanelTrashed'
import { formatDate } from 'browser/lib/date-formatter'
import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import markdownToc from 'browser/lib/markdown-toc-generator'
const electron = require('electron')
const { remote } = electron
@@ -52,6 +53,7 @@ class SnippetNoteDetail extends React.Component {
}
this.scrollToNextTabThreshold = 0.7
this.generateToc = () => this.handleGenerateToc()
}
componentDidMount () {
@@ -65,6 +67,7 @@ class SnippetNoteDetail extends React.Component {
enableLeftArrow: allTabs.offsetLeft !== 0
})
}
ee.on('code:generate-toc', this.generateToc)
}
componentWillReceiveProps (nextProps) {
@@ -91,6 +94,16 @@ class SnippetNoteDetail extends React.Component {
componentWillUnmount () {
if (this.saveQueue != null) this.saveNow()
ee.off('code:generate-toc', this.generateToc)
}
handleGenerateToc () {
const { note, snippetIndex } = this.state
const currentMode = note.snippets[snippetIndex].mode
if (currentMode.includes('Markdown')) {
const currentEditor = this.refs[`code-${snippetIndex}`].refs.code.editor
markdownToc.generateInEditor(currentEditor)
}
}
handleChange (e) {
@@ -441,7 +454,7 @@ class SnippetNoteDetail extends React.Component {
const isSuper = global.process.platform === 'darwin'
? e.metaKey
: e.ctrlKey
if (isSuper && !e.shiftKey) {
if (isSuper && !e.shiftKey && !e.altKey) {
e.preventDefault()
this.addSnippet()
}

View File

@@ -6,71 +6,33 @@ import _ from 'lodash'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n'
import ee from 'browser/main/lib/eventEmitter'
import Autosuggest from 'react-autosuggest'
class TagSelect extends React.Component {
constructor (props) {
super(props)
this.state = {
newTag: ''
newTag: '',
suggestions: []
}
this.addtagHandler = this.handleAddTag.bind(this)
this.handleAddTag = this.handleAddTag.bind(this)
this.onInputBlur = this.onInputBlur.bind(this)
this.onInputChange = this.onInputChange.bind(this)
this.onInputKeyDown = this.onInputKeyDown.bind(this)
this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this)
this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this)
this.onSuggestionSelected = this.onSuggestionSelected.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:
e.preventDefault()
this.submitTag()
break
case 13:
this.submitTag()
break
case 8:
if (this.refs.newTag.value.length === 0) {
this.removeLastTag()
}
}
}
handleNewTagBlur (e) {
this.submitTag()
}
removeLastTag () {
this.removeTagByCallback((value) => {
value.pop()
})
}
reset () {
this.setState({
newTag: ''
})
}
submitTag () {
addNewTag (newTag) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
let { value } = this.props
let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
newTag = newTag.charAt(0) === '#' ? newTag.substring(1) : newTag
newTag = newTag.trim().replace(/ +/g, '_')
if (newTag.charAt(0) === '#') {
newTag.substring(1)
}
if (newTag.length <= 0) {
this.setState({
@@ -79,17 +41,12 @@ class TagSelect extends React.Component {
return
}
let { value } = this.props
value = _.isArray(value)
? value.slice()
: []
if (!_.includes(value, newTag)) {
value.push(newTag)
}
if (this.props.saveTagsAlphabetically) {
value = _.sortBy(value)
}
value.push(newTag)
value = _.uniq(value)
this.setState({
newTag: ''
@@ -99,10 +56,36 @@ class TagSelect extends React.Component {
})
}
handleNewTagInputChange (e) {
this.setState({
newTag: this.refs.newTag.value
})
buildSuggestions () {
this.suggestions = _.sortBy(this.props.data.tagNoteMap.map(
(tag, name) => ({
name,
nameLC: name.toLowerCase(),
size: tag.size
})
).filter(
tag => tag.size > 0
), ['name'])
}
componentDidMount () {
this.value = this.props.value
this.buildSuggestions()
ee.on('editor:add-tag', this.handleAddTag)
}
componentDidUpdate () {
this.value = this.props.value
}
componentWillUnmount () {
ee.off('editor:add-tag', this.handleAddTag)
}
handleAddTag () {
this.refs.newTag.input.focus()
}
handleTagRemoveButtonClick (tag) {
@@ -111,6 +94,60 @@ class TagSelect extends React.Component {
}, tag)
}
onInputBlur (e) {
this.submitNewTag()
}
onInputChange (e, { newValue, method }) {
this.setState({
newTag: newValue
})
}
onInputKeyDown (e) {
switch (e.keyCode) {
case 9:
e.preventDefault()
this.submitNewTag()
break
case 13:
this.submitNewTag()
break
case 8:
if (this.state.newTag.length === 0) {
this.removeLastTag()
}
}
}
onSuggestionsClearRequested () {
this.setState({
suggestions: []
})
}
onSuggestionsFetchRequested ({ value }) {
const valueLC = value.toLowerCase()
const suggestions = _.filter(
this.suggestions,
tag => !_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1
)
this.setState({
suggestions
})
}
onSuggestionSelected (event, { suggestion, suggestionValue }) {
this.addNewTag(suggestionValue)
}
removeLastTag () {
this.removeTagByCallback((value) => {
value.pop()
})
}
removeTagByCallback (callback, tag = null) {
let { value } = this.props
@@ -124,6 +161,18 @@ class TagSelect extends React.Component {
this.props.onChange()
}
reset () {
this.buildSuggestions()
this.setState({
newTag: ''
})
}
submitNewTag () {
this.addNewTag(this.refs.newTag.input.value)
}
render () {
const { value, className, showTagsAlphabetically } = this.props
@@ -144,6 +193,8 @@ class TagSelect extends React.Component {
})
: []
const { newTag, suggestions } = this.state
return (
<div className={_.isString(className)
? 'TagSelect ' + className
@@ -152,13 +203,25 @@ class TagSelect extends React.Component {
styleName='root'
>
{tagList}
<input styleName='newTag'
<Autosuggest
ref='newTag'
value={this.state.newTag}
placeholder={i18n.__('Add tag...')}
onChange={(e) => this.handleNewTagInputChange(e)}
onKeyDown={(e) => this.handleNewTagInputKeyDown(e)}
onBlur={(e) => this.handleNewTagBlur(e)}
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
getSuggestionValue={suggestion => suggestion.name}
renderSuggestion={suggestion => (
<div>
{suggestion.name}
</div>
)}
inputProps={{
placeholder: i18n.__('Add tag...'),
value: newTag,
onChange: this.onInputChange,
onKeyDown: this.onInputKeyDown,
onBlur: this.onInputBlur
}}
/>
</div>
)
@@ -169,7 +232,6 @@ TagSelect.propTypes = {
className: PropTypes.string,
value: PropTypes.arrayOf(PropTypes.string),
onChange: PropTypes.func
}
export default CSSModules(TagSelect, styles)

View File

@@ -42,14 +42,6 @@
color: $ui-text-color
padding 4px 16px 4px 8px
.newTag
box-sizing border-box
border none
background-color transparent
outline none
padding 0 4px
font-size 13px
body[data-theme="dark"]
.tag
background-color alpha($ui-dark-tag-backgroundColor, 60%)
@@ -62,11 +54,6 @@ body[data-theme="dark"]
.tag-label
color $ui-dark-text-color
.newTag
border-color none
background-color transparent
color $ui-dark-text-color
body[data-theme="solarized-dark"]
.tag
background-color $ui-solarized-dark-tag-backgroundColor
@@ -78,11 +65,6 @@ body[data-theme="solarized-dark"]
.tag-label
color $ui-solarized-dark-text-color
.newTag
border-color none
background-color transparent
color $ui-solarized-dark-text-color
body[data-theme="monokai"]
.tag
background-color $ui-monokai-button-backgroundColor
@@ -92,9 +74,4 @@ body[data-theme="monokai"]
background-color transparent
.tag-label
color $ui-monokai-text-color
.newTag
border-color none
background-color transparent
color $ui-monokai-text-color
color $ui-monokai-text-color

View File

@@ -7,6 +7,7 @@ import modal from 'browser/main/lib/modal'
import NewNoteModal from 'browser/main/modals/NewNoteModal'
import eventEmitter from 'browser/main/lib/eventEmitter'
import i18n from 'browser/lib/i18n'
import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote'
const { remote } = require('electron')
const { dialog } = remote
@@ -37,13 +38,19 @@ class NewNoteButton extends React.Component {
const { location, dispatch, config } = this.props
const { storage, folder } = this.resolveTargetFolder()
modal.open(NewNoteModal, {
storage: storage.key,
folder: folder.key,
dispatch,
location,
config
})
if (config.ui.defaultNote === 'MARKDOWN_NOTE') {
createMarkdownNote(storage.key, folder.key, dispatch, location)
} else if (config.ui.defaultNote === 'SNIPPET_NOTE') {
createSnippetNote(storage.key, folder.key, dispatch, location, config)
} else {
modal.open(NewNoteModal, {
storage: storage.key,
folder: folder.key,
dispatch,
location,
config
})
}
}
resolveTargetFolder () {

View File

@@ -80,6 +80,7 @@ class NoteList extends React.Component {
this.getViewType = this.getViewType.bind(this)
this.restoreNote = this.restoreNote.bind(this)
this.copyNoteLink = this.copyNoteLink.bind(this)
this.navigate = this.navigate.bind(this)
// TODO: not Selected noteKeys but SelectedNote(for reusing)
this.state = {
@@ -98,6 +99,7 @@ class NoteList extends React.Component {
ee.on('list:isMarkdownNote', this.alertIfSnippetHandler)
ee.on('import:file', this.importFromFileHandler)
ee.on('list:jump', this.jumpNoteByHash)
ee.on('list:navigate', this.navigate)
}
componentWillReceiveProps (nextProps) {
@@ -687,6 +689,16 @@ class NoteList extends React.Component {
return copy(noteLink)
}
navigate (sender, pathname) {
const { router } = this.context
router.push({
pathname,
query: {
// key: noteKey
}
})
}
save (note) {
const { dispatch } = this.props
dataApi

View File

@@ -38,6 +38,22 @@ class StorageItem extends React.Component {
{
type: 'separator'
},
{
label: i18n.__('Export Storage'),
submenu: [
{
label: i18n.__('Export as txt'),
click: (e) => this.handleExportStorageClick(e, 'txt')
},
{
label: i18n.__('Export as md'),
click: (e) => this.handleExportStorageClick(e, 'md')
}
]
},
{
type: 'separator'
},
{
label: i18n.__('Unlink Storage'),
click: (e) => this.handleUnlinkStorageClick(e)
@@ -68,6 +84,30 @@ class StorageItem extends React.Component {
}
}
handleExportStorageClick (e, fileType) {
const options = {
properties: ['openDirectory', 'createDirectory'],
buttonLabel: i18n.__('Select directory'),
title: i18n.__('Select a folder to export the files to'),
multiSelections: false
}
dialog.showOpenDialog(remote.getCurrentWindow(), options,
(paths) => {
if (paths && paths.length === 1) {
const { storage, dispatch } = this.props
dataApi
.exportStorage(storage.key, fileType, paths[0])
.then(data => {
dispatch({
type: 'EXPORT_STORAGE',
storage: data.storage,
fileType: data.fileType
})
})
}
})
}
handleToggleButtonClick (e) {
const { storage, dispatch } = this.props
const isOpen = !this.state.isOpen

View File

@@ -210,12 +210,12 @@ class SideNav extends React.Component {
const tags = pathSegments[pathSegments.length - 1]
return (tags === 'alltags')
? []
: tags.split(' ')
: tags.split(' ').map(tag => decodeURIComponent(tag))
}
handleClickTagListItem (name) {
const { router } = this.context
router.push(`/tags/${name}`)
router.push(`/tags/${encodeURIComponent(name)}`)
}
handleSortTagsByChange (e) {
@@ -242,7 +242,7 @@ class SideNav extends React.Component {
} else {
listOfTags.push(tag)
}
router.push(`/tags/${listOfTags.join(' ')}`)
router.push(`/tags/${listOfTags.map(tag => encodeURIComponent(tag)).join(' ')}`)
}
emptyTrash (entries) {

View File

@@ -132,6 +132,15 @@ body[data-theme="dark"]
.CodeMirror-foldgutter-folded:after
content: "\25B8"
.CodeMirror-hover
padding 2px 4px 0 4px
position absolute
z-index 99
.CodeMirror-hyperlink
cursor pointer
.sortableItemHelper
z-index modalZIndex + 5
@@ -156,3 +165,5 @@ body[data-theme="monokai"]
body[data-theme="default"]
.SideNav ::-webkit-scrollbar-thumb
background-color rgba(255, 255, 255, 0.3)
@import '../styles/Detail/TagSelect.styl'

View File

@@ -0,0 +1,63 @@
import { findStorage } from 'browser/lib/findStorage'
import resolveStorageData from './resolveStorageData'
import resolveStorageNotes from './resolveStorageNotes'
import filenamify from 'filenamify'
import * as path from 'path'
import * as fs from 'fs'
/**
* @param {String} storageKey
* @param {String} fileType
* @param {String} exportDir
*
* @return {Object}
* ```
* {
* storage: Object,
* fileType: String,
* exportDir: String
* }
* ```
*/
function exportStorage (storageKey, fileType, exportDir) {
let targetStorage
try {
targetStorage = findStorage(storageKey)
} catch (e) {
return Promise.reject(e)
}
return resolveStorageData(targetStorage)
.then(storage => (
resolveStorageNotes(storage).then(notes => ({storage, notes}))
))
.then(function exportNotes (data) {
const { storage, notes } = data
const folderNamesMapping = {}
storage.folders.forEach(folder => {
const folderExportedDir = path.join(exportDir, filenamify(folder.name, {replacement: '_'}))
folderNamesMapping[folder.key] = folderExportedDir
// make sure directory exists
try {
fs.mkdirSync(folderExportedDir)
} catch (e) {}
})
notes
.filter(note => !note.isTrashed && note.type === 'MARKDOWN_NOTE')
.forEach(markdownNote => {
const folderExportedDir = folderNamesMapping[markdownNote.folder]
const snippetName = `${filenamify(markdownNote.title, {replacement: '_'})}.${fileType}`
const notePath = path.join(folderExportedDir, snippetName)
fs.writeFileSync(notePath, markdownNote.content)
})
return {
storage,
fileType,
exportDir
}
})
}
module.exports = exportStorage

View File

@@ -9,6 +9,7 @@ const dataApi = {
deleteFolder: require('./deleteFolder'),
reorderFolder: require('./reorderFolder'),
exportFolder: require('./exportFolder'),
exportStorage: require('./exportStorage'),
createNote: require('./createNote'),
updateNote: require('./updateNote'),
deleteNote: require('./deleteNote'),

View File

@@ -1,12 +1,9 @@
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './NewNoteModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter'
import ModalEscButton from 'browser/components/ModalEscButton'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n'
import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote'
class NewNoteModal extends React.Component {
constructor (props) {
@@ -24,31 +21,10 @@ class NewNoteModal extends React.Component {
}
handleMarkdownNoteButtonClick (e) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
const { storage, folder, dispatch, location } = this.props
dataApi
.createNote(storage, {
type: 'MARKDOWN_NOTE',
folder: folder,
title: '',
content: ''
})
.then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: { key: noteHash }
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
setTimeout(this.props.close, 200)
})
createMarkdownNote(storage, folder, dispatch, location).then(() => {
setTimeout(this.props.close, 200)
})
}
handleMarkdownNoteButtonKeyDown (e) {
@@ -59,38 +35,10 @@ class NewNoteModal extends React.Component {
}
handleSnippetNoteButtonClick (e) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
const { storage, folder, dispatch, location, config } = this.props
dataApi
.createNote(storage, {
type: 'SNIPPET_NOTE',
folder: folder,
title: '',
description: '',
snippets: [
{
name: '',
mode: config.editor.snippetDefaultLanguage || 'text',
content: ''
}
]
})
.then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: { key: noteHash }
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
setTimeout(this.props.close, 200)
})
createSnippetNote(storage, folder, dispatch, location, config).then(() => {
setTimeout(this.props.close, 200)
})
}
handleSnippetNoteButtonKeyDown (e) {

View File

@@ -67,6 +67,7 @@ class UiTab extends React.Component {
ui: {
theme: this.refs.uiTheme.value,
language: this.refs.uiLanguage.value,
defaultNote: this.refs.defaultNote.value,
showCopyNotification: this.refs.showCopyNotification.checked,
confirmDeletion: this.refs.confirmDeletion.checked,
showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked,
@@ -210,6 +211,22 @@ class UiTab extends React.Component {
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Default New Note')}
</div>
<div styleName='group-section-control'>
<select value={config.ui.defaultNote}
onChange={(e) => this.handleUIChange(e)}
ref='defaultNote'
>
<option value='ALWAYS_ASK'>{i18n.__('Always Ask')}</option>
<option value='MARKDOWN_NOTE'>{i18n.__('Markdown Note')}</option>
<option value='SNIPPET_NOTE'>{i18n.__('Snippet Note')}</option>
</select>
</div>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}

View File

@@ -216,16 +216,10 @@ function data (state = defaultDataMap(), action) {
return state
}
case 'UPDATE_FOLDER':
state = Object.assign({}, state)
state.storageMap = new Map(state.storageMap)
state.storageMap.set(action.storage.key, action.storage)
return state
case 'REORDER_FOLDER':
state = Object.assign({}, state)
state.storageMap = new Map(state.storageMap)
state.storageMap.set(action.storage.key, action.storage)
return state
case 'EXPORT_FOLDER':
case 'RENAME_STORAGE':
case 'EXPORT_STORAGE':
state = Object.assign({}, state)
state.storageMap = new Map(state.storageMap)
state.storageMap.set(action.storage.key, action.storage)
@@ -355,11 +349,6 @@ function data (state = defaultDataMap(), action) {
})
}
return state
case 'RENAME_STORAGE':
state = Object.assign({}, state)
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)

View File

@@ -0,0 +1,109 @@
.TagSelect
.react-autosuggest__input
box-sizing border-box
border none
background-color transparent
outline none
padding 0 4px
font-size 13px
ul
position fixed
z-index 999
box-sizing border-box
list-style none
padding 0
margin 0
border-radius 4px
margin .2em 0 0
background-color $ui-noteList-backgroundColor
border 1px solid rgba(0,0,0,.3)
box-shadow .05em .2em .6em rgba(0,0,0,.2)
text-shadow none
&:empty,
&[hidden]
display none
&:before
content ""
position absolute
top -7px
left 14px
width 0 height 0
padding 6px
background-color $ui-noteList-backgroundColor
border inherit
border-right 0
border-bottom 0
-webkit-transform rotate(45deg)
transform rotate(45deg)
li
position relative
padding 6px 18px 6px 10px
cursor pointer
li[aria-selected="true"]
background-color alpha($ui-button--active-backgroundColor, 40%)
color $ui-text-color
body[data-theme="dark"]
.TagSelect
.react-autosuggest__input
color $ui-dark-text-color
ul
border-color $ui-dark-borderColor
background-color $ui-dark-noteList-backgroundColor
color $ui-dark-text-color
&:before
background-color $ui-dark-noteList-backgroundColor
li[aria-selected="true"]
background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color
body[data-theme="monokai"]
.TagSelect
.react-autosuggest__input
color $ui-monokai-text-color
ul
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
color $ui-monokai-text-color
&:before
background-color $ui-dark-noteList-backgroundColor
li[aria-selected="true"]
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
body[data-theme="solarized-dark"]
.TagSelect
.react-autosuggest__input
color $ui-solarized-dark-text-color
ul
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteList-backgroundColor
color $ui-solarized-dark-text-color
&:before
background-color $ui-solarized-dark-noteList-backgroundColor
li[aria-selected="true"]
background-color $ui-dark-button--active-backgroundColor
color $ui-solarized-dark-text-color
body[data-theme="white"]
.TagSelect
ul
background-color $ui-white-noteList-backgroundColor
li[aria-selected="true"]
background-color $ui-button--active-backgroundColor