mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-12 17:26:17 +00:00
fixed eslint error & integrated with prettier as well as formatted the whole codebase (#3450)
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": ["standard", "standard-jsx", "plugin:react/recommended"],
|
"extends": ["standard", "standard-jsx", "plugin:react/recommended", "prettier"],
|
||||||
"plugins": ["react"],
|
"plugins": ["react", "prettier"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-useless-escape": 0,
|
"no-useless-escape": 0,
|
||||||
"prefer-const": ["warn", {
|
"prefer-const": ["warn", {
|
||||||
@@ -13,7 +13,8 @@
|
|||||||
"react/no-string-refs": 0,
|
"react/no-string-refs": 0,
|
||||||
"react/no-find-dom-node": "warn",
|
"react/no-find-dom-node": "warn",
|
||||||
"react/no-render-return-value": "warn",
|
"react/no-render-return-value": "warn",
|
||||||
"react/no-deprecated": "warn"
|
"react/no-deprecated": "warn",
|
||||||
|
"prettier/prettier": ["error"]
|
||||||
},
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
"FileReader": true,
|
"FileReader": true,
|
||||||
|
|||||||
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"semi": false,
|
||||||
|
"jsxSingleQuote": true
|
||||||
|
}
|
||||||
@@ -6,11 +6,7 @@ import hljs from 'highlight.js'
|
|||||||
import 'codemirror-mode-elixir'
|
import 'codemirror-mode-elixir'
|
||||||
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
||||||
import convertModeName from 'browser/lib/convertModeName'
|
import convertModeName from 'browser/lib/convertModeName'
|
||||||
import {
|
import { options, TableEditor, Alignment } from '@susisu/mte-kernel'
|
||||||
options,
|
|
||||||
TableEditor,
|
|
||||||
Alignment
|
|
||||||
} from '@susisu/mte-kernel'
|
|
||||||
import TextEditorInterface from 'browser/lib/TextEditorInterface'
|
import TextEditorInterface from 'browser/lib/TextEditorInterface'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
import iconv from 'iconv-lite'
|
import iconv from 'iconv-lite'
|
||||||
@@ -20,11 +16,15 @@ import styles from '../components/CodeEditor.styl'
|
|||||||
const { ipcRenderer, remote, clipboard } = require('electron')
|
const { ipcRenderer, remote, clipboard } = require('electron')
|
||||||
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
|
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
|
||||||
const spellcheck = require('browser/lib/spellcheck')
|
const spellcheck = require('browser/lib/spellcheck')
|
||||||
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder').buildEditorContextMenu
|
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
|
||||||
|
.buildEditorContextMenu
|
||||||
import { createTurndownService } from '../lib/turndown'
|
import { createTurndownService } from '../lib/turndown'
|
||||||
import {languageMaps} from '../lib/CMLanguageList'
|
import { languageMaps } from '../lib/CMLanguageList'
|
||||||
import snippetManager from '../lib/SnippetManager'
|
import snippetManager from '../lib/SnippetManager'
|
||||||
import {generateInEditor, tocExistsInEditor} from 'browser/lib/markdown-toc-generator'
|
import {
|
||||||
|
generateInEditor,
|
||||||
|
tocExistsInEditor
|
||||||
|
} from 'browser/lib/markdown-toc-generator'
|
||||||
import markdownlint from 'markdownlint'
|
import markdownlint from 'markdownlint'
|
||||||
import Jsonlint from 'jsonlint-mod'
|
import Jsonlint from 'jsonlint-mod'
|
||||||
import { DEFAULT_CONFIG } from '../main/lib/ConfigManager'
|
import { DEFAULT_CONFIG } from '../main/lib/ConfigManager'
|
||||||
@@ -33,28 +33,38 @@ import prettier from 'prettier'
|
|||||||
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||||
|
|
||||||
const buildCMRulers = (rulers, enableRulers) =>
|
const buildCMRulers = (rulers, enableRulers) =>
|
||||||
(enableRulers ? rulers.map(ruler => ({
|
enableRulers
|
||||||
column: ruler
|
? rulers.map(ruler => ({
|
||||||
})) : [])
|
column: ruler
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
|
||||||
function translateHotkey (hotkey) {
|
function translateHotkey(hotkey) {
|
||||||
return hotkey.replace(/\s*\+\s*/g, '-').replace(/Command/g, 'Cmd').replace(/Control/g, 'Ctrl')
|
return hotkey
|
||||||
|
.replace(/\s*\+\s*/g, '-')
|
||||||
|
.replace(/Command/g, 'Cmd')
|
||||||
|
.replace(/Control/g, 'Ctrl')
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class CodeEditor extends React.Component {
|
export default class CodeEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {
|
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {
|
||||||
leading: false,
|
leading: false,
|
||||||
trailing: true
|
trailing: true
|
||||||
})
|
})
|
||||||
this.changeHandler = (editor, changeObject) => this.handleChange(editor, changeObject)
|
this.changeHandler = (editor, changeObject) =>
|
||||||
this.highlightHandler = (editor, changeObject) => this.handleHighlight(editor, changeObject)
|
this.handleChange(editor, changeObject)
|
||||||
|
this.highlightHandler = (editor, changeObject) =>
|
||||||
|
this.handleHighlight(editor, changeObject)
|
||||||
this.focusHandler = () => {
|
this.focusHandler = () => {
|
||||||
ipcRenderer.send('editor:focused', true)
|
ipcRenderer.send('editor:focused', true)
|
||||||
}
|
}
|
||||||
const debouncedDeletionOfAttachments = _.debounce(attachmentManagement.deleteAttachmentsNotPresentInNote, 30000)
|
const debouncedDeletionOfAttachments = _.debounce(
|
||||||
|
attachmentManagement.deleteAttachmentsNotPresentInNote,
|
||||||
|
30000
|
||||||
|
)
|
||||||
this.blurHandler = (editor, e) => {
|
this.blurHandler = (editor, e) => {
|
||||||
ipcRenderer.send('editor:focused', false)
|
ipcRenderer.send('editor:focused', false)
|
||||||
if (e == null) return null
|
if (e == null) return null
|
||||||
@@ -66,12 +76,13 @@ export default class CodeEditor extends React.Component {
|
|||||||
el = el.parentNode
|
el = el.parentNode
|
||||||
}
|
}
|
||||||
this.props.onBlur != null && this.props.onBlur(e)
|
this.props.onBlur != null && this.props.onBlur(e)
|
||||||
const {
|
const { storageKey, noteKey } = this.props
|
||||||
storageKey,
|
|
||||||
noteKey
|
|
||||||
} = this.props
|
|
||||||
if (this.props.deleteUnusedAttachments === true) {
|
if (this.props.deleteUnusedAttachments === true) {
|
||||||
debouncedDeletionOfAttachments(this.editor.getValue(), storageKey, noteKey)
|
debouncedDeletionOfAttachments(
|
||||||
|
this.editor.getValue(),
|
||||||
|
storageKey,
|
||||||
|
noteKey
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.pasteHandler = (editor, e) => {
|
this.pasteHandler = (editor, e) => {
|
||||||
@@ -91,7 +102,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.formatTable = () => this.handleFormatTable()
|
this.formatTable = () => this.handleFormatTable()
|
||||||
|
|
||||||
if (props.switchPreview !== 'RIGHTCLICK') {
|
if (props.switchPreview !== 'RIGHTCLICK') {
|
||||||
this.contextMenuHandler = function (editor, event) {
|
this.contextMenuHandler = function(editor, event) {
|
||||||
const menu = buildEditorContextMenu(editor, event)
|
const menu = buildEditorContextMenu(editor, event)
|
||||||
if (menu != null) {
|
if (menu != null) {
|
||||||
setTimeout(() => menu.popup(remote.getCurrentWindow()), 30)
|
setTimeout(() => menu.popup(remote.getCurrentWindow()), 30)
|
||||||
@@ -104,24 +115,24 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.turndownService = createTurndownService()
|
this.turndownService = createTurndownService()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearch (msg) {
|
handleSearch(msg) {
|
||||||
const cm = this.editor
|
const cm = this.editor
|
||||||
const component = this
|
const component = this
|
||||||
|
|
||||||
if (component.searchState) cm.removeOverlay(component.searchState)
|
if (component.searchState) cm.removeOverlay(component.searchState)
|
||||||
if (msg.length < 1) return
|
if (msg.length < 1) return
|
||||||
|
|
||||||
cm.operation(function () {
|
cm.operation(function() {
|
||||||
component.searchState = makeOverlay(msg, 'searching')
|
component.searchState = makeOverlay(msg, 'searching')
|
||||||
cm.addOverlay(component.searchState)
|
cm.addOverlay(component.searchState)
|
||||||
|
|
||||||
function makeOverlay (query, style) {
|
function makeOverlay(query, style) {
|
||||||
query = new RegExp(
|
query = new RegExp(
|
||||||
query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'),
|
query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'),
|
||||||
'gi'
|
'gi'
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
token: function (stream) {
|
token: function(stream) {
|
||||||
query.lastIndex = stream.pos
|
query.lastIndex = stream.pos
|
||||||
var match = query.exec(stream.string)
|
var match = query.exec(stream.string)
|
||||||
if (match && match.index === stream.pos) {
|
if (match && match.index === stream.pos) {
|
||||||
@@ -138,25 +149,27 @@ export default class CodeEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFormatTable () {
|
handleFormatTable() {
|
||||||
this.tableEditor.formatAll(options({
|
this.tableEditor.formatAll(
|
||||||
textWidthOptions: {}
|
options({
|
||||||
}))
|
textWidthOptions: {}
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEditorActivity () {
|
handleEditorActivity() {
|
||||||
if (!this.textEditorInterface.transaction) {
|
if (!this.textEditorInterface.transaction) {
|
||||||
this.updateTableEditorState()
|
this.updateTableEditorState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDefaultKeyMap () {
|
updateDefaultKeyMap() {
|
||||||
const { hotkey } = this.props
|
const { hotkey } = this.props
|
||||||
const self = this
|
const self = this
|
||||||
const expandSnippet = snippetManager.expandSnippet
|
const expandSnippet = snippetManager.expandSnippet
|
||||||
|
|
||||||
this.defaultKeyMap = CodeMirror.normalizeKeyMap({
|
this.defaultKeyMap = CodeMirror.normalizeKeyMap({
|
||||||
Tab: function (cm) {
|
Tab: function(cm) {
|
||||||
const cursor = cm.getCursor()
|
const cursor = cm.getCursor()
|
||||||
const line = cm.getLine(cursor.line)
|
const line = cm.getLine(cursor.line)
|
||||||
const cursorPosition = cursor.ch
|
const cursorPosition = cursor.ch
|
||||||
@@ -198,17 +211,17 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Cmd-Left': function (cm) {
|
'Cmd-Left': function(cm) {
|
||||||
cm.execCommand('goLineLeft')
|
cm.execCommand('goLineLeft')
|
||||||
},
|
},
|
||||||
'Cmd-T': function (cm) {
|
'Cmd-T': function(cm) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
},
|
},
|
||||||
[translateHotkey(hotkey.insertDate)]: function (cm) {
|
[translateHotkey(hotkey.insertDate)]: function(cm) {
|
||||||
const dateNow = new Date()
|
const dateNow = new Date()
|
||||||
cm.replaceSelection(dateNow.toLocaleDateString())
|
cm.replaceSelection(dateNow.toLocaleDateString())
|
||||||
},
|
},
|
||||||
[translateHotkey(hotkey.insertDateTime)]: function (cm) {
|
[translateHotkey(hotkey.insertDateTime)]: function(cm) {
|
||||||
const dateNow = new Date()
|
const dateNow = new Date()
|
||||||
cm.replaceSelection(dateNow.toLocaleString())
|
cm.replaceSelection(dateNow.toLocaleString())
|
||||||
},
|
},
|
||||||
@@ -231,7 +244,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
currentConfig.cursorOffset = cm.doc.indexFromPos(cursorPos)
|
currentConfig.cursorOffset = cm.doc.indexFromPos(cursorPos)
|
||||||
|
|
||||||
// Prettify contents of editor
|
// Prettify contents of editor
|
||||||
const formattedTextDetails = prettier.formatWithCursor(cm.doc.getValue(), currentConfig)
|
const formattedTextDetails = prettier.formatWithCursor(
|
||||||
|
cm.doc.getValue(),
|
||||||
|
currentConfig
|
||||||
|
)
|
||||||
|
|
||||||
const formattedText = formattedTextDetails.formatted
|
const formattedText = formattedTextDetails.formatted
|
||||||
const formattedCursorPos = formattedTextDetails.cursorOffset
|
const formattedCursorPos = formattedTextDetails.cursorOffset
|
||||||
@@ -246,7 +262,8 @@ export default class CodeEditor extends React.Component {
|
|||||||
const appendLineBreak = /\n$/.test(selection)
|
const appendLineBreak = /\n$/.test(selection)
|
||||||
|
|
||||||
const sorted = _.split(selection.trim(), '\n').sort()
|
const sorted = _.split(selection.trim(), '\n').sort()
|
||||||
const sortedString = _.join(sorted, '\n') + (appendLineBreak ? '\n' : '')
|
const sortedString =
|
||||||
|
_.join(sorted, '\n') + (appendLineBreak ? '\n' : '')
|
||||||
|
|
||||||
cm.doc.replaceSelection(sortedString)
|
cm.doc.replaceSelection(sortedString)
|
||||||
},
|
},
|
||||||
@@ -256,7 +273,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTableEditorState () {
|
updateTableEditorState() {
|
||||||
const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions)
|
const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions)
|
||||||
if (active) {
|
if (active) {
|
||||||
if (this.extraKeysMode !== 'editor') {
|
if (this.extraKeysMode !== 'editor') {
|
||||||
@@ -272,7 +289,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
const { rulers, enableRulers, enableMarkdownLint, RTL } = this.props
|
const { rulers, enableRulers, enableMarkdownLint, RTL } = this.props
|
||||||
eventEmitter.on('line:jump', this.scrollToLineHandeler)
|
eventEmitter.on('line:jump', this.scrollToLineHandeler)
|
||||||
|
|
||||||
@@ -298,7 +315,11 @@ export default class CodeEditor extends React.Component {
|
|||||||
rtlMoveVisually: RTL,
|
rtlMoveVisually: RTL,
|
||||||
foldGutter: true,
|
foldGutter: true,
|
||||||
lint: enableMarkdownLint ? this.getCodeEditorLintConfig() : false,
|
lint: enableMarkdownLint ? this.getCodeEditorLintConfig() : false,
|
||||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
|
gutters: [
|
||||||
|
'CodeMirror-linenumbers',
|
||||||
|
'CodeMirror-foldgutter',
|
||||||
|
'CodeMirror-lint-markers'
|
||||||
|
],
|
||||||
autoCloseBrackets: {
|
autoCloseBrackets: {
|
||||||
pairs: this.props.matchingPairs,
|
pairs: this.props.matchingPairs,
|
||||||
triples: this.props.matchingTriples,
|
triples: this.props.matchingTriples,
|
||||||
@@ -309,7 +330,9 @@ export default class CodeEditor extends React.Component {
|
|||||||
prettierConfig: this.props.prettierConfig
|
prettierConfig: this.props.prettierConfig
|
||||||
})
|
})
|
||||||
|
|
||||||
document.querySelector('.CodeMirror-lint-markers').style.display = enableMarkdownLint ? 'inline-block' : 'none'
|
document.querySelector(
|
||||||
|
'.CodeMirror-lint-markers'
|
||||||
|
).style.display = enableMarkdownLint ? 'inline-block' : 'none'
|
||||||
|
|
||||||
if (!this.props.mode && this.props.value && this.props.autoDetect) {
|
if (!this.props.mode && this.props.value && this.props.autoDetect) {
|
||||||
this.autoDetectLanguage(this.props.value)
|
this.autoDetectLanguage(this.props.value)
|
||||||
@@ -342,7 +365,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.textEditorInterface = new TextEditorInterface(this.editor)
|
this.textEditorInterface = new TextEditorInterface(this.editor)
|
||||||
this.tableEditor = new TableEditor(this.textEditorInterface)
|
this.tableEditor = new TableEditor(this.textEditorInterface)
|
||||||
if (this.props.spellCheck) {
|
if (this.props.spellCheck) {
|
||||||
this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'})
|
this.editor.addPanel(this.createSpellCheckPanel(), { position: 'bottom' })
|
||||||
}
|
}
|
||||||
|
|
||||||
eventEmitter.on('code:format-table', this.formatTable)
|
eventEmitter.on('code:format-table', this.formatTable)
|
||||||
@@ -352,13 +375,13 @@ export default class CodeEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.editorKeyMap = CodeMirror.normalizeKeyMap({
|
this.editorKeyMap = CodeMirror.normalizeKeyMap({
|
||||||
'Tab': () => {
|
Tab: () => {
|
||||||
this.tableEditor.nextCell(this.tableEditorOptions)
|
this.tableEditor.nextCell(this.tableEditorOptions)
|
||||||
},
|
},
|
||||||
'Shift-Tab': () => {
|
'Shift-Tab': () => {
|
||||||
this.tableEditor.previousCell(this.tableEditorOptions)
|
this.tableEditor.previousCell(this.tableEditorOptions)
|
||||||
},
|
},
|
||||||
'Enter': () => {
|
Enter: () => {
|
||||||
this.tableEditor.nextRow(this.tableEditorOptions)
|
this.tableEditor.nextRow(this.tableEditorOptions)
|
||||||
},
|
},
|
||||||
'Ctrl-Enter': () => {
|
'Ctrl-Enter': () => {
|
||||||
@@ -477,7 +500,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.initialHighlighting()
|
this.initialHighlighting()
|
||||||
}
|
}
|
||||||
|
|
||||||
getWordBeforeCursor (line, lineNumber, cursorPosition) {
|
getWordBeforeCursor(line, lineNumber, cursorPosition) {
|
||||||
let wordBeforeCursor = ''
|
let wordBeforeCursor = ''
|
||||||
const originCursorPosition = cursorPosition
|
const originCursorPosition = cursorPosition
|
||||||
const emptyChars = /\t|\s|\r|\n|\$/
|
const emptyChars = /\t|\s|\r|\n|\$/
|
||||||
@@ -514,11 +537,11 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
quitEditor () {
|
quitEditor() {
|
||||||
document.querySelector('textarea').blur()
|
document.querySelector('textarea').blur()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount() {
|
||||||
this.editor.off('focus', this.focusHandler)
|
this.editor.off('focus', this.focusHandler)
|
||||||
this.editor.off('blur', this.blurHandler)
|
this.editor.off('blur', this.blurHandler)
|
||||||
this.editor.off('change', this.changeHandler)
|
this.editor.off('change', this.changeHandler)
|
||||||
@@ -533,7 +556,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
eventEmitter.off('code:format-table', this.formatTable)
|
eventEmitter.off('code:format-table', this.formatTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
let needRefresh = false
|
let needRefresh = false
|
||||||
const {
|
const {
|
||||||
rulers,
|
rulers,
|
||||||
@@ -561,13 +584,18 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.setOption('direction', this.props.RTL ? 'rtl' : 'ltr')
|
this.editor.setOption('direction', this.props.RTL ? 'rtl' : 'ltr')
|
||||||
this.editor.setOption('rtlMoveVisually', this.props.RTL)
|
this.editor.setOption('rtlMoveVisually', this.props.RTL)
|
||||||
}
|
}
|
||||||
if (prevProps.enableMarkdownLint !== enableMarkdownLint || prevProps.customMarkdownLintConfig !== customMarkdownLintConfig) {
|
if (
|
||||||
|
prevProps.enableMarkdownLint !== enableMarkdownLint ||
|
||||||
|
prevProps.customMarkdownLintConfig !== customMarkdownLintConfig
|
||||||
|
) {
|
||||||
if (!enableMarkdownLint) {
|
if (!enableMarkdownLint) {
|
||||||
this.editor.setOption('lint', {default: false})
|
this.editor.setOption('lint', { default: false })
|
||||||
document.querySelector('.CodeMirror-lint-markers').style.display = 'none'
|
document.querySelector('.CodeMirror-lint-markers').style.display =
|
||||||
|
'none'
|
||||||
} else {
|
} else {
|
||||||
this.editor.setOption('lint', this.getCodeEditorLintConfig())
|
this.editor.setOption('lint', this.getCodeEditorLintConfig())
|
||||||
document.querySelector('.CodeMirror-lint-markers').style.display = 'inline-block'
|
document.querySelector('.CodeMirror-lint-markers').style.display =
|
||||||
|
'inline-block'
|
||||||
}
|
}
|
||||||
needRefresh = true
|
needRefresh = true
|
||||||
}
|
}
|
||||||
@@ -599,9 +627,11 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
|
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevProps.matchingPairs !== this.props.matchingPairs ||
|
if (
|
||||||
|
prevProps.matchingPairs !== this.props.matchingPairs ||
|
||||||
prevProps.matchingTriples !== this.props.matchingTriples ||
|
prevProps.matchingTriples !== this.props.matchingTriples ||
|
||||||
prevProps.explodingPairs !== this.props.explodingPairs) {
|
prevProps.explodingPairs !== this.props.explodingPairs
|
||||||
|
) {
|
||||||
const bracketObject = {
|
const bracketObject = {
|
||||||
pairs: this.props.matchingPairs,
|
pairs: this.props.matchingPairs,
|
||||||
triples: this.props.matchingTriples,
|
triples: this.props.matchingTriples,
|
||||||
@@ -646,11 +676,18 @@ export default class CodeEditor extends React.Component {
|
|||||||
const elem = document.getElementById('editor-bottom-panel')
|
const elem = document.getElementById('editor-bottom-panel')
|
||||||
elem.parentNode.removeChild(elem)
|
elem.parentNode.removeChild(elem)
|
||||||
} else {
|
} else {
|
||||||
this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'})
|
this.editor.addPanel(this.createSpellCheckPanel(), {
|
||||||
|
position: 'bottom'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (prevProps.deleteUnusedAttachments !== this.props.deleteUnusedAttachments) {
|
if (
|
||||||
this.editor.setOption('deleteUnusedAttachments', this.props.deleteUnusedAttachments)
|
prevProps.deleteUnusedAttachments !== this.props.deleteUnusedAttachments
|
||||||
|
) {
|
||||||
|
this.editor.setOption(
|
||||||
|
'deleteUnusedAttachments',
|
||||||
|
this.props.deleteUnusedAttachments
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needRefresh) {
|
if (needRefresh) {
|
||||||
@@ -658,17 +695,19 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getCodeEditorLintConfig () {
|
getCodeEditorLintConfig() {
|
||||||
const { mode } = this.props
|
const { mode } = this.props
|
||||||
const checkMarkdownNoteIsOpen = mode === 'Boost Flavored Markdown'
|
const checkMarkdownNoteIsOpen = mode === 'Boost Flavored Markdown'
|
||||||
|
|
||||||
return checkMarkdownNoteIsOpen ? {
|
return checkMarkdownNoteIsOpen
|
||||||
getAnnotations: this.validatorOfMarkdown,
|
? {
|
||||||
async: true
|
getAnnotations: this.validatorOfMarkdown,
|
||||||
} : false
|
async: true
|
||||||
|
}
|
||||||
|
: false
|
||||||
}
|
}
|
||||||
|
|
||||||
validatorOfMarkdown (text, updateLinting) {
|
validatorOfMarkdown(text, updateLinting) {
|
||||||
const { customMarkdownLintConfig } = this.props
|
const { customMarkdownLintConfig } = this.props
|
||||||
let lintConfigJson
|
let lintConfigJson
|
||||||
try {
|
try {
|
||||||
@@ -693,7 +732,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
let ruleNames = ''
|
let ruleNames = ''
|
||||||
item.ruleNames.map((ruleName, index) => {
|
item.ruleNames.map((ruleName, index) => {
|
||||||
ruleNames += ruleName
|
ruleNames += ruleName
|
||||||
ruleNames += (index === item.ruleNames.length - 1) ? ': ' : '/'
|
ruleNames += index === item.ruleNames.length - 1 ? ': ' : '/'
|
||||||
})
|
})
|
||||||
const lineNumber = item.lineNumber - 1
|
const lineNumber = item.lineNumber - 1
|
||||||
foundIssues.push({
|
foundIssues.push({
|
||||||
@@ -708,7 +747,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setMode (mode) {
|
setMode(mode) {
|
||||||
let syntax = CodeMirror.findModeByName(convertModeName(mode || 'text'))
|
let syntax = CodeMirror.findModeByName(convertModeName(mode || 'text'))
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
|
|
||||||
@@ -716,7 +755,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
CodeMirror.autoLoadMode(this.editor, syntax.mode)
|
CodeMirror.autoLoadMode(this.editor, syntax.mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange (editor, changeObject) {
|
handleChange(editor, changeObject) {
|
||||||
spellcheck.handleChange(editor, changeObject)
|
spellcheck.handleChange(editor, changeObject)
|
||||||
|
|
||||||
// The current note contains an toc. We'll check for changes on headlines.
|
// The current note contains an toc. We'll check for changes on headlines.
|
||||||
@@ -726,7 +765,11 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
// Check if one of the changed lines contains a headline
|
// Check if one of the changed lines contains a headline
|
||||||
for (let line = 0; line < changeObject.text.length; line++) {
|
for (let line = 0; line < changeObject.text.length; line++) {
|
||||||
if (this.linePossibleContainsHeadline(editor.getLine(changeObject.from.line + line))) {
|
if (
|
||||||
|
this.linePossibleContainsHeadline(
|
||||||
|
editor.getLine(changeObject.from.line + line)
|
||||||
|
)
|
||||||
|
) {
|
||||||
requireTocUpdate = true
|
requireTocUpdate = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -755,13 +798,13 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
linePossibleContainsHeadline (currentLine) {
|
linePossibleContainsHeadline(currentLine) {
|
||||||
// We can't check if the line start with # because when some write text before
|
// We can't check if the line start with # because when some write text before
|
||||||
// the # we also need to update the toc
|
// the # we also need to update the toc
|
||||||
return currentLine.includes('# ')
|
return currentLine.includes('# ')
|
||||||
}
|
}
|
||||||
|
|
||||||
incrementLines (start, linesAdded, linesRemoved, editor) {
|
incrementLines(start, linesAdded, linesRemoved, editor) {
|
||||||
const highlightedLines = editor.options.linesHighlighted
|
const highlightedLines = editor.options.linesHighlighted
|
||||||
|
|
||||||
const totalHighlightedLines = highlightedLines.length
|
const totalHighlightedLines = highlightedLines.length
|
||||||
@@ -782,7 +825,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
highlightedLines.splice(highlightedLines.indexOf(lineNumber), 1)
|
highlightedLines.splice(highlightedLines.indexOf(lineNumber), 1)
|
||||||
|
|
||||||
// Lines that need to be relocated
|
// Lines that need to be relocated
|
||||||
if (lineNumber >= (start + linesRemoved)) {
|
if (lineNumber >= start + linesRemoved) {
|
||||||
newLines.push(lineNumber + offset)
|
newLines.push(lineNumber + offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -796,22 +839,30 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHighlight (editor, changeObject) {
|
handleHighlight(editor, changeObject) {
|
||||||
const lines = editor.options.linesHighlighted
|
const lines = editor.options.linesHighlighted
|
||||||
|
|
||||||
if (!lines.includes(changeObject)) {
|
if (!lines.includes(changeObject)) {
|
||||||
lines.push(changeObject)
|
lines.push(changeObject)
|
||||||
editor.addLineClass(changeObject, 'text', 'CodeMirror-activeline-background')
|
editor.addLineClass(
|
||||||
|
changeObject,
|
||||||
|
'text',
|
||||||
|
'CodeMirror-activeline-background'
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
lines.splice(lines.indexOf(changeObject), 1)
|
lines.splice(lines.indexOf(changeObject), 1)
|
||||||
editor.removeLineClass(changeObject, 'text', 'CodeMirror-activeline-background')
|
editor.removeLineClass(
|
||||||
|
changeObject,
|
||||||
|
'text',
|
||||||
|
'CodeMirror-activeline-background'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (this.props.onChange) {
|
if (this.props.onChange) {
|
||||||
this.props.onChange(editor)
|
this.props.onChange(editor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateHighlight (editor, changeObject) {
|
updateHighlight(editor, changeObject) {
|
||||||
const linesAdded = changeObject.text.length - 1
|
const linesAdded = changeObject.text.length - 1
|
||||||
const linesRemoved = changeObject.removed.length - 1
|
const linesRemoved = changeObject.removed.length - 1
|
||||||
|
|
||||||
@@ -842,28 +893,28 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.incrementLines(start, linesAdded, linesRemoved, editor)
|
this.incrementLines(start, linesAdded, linesRemoved, editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
moveCursorTo (row, col) {}
|
moveCursorTo(row, col) {}
|
||||||
|
|
||||||
scrollToLine (event, num) {
|
scrollToLine(event, num) {
|
||||||
const cursor = {
|
const cursor = {
|
||||||
line: num,
|
line: num,
|
||||||
ch: 1
|
ch: 1
|
||||||
}
|
}
|
||||||
this.editor.setCursor(cursor)
|
this.editor.setCursor(cursor)
|
||||||
const top = this.editor.charCoords({line: num, ch: 0}, 'local').top
|
const top = this.editor.charCoords({ line: num, ch: 0 }, 'local').top
|
||||||
const middleHeight = this.editor.getScrollerElement().offsetHeight / 2
|
const middleHeight = this.editor.getScrollerElement().offsetHeight / 2
|
||||||
this.editor.scrollTo(null, top - middleHeight - 5)
|
this.editor.scrollTo(null, top - middleHeight - 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus() {
|
||||||
this.editor.focus()
|
this.editor.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
blur () {
|
blur() {
|
||||||
this.editor.blur()
|
this.editor.blur()
|
||||||
}
|
}
|
||||||
|
|
||||||
reload () {
|
reload() {
|
||||||
// Change event shouldn't be fired when switch note
|
// Change event shouldn't be fired when switch note
|
||||||
this.editor.off('change', this.changeHandler)
|
this.editor.off('change', this.changeHandler)
|
||||||
this.value = this.props.value
|
this.value = this.props.value
|
||||||
@@ -874,7 +925,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.refresh()
|
this.editor.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue (value) {
|
setValue(value) {
|
||||||
const cursor = this.editor.getCursor()
|
const cursor = this.editor.getCursor()
|
||||||
this.editor.setValue(value)
|
this.editor.setValue(value)
|
||||||
this.editor.setCursor(cursor)
|
this.editor.setCursor(cursor)
|
||||||
@@ -885,18 +936,19 @@ export default class CodeEditor extends React.Component {
|
|||||||
* @param {Number} lineNumber
|
* @param {Number} lineNumber
|
||||||
* @param {String} content
|
* @param {String} content
|
||||||
*/
|
*/
|
||||||
setLineContent (lineNumber, content) {
|
setLineContent(lineNumber, content) {
|
||||||
const prevContent = this.editor.getLine(lineNumber)
|
const prevContent = this.editor.getLine(lineNumber)
|
||||||
const prevContentLength = prevContent ? prevContent.length : 0
|
const prevContentLength = prevContent ? prevContent.length : 0
|
||||||
this.editor.replaceRange(content, { line: lineNumber, ch: 0 }, { line: lineNumber, ch: prevContentLength })
|
this.editor.replaceRange(
|
||||||
|
content,
|
||||||
|
{ line: lineNumber, ch: 0 },
|
||||||
|
{ line: lineNumber, ch: prevContentLength }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDropImage (dropEvent) {
|
handleDropImage(dropEvent) {
|
||||||
dropEvent.preventDefault()
|
dropEvent.preventDefault()
|
||||||
const {
|
const { storageKey, noteKey } = this.props
|
||||||
storageKey,
|
|
||||||
noteKey
|
|
||||||
} = this.props
|
|
||||||
attachmentManagement.handleAttachmentDrop(
|
attachmentManagement.handleAttachmentDrop(
|
||||||
this,
|
this,
|
||||||
storageKey,
|
storageKey,
|
||||||
@@ -905,37 +957,44 @@ export default class CodeEditor extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
insertAttachmentMd (imageMd) {
|
insertAttachmentMd(imageMd) {
|
||||||
this.editor.replaceSelection(imageMd)
|
this.editor.replaceSelection(imageMd)
|
||||||
}
|
}
|
||||||
|
|
||||||
autoDetectLanguage (content) {
|
autoDetectLanguage(content) {
|
||||||
const res = hljs.highlightAuto(content, Object.keys(languageMaps))
|
const res = hljs.highlightAuto(content, Object.keys(languageMaps))
|
||||||
this.setMode(languageMaps[res.language])
|
this.setMode(languageMaps[res.language])
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePaste (editor, forceSmartPaste) {
|
handlePaste(editor, forceSmartPaste) {
|
||||||
const { storageKey, noteKey, fetchUrlTitle, enableSmartPaste } = this.props
|
const { storageKey, noteKey, fetchUrlTitle, enableSmartPaste } = this.props
|
||||||
|
|
||||||
const isURL = str => /(?:^\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
|
const isURL = str =>
|
||||||
|
/(?:^\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
|
||||||
|
|
||||||
const isInLinkTag = editor => {
|
const isInLinkTag = editor => {
|
||||||
const startCursor = editor.getCursor('start')
|
const startCursor = editor.getCursor('start')
|
||||||
const prevChar = editor.getRange({
|
const prevChar = editor.getRange(
|
||||||
line: startCursor.line,
|
{
|
||||||
ch: startCursor.ch - 2
|
line: startCursor.line,
|
||||||
}, {
|
ch: startCursor.ch - 2
|
||||||
line: startCursor.line,
|
},
|
||||||
ch: startCursor.ch
|
{
|
||||||
})
|
line: startCursor.line,
|
||||||
|
ch: startCursor.ch
|
||||||
|
}
|
||||||
|
)
|
||||||
const endCursor = editor.getCursor('end')
|
const endCursor = editor.getCursor('end')
|
||||||
const nextChar = editor.getRange({
|
const nextChar = editor.getRange(
|
||||||
line: endCursor.line,
|
{
|
||||||
ch: endCursor.ch
|
line: endCursor.line,
|
||||||
}, {
|
ch: endCursor.ch
|
||||||
line: endCursor.line,
|
},
|
||||||
ch: endCursor.ch + 1
|
{
|
||||||
})
|
line: endCursor.line,
|
||||||
|
ch: endCursor.ch + 1
|
||||||
|
}
|
||||||
|
)
|
||||||
return prevChar === '](' && nextChar === ')'
|
return prevChar === '](' && nextChar === ')'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -947,7 +1006,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
let line = line = cursor.line - 1
|
let line = (line = cursor.line - 1)
|
||||||
while (line >= 0) {
|
while (line >= 0) {
|
||||||
token = editor.getTokenAt({
|
token = editor.getTokenAt({
|
||||||
ch: 3,
|
ch: 3,
|
||||||
@@ -979,7 +1038,11 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
if (isInFencedCodeBlock(editor)) {
|
if (isInFencedCodeBlock(editor)) {
|
||||||
this.handlePasteText(editor, pastedTxt)
|
this.handlePasteText(editor, pastedTxt)
|
||||||
} else if (fetchUrlTitle && isMarkdownTitleURL(pastedTxt) && !isInLinkTag(editor)) {
|
} else if (
|
||||||
|
fetchUrlTitle &&
|
||||||
|
isMarkdownTitleURL(pastedTxt) &&
|
||||||
|
!isInLinkTag(editor)
|
||||||
|
) {
|
||||||
this.handlePasteUrl(editor, pastedTxt)
|
this.handlePasteUrl(editor, pastedTxt)
|
||||||
} else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
|
} else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
|
||||||
this.handlePasteUrl(editor, pastedTxt)
|
this.handlePasteUrl(editor, pastedTxt)
|
||||||
@@ -1015,13 +1078,13 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScroll (e) {
|
handleScroll(e) {
|
||||||
if (this.props.onScroll) {
|
if (this.props.onScroll) {
|
||||||
this.props.onScroll(e)
|
this.props.onScroll(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePasteUrl (editor, pastedTxt) {
|
handlePasteUrl(editor, pastedTxt) {
|
||||||
let taggedUrl = `<${pastedTxt}>`
|
let taggedUrl = `<${pastedTxt}>`
|
||||||
let urlToFetch = pastedTxt
|
let urlToFetch = pastedTxt
|
||||||
let titleMark = ''
|
let titleMark = ''
|
||||||
@@ -1071,16 +1134,16 @@ export default class CodeEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePasteHtml (editor, pastedHtml) {
|
handlePasteHtml(editor, pastedHtml) {
|
||||||
const markdown = this.turndownService.turndown(pastedHtml)
|
const markdown = this.turndownService.turndown(pastedHtml)
|
||||||
editor.replaceSelection(markdown)
|
editor.replaceSelection(markdown)
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePasteText (editor, pastedTxt) {
|
handlePasteText(editor, pastedTxt) {
|
||||||
editor.replaceSelection(pastedTxt)
|
editor.replaceSelection(pastedTxt)
|
||||||
}
|
}
|
||||||
|
|
||||||
mapNormalResponse (response, pastedTxt) {
|
mapNormalResponse(response, pastedTxt) {
|
||||||
return this.decodeResponse(response).then(body => {
|
return this.decodeResponse(response).then(body => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
@@ -1088,10 +1151,12 @@ export default class CodeEditor extends React.Component {
|
|||||||
body,
|
body,
|
||||||
'text/html'
|
'text/html'
|
||||||
)
|
)
|
||||||
const escapePipe = (str) => {
|
const escapePipe = str => {
|
||||||
return str.replace('|', '\\|')
|
return str.replace('|', '\\|')
|
||||||
}
|
}
|
||||||
const linkWithTitle = `[${escapePipe(parsedBody.title)}](${pastedTxt})`
|
const linkWithTitle = `[${escapePipe(
|
||||||
|
parsedBody.title
|
||||||
|
)}](${pastedTxt})`
|
||||||
resolve(linkWithTitle)
|
resolve(linkWithTitle)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e)
|
reject(e)
|
||||||
@@ -1100,7 +1165,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
initialHighlighting () {
|
initialHighlighting() {
|
||||||
if (this.editor.options.linesHighlighted == null) {
|
if (this.editor.options.linesHighlighted == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1114,16 +1179,20 @@ export default class CodeEditor extends React.Component {
|
|||||||
// make sure that we skip the invalid lines althrough this case should not be happened.
|
// make sure that we skip the invalid lines althrough this case should not be happened.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
this.editor.addLineClass(lineNumber, 'text', 'CodeMirror-activeline-background')
|
this.editor.addLineClass(
|
||||||
|
lineNumber,
|
||||||
|
'text',
|
||||||
|
'CodeMirror-activeline-background'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
restartHighlighting () {
|
restartHighlighting() {
|
||||||
this.editor.options.linesHighlighted = this.props.linesHighlighted
|
this.editor.options.linesHighlighted = this.props.linesHighlighted
|
||||||
this.initialHighlighting()
|
this.initialHighlighting()
|
||||||
}
|
}
|
||||||
|
|
||||||
mapImageResponse (response, pastedTxt) {
|
mapImageResponse(response, pastedTxt) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const url = response.url
|
const url = response.url
|
||||||
@@ -1136,7 +1205,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
decodeResponse (response) {
|
decodeResponse(response) {
|
||||||
const headers = response.headers
|
const headers = response.headers
|
||||||
const _charset = headers.has('content-type')
|
const _charset = headers.has('content-type')
|
||||||
? this.extractContentTypeCharset(headers.get('content-type'))
|
? this.extractContentTypeCharset(headers.get('content-type'))
|
||||||
@@ -1144,10 +1213,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
return response.arrayBuffer().then(buff => {
|
return response.arrayBuffer().then(buff => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const charset = _charset !== undefined &&
|
const charset =
|
||||||
iconv.encodingExists(_charset)
|
_charset !== undefined && iconv.encodingExists(_charset)
|
||||||
? _charset
|
? _charset
|
||||||
: 'utf-8'
|
: 'utf-8'
|
||||||
resolve(iconv.decode(Buffer.from(buff), charset).toString())
|
resolve(iconv.decode(Buffer.from(buff), charset).toString())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e)
|
reject(e)
|
||||||
@@ -1156,50 +1225,49 @@ export default class CodeEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
extractContentTypeCharset (contentType) {
|
extractContentTypeCharset(contentType) {
|
||||||
return contentType
|
return contentType
|
||||||
.split(';')
|
.split(';')
|
||||||
.filter(str => {
|
.filter(str => {
|
||||||
return str.trim().toLowerCase().startsWith('charset')
|
return str
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.startsWith('charset')
|
||||||
})
|
})
|
||||||
.map(str => {
|
.map(str => {
|
||||||
return str.replace(/['"]/g, '').split('=')[1]
|
return str.replace(/['"]/g, '').split('=')[1]
|
||||||
})[0]
|
})[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const {
|
const { className, fontSize } = this.props
|
||||||
className,
|
|
||||||
fontSize
|
|
||||||
} = this.props
|
|
||||||
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
|
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
|
||||||
const width = this.props.width
|
const width = this.props.width
|
||||||
return (<
|
return (
|
||||||
div className={
|
<div
|
||||||
className == null ? 'CodeEditor' : `CodeEditor ${className}`
|
className={className == null ? 'CodeEditor' : `CodeEditor ${className}`}
|
||||||
}
|
ref='root'
|
||||||
ref='root'
|
tabIndex='-1'
|
||||||
tabIndex='-1'
|
style={{
|
||||||
style={{
|
fontFamily,
|
||||||
fontFamily,
|
fontSize: fontSize,
|
||||||
fontSize: fontSize,
|
width: width
|
||||||
width: width
|
}}
|
||||||
}}
|
onDrop={e => this.handleDropImage(e)}
|
||||||
onDrop={
|
|
||||||
e => this.handleDropImage(e)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
createSpellCheckPanel () {
|
createSpellCheckPanel() {
|
||||||
const panel = document.createElement('div')
|
const panel = document.createElement('div')
|
||||||
panel.className = 'panel bottom'
|
panel.className = 'panel bottom'
|
||||||
panel.id = 'editor-bottom-panel'
|
panel.id = 'editor-bottom-panel'
|
||||||
const dropdown = document.createElement('select')
|
const dropdown = document.createElement('select')
|
||||||
dropdown.title = 'Spellcheck'
|
dropdown.title = 'Spellcheck'
|
||||||
dropdown.className = styles['spellcheck-select']
|
dropdown.className = styles['spellcheck-select']
|
||||||
dropdown.addEventListener('change', (e) => spellcheck.setLanguage(this.editor, dropdown.value))
|
dropdown.addEventListener('change', e =>
|
||||||
|
spellcheck.setLanguage(this.editor, dropdown.value)
|
||||||
|
)
|
||||||
const options = spellcheck.getAvailableDictionaries()
|
const options = spellcheck.getAvailableDictionaries()
|
||||||
for (const op of options) {
|
for (const op of options) {
|
||||||
const option = document.createElement('option')
|
const option = document.createElement('option')
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import styles from './ColorPicker.styl'
|
|||||||
const componentHeight = 330
|
const componentHeight = 330
|
||||||
|
|
||||||
class ColorPicker extends React.Component {
|
class ColorPicker extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -18,21 +18,21 @@ class ColorPicker extends React.Component {
|
|||||||
this.handleConfirm = this.handleConfirm.bind(this)
|
this.handleConfirm = this.handleConfirm.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.onColorChange(nextProps.color)
|
this.onColorChange(nextProps.color)
|
||||||
}
|
}
|
||||||
|
|
||||||
onColorChange (color) {
|
onColorChange(color) {
|
||||||
this.setState({
|
this.setState({
|
||||||
color
|
color
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleConfirm () {
|
handleConfirm() {
|
||||||
this.props.onConfirm(this.state.color)
|
this.props.onConfirm(this.state.color)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { onReset, onCancel, targetRect } = this.props
|
const { onReset, onCancel, targetRect } = this.props
|
||||||
const { color } = this.state
|
const { color } = this.state
|
||||||
|
|
||||||
@@ -44,13 +44,22 @@ class ColorPicker extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div styleName='colorPicker' style={{top: `${alignY}px`, left: `${alignX}px`}}>
|
<div
|
||||||
|
styleName='colorPicker'
|
||||||
|
style={{ top: `${alignY}px`, left: `${alignX}px` }}
|
||||||
|
>
|
||||||
<div styleName='cover' onClick={onCancel} />
|
<div styleName='cover' onClick={onCancel} />
|
||||||
<SketchPicker color={color} onChange={this.onColorChange} />
|
<SketchPicker color={color} onChange={this.onColorChange} />
|
||||||
<div styleName='footer'>
|
<div styleName='footer'>
|
||||||
<button styleName='btn-reset' onClick={onReset}>Reset</button>
|
<button styleName='btn-reset' onClick={onReset}>
|
||||||
<button styleName='btn-cancel' onClick={onCancel}>Cancel</button>
|
Reset
|
||||||
<button styleName='btn-confirm' onClick={this.handleConfirm}>Confirm</button>
|
</button>
|
||||||
|
<button styleName='btn-cancel' onClick={onCancel}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button styleName='btn-confirm' onClick={this.handleConfirm}>
|
||||||
|
Confirm
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import ConfigManager from 'browser/main/lib/ConfigManager'
|
|||||||
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
||||||
|
|
||||||
class MarkdownEditor extends React.Component {
|
class MarkdownEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
// char codes for ctrl + w
|
// char codes for ctrl + w
|
||||||
@@ -20,7 +20,10 @@ class MarkdownEditor extends React.Component {
|
|||||||
this.supportMdSelectionBold = [16, 17, 186]
|
this.supportMdSelectionBold = [16, 17, 186]
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
status: props.config.editor.switchPreview === 'RIGHTCLICK' ? props.config.editor.delfaultStatus : 'CODE',
|
status:
|
||||||
|
props.config.editor.switchPreview === 'RIGHTCLICK'
|
||||||
|
? props.config.editor.delfaultStatus
|
||||||
|
: 'CODE',
|
||||||
renderValue: props.value,
|
renderValue: props.value,
|
||||||
keyPressed: new Set(),
|
keyPressed: new Set(),
|
||||||
isLocked: props.isLocked
|
isLocked: props.isLocked
|
||||||
@@ -29,133 +32,153 @@ class MarkdownEditor extends React.Component {
|
|||||||
this.lockEditorCode = () => this.handleLockEditor()
|
this.lockEditorCode = () => this.handleLockEditor()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
this.value = this.refs.code.value
|
this.value = this.refs.code.value
|
||||||
eventEmitter.on('editor:lock', this.lockEditorCode)
|
eventEmitter.on('editor:lock', this.lockEditorCode)
|
||||||
eventEmitter.on('editor:focus', this.focusEditor.bind(this))
|
eventEmitter.on('editor:focus', this.focusEditor.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
componentDidUpdate() {
|
||||||
this.value = this.refs.code.value
|
this.value = this.refs.code.value
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (props) {
|
componentWillReceiveProps(props) {
|
||||||
if (props.value !== this.props.value) {
|
if (props.value !== this.props.value) {
|
||||||
this.queueRendering(props.value)
|
this.queueRendering(props.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount() {
|
||||||
this.cancelQueue()
|
this.cancelQueue()
|
||||||
eventEmitter.off('editor:lock', this.lockEditorCode)
|
eventEmitter.off('editor:lock', this.lockEditorCode)
|
||||||
eventEmitter.off('editor:focus', this.focusEditor.bind(this))
|
eventEmitter.off('editor:focus', this.focusEditor.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
focusEditor () {
|
focusEditor() {
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'CODE'
|
{
|
||||||
}, () => {
|
status: 'CODE'
|
||||||
this.refs.code.focus()
|
},
|
||||||
})
|
() => {
|
||||||
|
this.refs.code.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
queueRendering (value) {
|
queueRendering(value) {
|
||||||
clearTimeout(this.renderTimer)
|
clearTimeout(this.renderTimer)
|
||||||
this.renderTimer = setTimeout(() => {
|
this.renderTimer = setTimeout(() => {
|
||||||
this.renderPreview(value)
|
this.renderPreview(value)
|
||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelQueue () {
|
cancelQueue() {
|
||||||
clearTimeout(this.renderTimer)
|
clearTimeout(this.renderTimer)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPreview (value) {
|
renderPreview(value) {
|
||||||
this.setState({
|
this.setState({
|
||||||
renderValue: value
|
renderValue: value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue (value) {
|
setValue(value) {
|
||||||
this.refs.code.setValue(value)
|
this.refs.code.setValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange (e) {
|
handleChange(e) {
|
||||||
this.value = this.refs.code.value
|
this.value = this.refs.code.value
|
||||||
this.props.onChange(e)
|
this.props.onChange(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContextMenu (e) {
|
handleContextMenu(e) {
|
||||||
if (this.state.isLocked) return
|
if (this.state.isLocked) return
|
||||||
const { config } = this.props
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'RIGHTCLICK') {
|
if (config.editor.switchPreview === 'RIGHTCLICK') {
|
||||||
const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW'
|
const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW'
|
||||||
this.setState({
|
this.setState(
|
||||||
status: newStatus
|
{
|
||||||
}, () => {
|
status: newStatus
|
||||||
if (newStatus === 'CODE') {
|
},
|
||||||
this.refs.code.focus()
|
() => {
|
||||||
} else {
|
if (newStatus === 'CODE') {
|
||||||
this.refs.preview.focus()
|
this.refs.code.focus()
|
||||||
}
|
} else {
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
this.refs.preview.focus()
|
||||||
|
}
|
||||||
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
|
|
||||||
const newConfig = Object.assign({}, config)
|
const newConfig = Object.assign({}, config)
|
||||||
newConfig.editor.delfaultStatus = newStatus
|
newConfig.editor.delfaultStatus = newStatus
|
||||||
ConfigManager.set(newConfig)
|
ConfigManager.set(newConfig)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBlur (e) {
|
handleBlur(e) {
|
||||||
if (this.state.isLocked) return
|
if (this.state.isLocked) return
|
||||||
this.setState({ keyPressed: new Set() })
|
this.setState({ keyPressed: new Set() })
|
||||||
const { config } = this.props
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'BLUR' ||
|
if (
|
||||||
(config.editor.switchPreview === 'DBL_CLICK' && this.state.status === 'CODE')
|
config.editor.switchPreview === 'BLUR' ||
|
||||||
|
(config.editor.switchPreview === 'DBL_CLICK' &&
|
||||||
|
this.state.status === 'CODE')
|
||||||
) {
|
) {
|
||||||
const cursorPosition = this.refs.code.editor.getCursor()
|
const cursorPosition = this.refs.code.editor.getCursor()
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'PREVIEW'
|
{
|
||||||
}, () => {
|
status: 'PREVIEW'
|
||||||
this.refs.preview.focus()
|
},
|
||||||
this.refs.preview.scrollToRow(cursorPosition.line)
|
() => {
|
||||||
})
|
this.refs.preview.focus()
|
||||||
|
this.refs.preview.scrollToRow(cursorPosition.line)
|
||||||
|
}
|
||||||
|
)
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDoubleClick (e) {
|
handleDoubleClick(e) {
|
||||||
if (this.state.isLocked) return
|
if (this.state.isLocked) return
|
||||||
this.setState({keyPressed: new Set()})
|
this.setState({ keyPressed: new Set() })
|
||||||
const { config } = this.props
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'DBL_CLICK') {
|
if (config.editor.switchPreview === 'DBL_CLICK') {
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'CODE'
|
{
|
||||||
}, () => {
|
status: 'CODE'
|
||||||
this.refs.code.focus()
|
},
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
() => {
|
||||||
})
|
this.refs.code.focus()
|
||||||
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePreviewMouseDown (e) {
|
handlePreviewMouseDown(e) {
|
||||||
this.previewMouseDownedAt = new Date()
|
this.previewMouseDownedAt = new Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePreviewMouseUp (e) {
|
handlePreviewMouseUp(e) {
|
||||||
const { config } = this.props
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'BLUR' && new Date() - this.previewMouseDownedAt < 200) {
|
if (
|
||||||
this.setState({
|
config.editor.switchPreview === 'BLUR' &&
|
||||||
status: 'CODE'
|
new Date() - this.previewMouseDownedAt < 200
|
||||||
}, () => {
|
) {
|
||||||
this.refs.code.focus()
|
this.setState(
|
||||||
})
|
{
|
||||||
|
status: 'CODE'
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.refs.code.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCheckboxClick (e) {
|
handleCheckboxClick(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
const idMatch = /checkbox-([0-9]+)/
|
const idMatch = /checkbox-([0-9]+)/
|
||||||
@@ -164,9 +187,9 @@ class MarkdownEditor extends React.Component {
|
|||||||
const checkReplace = /\[x]/i
|
const checkReplace = /\[x]/i
|
||||||
const uncheckReplace = /\[ ]/
|
const uncheckReplace = /\[ ]/
|
||||||
if (idMatch.test(e.target.getAttribute('id'))) {
|
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||||
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
const lineIndex =
|
||||||
const lines = this.refs.code.value
|
parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||||
.split('\n')
|
const lines = this.refs.code.value.split('\n')
|
||||||
|
|
||||||
const targetLine = lines[lineIndex]
|
const targetLine = lines[lineIndex]
|
||||||
let newLine = targetLine
|
let newLine = targetLine
|
||||||
@@ -181,45 +204,56 @@ class MarkdownEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus() {
|
||||||
if (this.state.status === 'PREVIEW') {
|
if (this.state.status === 'PREVIEW') {
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'CODE'
|
{
|
||||||
}, () => {
|
status: 'CODE'
|
||||||
this.refs.code.focus()
|
},
|
||||||
})
|
() => {
|
||||||
|
this.refs.code.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
this.refs.code.focus()
|
this.refs.code.focus()
|
||||||
}
|
}
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
reload () {
|
reload() {
|
||||||
this.refs.code.reload()
|
this.refs.code.reload()
|
||||||
this.cancelQueue()
|
this.cancelQueue()
|
||||||
this.renderPreview(this.props.value)
|
this.renderPreview(this.props.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown (e) {
|
handleKeyDown(e) {
|
||||||
const { config } = this.props
|
const { config } = this.props
|
||||||
if (this.state.status !== 'CODE') return false
|
if (this.state.status !== 'CODE') return false
|
||||||
const keyPressed = this.state.keyPressed
|
const keyPressed = this.state.keyPressed
|
||||||
keyPressed.add(e.keyCode)
|
keyPressed.add(e.keyCode)
|
||||||
this.setState({ keyPressed })
|
this.setState({ keyPressed })
|
||||||
const isNoteHandlerKey = (el) => { return keyPressed.has(el) }
|
const isNoteHandlerKey = el => {
|
||||||
|
return keyPressed.has(el)
|
||||||
|
}
|
||||||
// These conditions are for ctrl-e and ctrl-w
|
// These conditions are for ctrl-e and ctrl-w
|
||||||
if (keyPressed.size === this.escapeFromEditor.length &&
|
if (
|
||||||
!this.state.isLocked && this.state.status === 'CODE' &&
|
keyPressed.size === this.escapeFromEditor.length &&
|
||||||
this.escapeFromEditor.every(isNoteHandlerKey)) {
|
!this.state.isLocked &&
|
||||||
|
this.state.status === 'CODE' &&
|
||||||
|
this.escapeFromEditor.every(isNoteHandlerKey)
|
||||||
|
) {
|
||||||
this.handleContextMenu()
|
this.handleContextMenu()
|
||||||
if (config.editor.switchPreview === 'BLUR') document.activeElement.blur()
|
if (config.editor.switchPreview === 'BLUR') document.activeElement.blur()
|
||||||
}
|
}
|
||||||
if (keyPressed.size === this.supportMdSelectionBold.length && this.supportMdSelectionBold.every(isNoteHandlerKey)) {
|
if (
|
||||||
|
keyPressed.size === this.supportMdSelectionBold.length &&
|
||||||
|
this.supportMdSelectionBold.every(isNoteHandlerKey)
|
||||||
|
) {
|
||||||
this.addMdAroundWord('**')
|
this.addMdAroundWord('**')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addMdAroundWord (mdElement) {
|
addMdAroundWord(mdElement) {
|
||||||
if (this.refs.code.editor.getSelection()) {
|
if (this.refs.code.editor.getSelection()) {
|
||||||
return this.addMdAroundSelection(mdElement)
|
return this.addMdAroundSelection(mdElement)
|
||||||
}
|
}
|
||||||
@@ -227,47 +261,63 @@ class MarkdownEditor extends React.Component {
|
|||||||
const word = this.refs.code.editor.findWordAt(currentCaret)
|
const word = this.refs.code.editor.findWordAt(currentCaret)
|
||||||
const cmDoc = this.refs.code.editor.getDoc()
|
const cmDoc = this.refs.code.editor.getDoc()
|
||||||
cmDoc.replaceRange(mdElement, word.anchor)
|
cmDoc.replaceRange(mdElement, word.anchor)
|
||||||
cmDoc.replaceRange(mdElement, { line: word.head.line, ch: word.head.ch + mdElement.length })
|
cmDoc.replaceRange(mdElement, {
|
||||||
}
|
line: word.head.line,
|
||||||
|
ch: word.head.ch + mdElement.length
|
||||||
addMdAroundSelection (mdElement) {
|
|
||||||
this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDropImage (dropEvent) {
|
|
||||||
dropEvent.preventDefault()
|
|
||||||
const { storageKey, noteKey } = this.props
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
status: 'CODE'
|
|
||||||
}, () => {
|
|
||||||
this.refs.code.focus()
|
|
||||||
|
|
||||||
this.refs.code.editor.execCommand('goDocEnd')
|
|
||||||
this.refs.code.editor.execCommand('goLineEnd')
|
|
||||||
this.refs.code.editor.execCommand('newlineAndIndent')
|
|
||||||
|
|
||||||
attachmentManagement.handleAttachmentDrop(
|
|
||||||
this.refs.code,
|
|
||||||
storageKey,
|
|
||||||
noteKey,
|
|
||||||
dropEvent
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyUp (e) {
|
addMdAroundSelection(mdElement) {
|
||||||
|
this.refs.code.editor.replaceSelection(
|
||||||
|
`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDropImage(dropEvent) {
|
||||||
|
dropEvent.preventDefault()
|
||||||
|
const { storageKey, noteKey } = this.props
|
||||||
|
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
status: 'CODE'
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.refs.code.focus()
|
||||||
|
|
||||||
|
this.refs.code.editor.execCommand('goDocEnd')
|
||||||
|
this.refs.code.editor.execCommand('goLineEnd')
|
||||||
|
this.refs.code.editor.execCommand('newlineAndIndent')
|
||||||
|
|
||||||
|
attachmentManagement.handleAttachmentDrop(
|
||||||
|
this.refs.code,
|
||||||
|
storageKey,
|
||||||
|
noteKey,
|
||||||
|
dropEvent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyUp(e) {
|
||||||
const keyPressed = this.state.keyPressed
|
const keyPressed = this.state.keyPressed
|
||||||
keyPressed.delete(e.keyCode)
|
keyPressed.delete(e.keyCode)
|
||||||
this.setState({ keyPressed })
|
this.setState({ keyPressed })
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLockEditor () {
|
handleLockEditor() {
|
||||||
this.setState({ isLocked: !this.state.isLocked })
|
this.setState({ isLocked: !this.state.isLocked })
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const {className, value, config, storageKey, noteKey, linesHighlighted, RTL} = this.props
|
const {
|
||||||
|
className,
|
||||||
|
value,
|
||||||
|
config,
|
||||||
|
storageKey,
|
||||||
|
noteKey,
|
||||||
|
linesHighlighted,
|
||||||
|
RTL
|
||||||
|
} = this.props
|
||||||
|
|
||||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
@@ -275,23 +325,24 @@ class MarkdownEditor extends React.Component {
|
|||||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
|
|
||||||
const previewStyle = {}
|
const previewStyle = {}
|
||||||
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
|
if (this.props.ignorePreviewPointerEvents)
|
||||||
|
previewStyle.pointerEvents = 'none'
|
||||||
|
|
||||||
const storage = findStorage(storageKey)
|
const storage = findStorage(storageKey)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className == null
|
<div
|
||||||
? 'MarkdownEditor'
|
className={
|
||||||
: `MarkdownEditor ${className}`
|
className == null ? 'MarkdownEditor' : `MarkdownEditor ${className}`
|
||||||
}
|
}
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
onContextMenu={e => this.handleContextMenu(e)}
|
||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
onKeyDown={e => this.handleKeyDown(e)}
|
||||||
onKeyUp={(e) => this.handleKeyUp(e)}
|
onKeyUp={e => this.handleKeyUp(e)}
|
||||||
>
|
>
|
||||||
<CodeEditor styleName={this.state.status === 'CODE'
|
<CodeEditor
|
||||||
? 'codeEditor'
|
styleName={
|
||||||
: 'codeEditor--hide'
|
this.state.status === 'CODE' ? 'codeEditor' : 'codeEditor--hide'
|
||||||
}
|
}
|
||||||
ref='code'
|
ref='code'
|
||||||
mode='Boost Flavored Markdown'
|
mode='Boost Flavored Markdown'
|
||||||
@@ -315,8 +366,8 @@ class MarkdownEditor extends React.Component {
|
|||||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||||
enableTableEditor={config.editor.enableTableEditor}
|
enableTableEditor={config.editor.enableTableEditor}
|
||||||
linesHighlighted={linesHighlighted}
|
linesHighlighted={linesHighlighted}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={e => this.handleChange(e)}
|
||||||
onBlur={(e) => this.handleBlur(e)}
|
onBlur={e => this.handleBlur(e)}
|
||||||
spellCheck={config.editor.spellcheck}
|
spellCheck={config.editor.spellcheck}
|
||||||
enableSmartPaste={config.editor.enableSmartPaste}
|
enableSmartPaste={config.editor.enableSmartPaste}
|
||||||
hotkey={config.hotkey}
|
hotkey={config.hotkey}
|
||||||
@@ -327,9 +378,9 @@ class MarkdownEditor extends React.Component {
|
|||||||
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
||||||
RTL={RTL}
|
RTL={RTL}
|
||||||
/>
|
/>
|
||||||
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
|
<MarkdownPreview
|
||||||
? 'preview'
|
styleName={
|
||||||
: 'preview--hide'
|
this.state.status === 'PREVIEW' ? 'preview' : 'preview--hide'
|
||||||
}
|
}
|
||||||
style={previewStyle}
|
style={previewStyle}
|
||||||
theme={config.ui.theme}
|
theme={config.ui.theme}
|
||||||
@@ -347,20 +398,20 @@ class MarkdownEditor extends React.Component {
|
|||||||
sanitize={config.preview.sanitize}
|
sanitize={config.preview.sanitize}
|
||||||
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
|
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
|
||||||
ref='preview'
|
ref='preview'
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
onContextMenu={e => this.handleContextMenu(e)}
|
||||||
onDoubleClick={(e) => this.handleDoubleClick(e)}
|
onDoubleClick={e => this.handleDoubleClick(e)}
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
value={this.state.renderValue}
|
value={this.state.renderValue}
|
||||||
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
|
onMouseUp={e => this.handlePreviewMouseUp(e)}
|
||||||
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
|
onMouseDown={e => this.handlePreviewMouseDown(e)}
|
||||||
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
onCheckboxClick={e => this.handleCheckboxClick(e)}
|
||||||
showCopyNotification={config.ui.showCopyNotification}
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
storagePath={storage.path}
|
storagePath={storage.path}
|
||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
customCSS={config.preview.customCSS}
|
customCSS={config.preview.customCSS}
|
||||||
allowCustomCSS={config.preview.allowCustomCSS}
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||||
onDrop={(e) => this.handleDropImage(e)}
|
onDrop={e => this.handleDropImage(e)}
|
||||||
RTL={RTL}
|
RTL={RTL}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ import i18n from 'browser/lib/i18n'
|
|||||||
|
|
||||||
const { remote, shell } = require('electron')
|
const { remote, shell } = require('electron')
|
||||||
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
|
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
|
||||||
const buildMarkdownPreviewContextMenu = require('browser/lib/contextMenuBuilder').buildMarkdownPreviewContextMenu
|
const buildMarkdownPreviewContextMenu = require('browser/lib/contextMenuBuilder')
|
||||||
|
.buildMarkdownPreviewContextMenu
|
||||||
|
|
||||||
const { app } = remote
|
const { app } = remote
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
@@ -56,7 +57,7 @@ const CSS_FILES = [
|
|||||||
* @param {String} [opts.customCSS] Will be added to bottom, only if `opts.allowCustomCSS` is truthy
|
* @param {String} [opts.customCSS] Will be added to bottom, only if `opts.allowCustomCSS` is truthy
|
||||||
* @returns {String}
|
* @returns {String}
|
||||||
*/
|
*/
|
||||||
function buildStyle (opts) {
|
function buildStyle(opts) {
|
||||||
const {
|
const {
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize,
|
fontSize,
|
||||||
@@ -104,11 +105,14 @@ body {
|
|||||||
font-family: '${fontFamily.join("','")}';
|
font-family: '${fontFamily.join("','")}';
|
||||||
font-size: ${fontSize}px;
|
font-size: ${fontSize}px;
|
||||||
|
|
||||||
${scrollPastEnd ? `
|
${
|
||||||
|
scrollPastEnd
|
||||||
|
? `
|
||||||
padding-bottom: 90vh;
|
padding-bottom: 90vh;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
`
|
`
|
||||||
: ''}
|
: ''
|
||||||
|
}
|
||||||
${RTL ? 'direction: rtl;' : ''}
|
${RTL ? 'direction: rtl;' : ''}
|
||||||
${RTL ? 'text-align: right;' : ''}
|
${RTL ? 'text-align: right;' : ''}
|
||||||
}
|
}
|
||||||
@@ -225,7 +229,7 @@ const defaultCodeBlockFontFamily = [
|
|||||||
|
|
||||||
// return the line number of the line that used to generate the specified element
|
// return the line number of the line that used to generate the specified element
|
||||||
// return -1 if the line is not found
|
// return -1 if the line is not found
|
||||||
function getSourceLineNumberByElement (element) {
|
function getSourceLineNumberByElement(element) {
|
||||||
let isHasLineNumber = element.dataset.line !== undefined
|
let isHasLineNumber = element.dataset.line !== undefined
|
||||||
let parent = element
|
let parent = element
|
||||||
while (!isHasLineNumber && parent.parentElement !== null) {
|
while (!isHasLineNumber && parent.parentElement !== null) {
|
||||||
@@ -236,7 +240,7 @@ function getSourceLineNumberByElement (element) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class MarkdownPreview extends React.Component {
|
export default class MarkdownPreview extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.contextMenuHandler = e => this.handleContextMenu(e)
|
this.contextMenuHandler = e => this.handleContextMenu(e)
|
||||||
@@ -260,7 +264,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.initMarkdown()
|
this.initMarkdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
initMarkdown () {
|
initMarkdown() {
|
||||||
const { smartQuotes, sanitize, breaks } = this.props
|
const { smartQuotes, sanitize, breaks } = this.props
|
||||||
this.markdown = new Markdown({
|
this.markdown = new Markdown({
|
||||||
typographer: smartQuotes,
|
typographer: smartQuotes,
|
||||||
@@ -269,17 +273,17 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCheckboxClick (e) {
|
handleCheckboxClick(e) {
|
||||||
this.props.onCheckboxClick(e)
|
this.props.onCheckboxClick(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScroll (e) {
|
handleScroll(e) {
|
||||||
if (this.props.onScroll) {
|
if (this.props.onScroll) {
|
||||||
this.props.onScroll(e)
|
this.props.onScroll(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContextMenu (event) {
|
handleContextMenu(event) {
|
||||||
const menu = buildMarkdownPreviewContextMenu(this, event)
|
const menu = buildMarkdownPreviewContextMenu(this, event)
|
||||||
const switchPreview = ConfigManager.get().editor.switchPreview
|
const switchPreview = ConfigManager.get().editor.switchPreview
|
||||||
if (menu != null && switchPreview !== 'RIGHTCLICK') {
|
if (menu != null && switchPreview !== 'RIGHTCLICK') {
|
||||||
@@ -289,17 +293,21 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDoubleClick (e) {
|
handleDoubleClick(e) {
|
||||||
if (this.props.onDoubleClick != null) this.props.onDoubleClick(e)
|
if (this.props.onDoubleClick != null) this.props.onDoubleClick(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseDown (e) {
|
handleMouseDown(e) {
|
||||||
const config = ConfigManager.get()
|
const config = ConfigManager.get()
|
||||||
const clickElement = e.target
|
const clickElement = e.target
|
||||||
const targetTag = clickElement.tagName // The direct parent HTML of where was clicked ie "BODY" or "DIV"
|
const targetTag = clickElement.tagName // The direct parent HTML of where was clicked ie "BODY" or "DIV"
|
||||||
const lineNumber = getSourceLineNumberByElement(clickElement) // Line location of element clicked.
|
const lineNumber = getSourceLineNumberByElement(clickElement) // Line location of element clicked.
|
||||||
|
|
||||||
if (config.editor.switchPreview === 'RIGHTCLICK' && e.buttons === 2 && config.editor.type === 'SPLIT') {
|
if (
|
||||||
|
config.editor.switchPreview === 'RIGHTCLICK' &&
|
||||||
|
e.buttons === 2 &&
|
||||||
|
config.editor.type === 'SPLIT'
|
||||||
|
) {
|
||||||
eventEmitter.emit('topbar:togglemodebutton', 'CODE')
|
eventEmitter.emit('topbar:togglemodebutton', 'CODE')
|
||||||
}
|
}
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
@@ -315,10 +323,11 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.onMouseDown != null && targetTag === 'BODY') this.props.onMouseDown(e)
|
if (this.props.onMouseDown != null && targetTag === 'BODY')
|
||||||
|
this.props.onMouseDown(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp (e) {
|
handleMouseUp(e) {
|
||||||
if (!this.props.onMouseUp) return
|
if (!this.props.onMouseUp) return
|
||||||
if (e.target != null && e.target.tagName === 'A') {
|
if (e.target != null && e.target.tagName === 'A') {
|
||||||
return null
|
return null
|
||||||
@@ -326,15 +335,15 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
if (this.props.onMouseUp != null) this.props.onMouseUp(e)
|
if (this.props.onMouseUp != null) this.props.onMouseUp(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsText () {
|
handleSaveAsText() {
|
||||||
this.exportAsDocument('txt')
|
this.exportAsDocument('txt')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsMd () {
|
handleSaveAsMd() {
|
||||||
this.exportAsDocument('md')
|
this.exportAsDocument('md')
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlContentFormatter (noteContent, exportTasks, targetDir) {
|
htmlContentFormatter(noteContent, exportTasks, targetDir) {
|
||||||
const {
|
const {
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize,
|
fontSize,
|
||||||
@@ -360,10 +369,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
RTL
|
RTL
|
||||||
})
|
})
|
||||||
let body = this.refs.root.contentWindow.document.body.innerHTML
|
let body = this.refs.root.contentWindow.document.body.innerHTML
|
||||||
body = attachmentManagement.fixLocalURLS(
|
body = attachmentManagement.fixLocalURLS(body, this.props.storagePath)
|
||||||
body,
|
|
||||||
this.props.storagePath
|
|
||||||
)
|
|
||||||
const files = [this.getCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
const files = [this.getCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
if (global.process.platform === 'win32') {
|
if (global.process.platform === 'win32') {
|
||||||
@@ -394,14 +400,24 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
</html>`
|
</html>`
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsHtml () {
|
handleSaveAsHtml() {
|
||||||
this.exportAsDocument('html', (noteContent, exportTasks, targetDir) => Promise.resolve(this.htmlContentFormatter(noteContent, exportTasks, targetDir)))
|
this.exportAsDocument('html', (noteContent, exportTasks, targetDir) =>
|
||||||
|
Promise.resolve(
|
||||||
|
this.htmlContentFormatter(noteContent, exportTasks, targetDir)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsPdf () {
|
handleSaveAsPdf() {
|
||||||
this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => {
|
this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => {
|
||||||
const printout = new remote.BrowserWindow({show: false, webPreferences: {webSecurity: false, javascript: false}})
|
const printout = new remote.BrowserWindow({
|
||||||
printout.loadURL('data:text/html;charset=UTF-8,' + this.htmlContentFormatter(noteContent, exportTasks, targetDir))
|
show: false,
|
||||||
|
webPreferences: { webSecurity: false, javascript: false }
|
||||||
|
})
|
||||||
|
printout.loadURL(
|
||||||
|
'data:text/html;charset=UTF-8,' +
|
||||||
|
this.htmlContentFormatter(noteContent, exportTasks, targetDir)
|
||||||
|
)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
printout.webContents.on('did-finish-load', () => {
|
printout.webContents.on('did-finish-load', () => {
|
||||||
printout.webContents.printToPDF({}, (err, data) => {
|
printout.webContents.printToPDF({}, (err, data) => {
|
||||||
@@ -414,11 +430,11 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePrint () {
|
handlePrint() {
|
||||||
this.refs.root.contentWindow.print()
|
this.refs.root.contentWindow.print()
|
||||||
}
|
}
|
||||||
|
|
||||||
exportAsDocument (fileType, contentFormatter) {
|
exportAsDocument(fileType, contentFormatter) {
|
||||||
const options = {
|
const options = {
|
||||||
filters: [{ name: 'Documents', extensions: [fileType] }],
|
filters: [{ name: 'Documents', extensions: [fileType] }],
|
||||||
properties: ['openFile', 'createDirectory']
|
properties: ['openFile', 'createDirectory']
|
||||||
@@ -449,7 +465,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fixDecodedURI (node) {
|
fixDecodedURI(node) {
|
||||||
if (
|
if (
|
||||||
node &&
|
node &&
|
||||||
node.children.length === 1 &&
|
node.children.length === 1 &&
|
||||||
@@ -466,17 +482,18 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
* @param {string[]} splitWithCodeTag Array of HTML strings separated by three ```
|
* @param {string[]} splitWithCodeTag Array of HTML strings separated by three ```
|
||||||
* @returns {string} HTML in which special characters between three ``` have been converted
|
* @returns {string} HTML in which special characters between three ``` have been converted
|
||||||
*/
|
*/
|
||||||
escapeHtmlCharactersInCodeTag (splitWithCodeTag) {
|
escapeHtmlCharactersInCodeTag(splitWithCodeTag) {
|
||||||
for (let index = 0; index < splitWithCodeTag.length; index++) {
|
for (let index = 0; index < splitWithCodeTag.length; index++) {
|
||||||
const codeTagRequired = (splitWithCodeTag[index] !== '\`\`\`' && index < splitWithCodeTag.length - 1)
|
const codeTagRequired =
|
||||||
|
splitWithCodeTag[index] !== '```' && index < splitWithCodeTag.length - 1
|
||||||
if (codeTagRequired) {
|
if (codeTagRequired) {
|
||||||
splitWithCodeTag.splice((index + 1), 0, '\`\`\`')
|
splitWithCodeTag.splice(index + 1, 0, '```')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let inCodeTag = false
|
let inCodeTag = false
|
||||||
let result = ''
|
let result = ''
|
||||||
for (let content of splitWithCodeTag) {
|
for (let content of splitWithCodeTag) {
|
||||||
if (content === '\`\`\`') {
|
if (content === '```') {
|
||||||
inCodeTag = !inCodeTag
|
inCodeTag = !inCodeTag
|
||||||
} else if (inCodeTag) {
|
} else if (inCodeTag) {
|
||||||
content = escapeHtmlCharacters(content)
|
content = escapeHtmlCharacters(content)
|
||||||
@@ -486,13 +503,15 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
getScrollBarStyle () {
|
getScrollBarStyle() {
|
||||||
const { theme } = this.props
|
const { theme } = this.props
|
||||||
|
|
||||||
return uiThemes.some(item => item.name === theme && item.isDark) ? scrollBarDarkStyle : scrollBarStyle
|
return uiThemes.some(item => item.name === theme && item.isDark)
|
||||||
|
? scrollBarDarkStyle
|
||||||
|
: scrollBarStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
const { onDrop } = this.props
|
const { onDrop } = this.props
|
||||||
|
|
||||||
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
||||||
@@ -542,10 +561,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
'scroll',
|
'scroll',
|
||||||
this.scrollHandler
|
this.scrollHandler
|
||||||
)
|
)
|
||||||
this.refs.root.contentWindow.addEventListener(
|
this.refs.root.contentWindow.addEventListener('resize', this.resizeHandler)
|
||||||
'resize',
|
|
||||||
this.resizeHandler
|
|
||||||
)
|
|
||||||
eventEmitter.on('export:save-text', this.saveAsTextHandler)
|
eventEmitter.on('export:save-text', this.saveAsTextHandler)
|
||||||
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
||||||
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
|
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
|
||||||
@@ -553,7 +569,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
eventEmitter.on('print', this.printHandler)
|
eventEmitter.on('print', this.printHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount() {
|
||||||
const { onDrop } = this.props
|
const { onDrop } = this.props
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.body.removeEventListener(
|
this.refs.root.contentWindow.document.body.removeEventListener(
|
||||||
@@ -595,7 +611,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
eventEmitter.off('print', this.printHandler)
|
eventEmitter.off('print', this.printHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
// actual rewriteIframe function should be called only once
|
// actual rewriteIframe function should be called only once
|
||||||
let needsRewriteIframe = false
|
let needsRewriteIframe = false
|
||||||
if (prevProps.value !== this.props.value) needsRewriteIframe = true
|
if (prevProps.value !== this.props.value) needsRewriteIframe = true
|
||||||
@@ -637,7 +653,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getStyleParams () {
|
getStyleParams() {
|
||||||
const {
|
const {
|
||||||
fontSize,
|
fontSize,
|
||||||
lineNumber,
|
lineNumber,
|
||||||
@@ -649,19 +665,20 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
RTL
|
RTL
|
||||||
} = this.props
|
} = this.props
|
||||||
let { fontFamily, codeBlockFontFamily } = this.props
|
let { fontFamily, codeBlockFontFamily } = this.props
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
fontFamily =
|
||||||
? fontFamily
|
_.isString(fontFamily) && fontFamily.trim().length > 0
|
||||||
.split(',')
|
? fontFamily
|
||||||
.map(fontName => fontName.trim())
|
.split(',')
|
||||||
.concat(defaultFontFamily)
|
.map(fontName => fontName.trim())
|
||||||
: defaultFontFamily
|
.concat(defaultFontFamily)
|
||||||
codeBlockFontFamily = _.isString(codeBlockFontFamily) &&
|
: defaultFontFamily
|
||||||
codeBlockFontFamily.trim().length > 0
|
codeBlockFontFamily =
|
||||||
? codeBlockFontFamily
|
_.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0
|
||||||
.split(',')
|
? codeBlockFontFamily
|
||||||
.map(fontName => fontName.trim())
|
.split(',')
|
||||||
.concat(defaultCodeBlockFontFamily)
|
.map(fontName => fontName.trim())
|
||||||
: defaultCodeBlockFontFamily
|
.concat(defaultCodeBlockFontFamily)
|
||||||
|
: defaultCodeBlockFontFamily
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fontFamily,
|
fontFamily,
|
||||||
@@ -677,7 +694,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStyle () {
|
applyStyle() {
|
||||||
const {
|
const {
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize,
|
fontSize,
|
||||||
@@ -707,7 +724,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getCodeThemeLink (name) {
|
getCodeThemeLink(name) {
|
||||||
const theme = consts.THEMES.find(theme => theme.name === name)
|
const theme = consts.THEMES.find(theme => theme.name === name)
|
||||||
|
|
||||||
return theme != null
|
return theme != null
|
||||||
@@ -715,7 +732,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
: `${appPath}/node_modules/codemirror/theme/elegant.css`
|
: `${appPath}/node_modules/codemirror/theme/elegant.css`
|
||||||
}
|
}
|
||||||
|
|
||||||
rewriteIframe () {
|
rewriteIframe() {
|
||||||
_.forEach(
|
_.forEach(
|
||||||
this.refs.root.contentWindow.document.querySelectorAll(
|
this.refs.root.contentWindow.document.querySelectorAll(
|
||||||
'input[type="checkbox"]'
|
'input[type="checkbox"]'
|
||||||
@@ -773,7 +790,9 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
codeBlockTheme = consts.THEMES.find(theme => theme.name === codeBlockTheme)
|
codeBlockTheme = consts.THEMES.find(theme => theme.name === codeBlockTheme)
|
||||||
|
|
||||||
const codeBlockThemeClassName = codeBlockTheme ? codeBlockTheme.className : 'cm-s-default'
|
const codeBlockThemeClassName = codeBlockTheme
|
||||||
|
? codeBlockTheme.className
|
||||||
|
: 'cm-s-default'
|
||||||
|
|
||||||
_.forEach(
|
_.forEach(
|
||||||
this.refs.root.contentWindow.document.querySelectorAll('.code code'),
|
this.refs.root.contentWindow.document.querySelectorAll('.code code'),
|
||||||
@@ -859,7 +878,10 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
el => {
|
el => {
|
||||||
try {
|
try {
|
||||||
const format = el.attributes.getNamedItem('data-format').value
|
const format = el.attributes.getNamedItem('data-format').value
|
||||||
const chartConfig = format === 'yaml' ? yaml.load(el.innerHTML) : JSON.parse(el.innerHTML)
|
const chartConfig =
|
||||||
|
format === 'yaml'
|
||||||
|
? yaml.load(el.innerHTML)
|
||||||
|
: JSON.parse(el.innerHTML)
|
||||||
el.innerHTML = ''
|
el.innerHTML = ''
|
||||||
|
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement('canvas')
|
||||||
@@ -882,7 +904,12 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
_.forEach(
|
_.forEach(
|
||||||
this.refs.root.contentWindow.document.querySelectorAll('.mermaid'),
|
this.refs.root.contentWindow.document.querySelectorAll('.mermaid'),
|
||||||
el => {
|
el => {
|
||||||
mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme, mermaidHTMLLabel)
|
mermaidRender(
|
||||||
|
el,
|
||||||
|
htmlTextHelper.decodeEntities(el.innerHTML),
|
||||||
|
theme,
|
||||||
|
mermaidHTMLLabel
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -904,20 +931,14 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
autoplay = 0
|
autoplay = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
render(
|
render(<Carousel images={images} autoplay={autoplay} />, el)
|
||||||
<Carousel
|
|
||||||
images={images}
|
|
||||||
autoplay={autoplay}
|
|
||||||
/>,
|
|
||||||
el
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const markdownPreviewIframe = document.querySelector('.MarkdownPreview')
|
const markdownPreviewIframe = document.querySelector('.MarkdownPreview')
|
||||||
const rect = markdownPreviewIframe.getBoundingClientRect()
|
const rect = markdownPreviewIframe.getBoundingClientRect()
|
||||||
const config = { attributes: true, subtree: true }
|
const config = { attributes: true, subtree: true }
|
||||||
const imgObserver = new MutationObserver((mutationList) => {
|
const imgObserver = new MutationObserver(mutationList => {
|
||||||
for (const mu of mutationList) {
|
for (const mu of mutationList) {
|
||||||
if (mu.target.className === 'carouselContent-enter-done') {
|
if (mu.target.className === 'carouselContent-enter-done') {
|
||||||
this.setImgOnClickEventHelper(mu.target, rect)
|
this.setImgOnClickEventHelper(mu.target, rect)
|
||||||
@@ -926,26 +947,32 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('img')
|
const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll(
|
||||||
|
'img'
|
||||||
|
)
|
||||||
for (const img of imgList) {
|
for (const img of imgList) {
|
||||||
const parentEl = img.parentElement
|
const parentEl = img.parentElement
|
||||||
this.setImgOnClickEventHelper(img, rect)
|
this.setImgOnClickEventHelper(img, rect)
|
||||||
imgObserver.observe(parentEl, config)
|
imgObserver.observe(parentEl, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
const aList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('a')
|
const aList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll(
|
||||||
|
'a'
|
||||||
|
)
|
||||||
for (const a of aList) {
|
for (const a of aList) {
|
||||||
a.removeEventListener('click', this.linkClickHandler)
|
a.removeEventListener('click', this.linkClickHandler)
|
||||||
a.addEventListener('click', this.linkClickHandler)
|
a.addEventListener('click', this.linkClickHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setImgOnClickEventHelper (img, rect) {
|
setImgOnClickEventHelper(img, rect) {
|
||||||
img.onclick = () => {
|
img.onclick = () => {
|
||||||
const widthMagnification = document.body.clientWidth / img.width
|
const widthMagnification = document.body.clientWidth / img.width
|
||||||
const heightMagnification = document.body.clientHeight / img.height
|
const heightMagnification = document.body.clientHeight / img.height
|
||||||
const baseOnWidth = widthMagnification < heightMagnification
|
const baseOnWidth = widthMagnification < heightMagnification
|
||||||
const magnification = baseOnWidth ? widthMagnification : heightMagnification
|
const magnification = baseOnWidth
|
||||||
|
? widthMagnification
|
||||||
|
: heightMagnification
|
||||||
|
|
||||||
const zoomImgWidth = img.width * magnification
|
const zoomImgWidth = img.width * magnification
|
||||||
const zoomImgHeight = img.height * magnification
|
const zoomImgHeight = img.height * magnification
|
||||||
@@ -976,10 +1003,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
width: ${zoomImgWidth};
|
width: ${zoomImgWidth};
|
||||||
height: ${zoomImgHeight}px;
|
height: ${zoomImgHeight}px;
|
||||||
`
|
`
|
||||||
zoomImg.animate([
|
zoomImg.animate([originalImgRect, zoomInImgRect], animationSpeed)
|
||||||
originalImgRect,
|
|
||||||
zoomInImgRect
|
|
||||||
], animationSpeed)
|
|
||||||
|
|
||||||
const overlay = document.createElement('div')
|
const overlay = document.createElement('div')
|
||||||
overlay.style = `
|
overlay.style = `
|
||||||
@@ -1000,10 +1024,10 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
width: ${img.width}px;
|
width: ${img.width}px;
|
||||||
height: ${img.height}px;
|
height: ${img.height}px;
|
||||||
`
|
`
|
||||||
const zoomOutImgAnimation = zoomImg.animate([
|
const zoomOutImgAnimation = zoomImg.animate(
|
||||||
zoomInImgRect,
|
[zoomInImgRect, originalImgRect],
|
||||||
originalImgRect
|
animationSpeed
|
||||||
], animationSpeed)
|
)
|
||||||
zoomOutImgAnimation.onfinish = () => overlay.remove()
|
zoomOutImgAnimation.onfinish = () => overlay.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1012,7 +1036,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResize () {
|
handleResize() {
|
||||||
_.forEach(
|
_.forEach(
|
||||||
this.refs.root.contentWindow.document.querySelectorAll('svg[ratio]'),
|
this.refs.root.contentWindow.document.querySelectorAll('svg[ratio]'),
|
||||||
el => {
|
el => {
|
||||||
@@ -1021,11 +1045,11 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus() {
|
||||||
this.refs.root.focus()
|
this.refs.root.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
getWindow () {
|
getWindow() {
|
||||||
return this.refs.root.contentWindow
|
return this.refs.root.contentWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1033,7 +1057,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
* @public
|
* @public
|
||||||
* @param {Number} targetRow
|
* @param {Number} targetRow
|
||||||
*/
|
*/
|
||||||
scrollToRow (targetRow) {
|
scrollToRow(targetRow) {
|
||||||
const blocks = this.getWindow().document.querySelectorAll(
|
const blocks = this.getWindow().document.querySelectorAll(
|
||||||
'body>[data-line]'
|
'body>[data-line]'
|
||||||
)
|
)
|
||||||
@@ -1054,16 +1078,16 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
* @param {Number} x
|
* @param {Number} x
|
||||||
* @param {Number} y
|
* @param {Number} y
|
||||||
*/
|
*/
|
||||||
scrollTo (x, y) {
|
scrollTo(x, y) {
|
||||||
this.getWindow().document.body.scrollTo(x, y)
|
this.getWindow().document.body.scrollTo(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
preventImageDroppedHandler (e) {
|
preventImageDroppedHandler(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
}
|
}
|
||||||
|
|
||||||
notify (title, options) {
|
notify(title, options) {
|
||||||
if (global.process.platform === 'win32') {
|
if (global.process.platform === 'win32') {
|
||||||
options.icon = path.join(
|
options.icon = path.join(
|
||||||
'file://',
|
'file://',
|
||||||
@@ -1074,7 +1098,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
return new window.Notification(title, options)
|
return new window.Notification(title, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLinkClick (e) {
|
handleLinkClick(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
@@ -1095,9 +1119,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
if (posOfHash > -1) {
|
if (posOfHash > -1) {
|
||||||
const extractedId = linkHash.slice(posOfHash + 1)
|
const extractedId = linkHash.slice(posOfHash + 1)
|
||||||
const targetId = mdurl.encode(extractedId)
|
const targetId = mdurl.encode(extractedId)
|
||||||
const targetElement = this.getWindow().document.getElementById(
|
const targetElement = this.getWindow().document.getElementById(targetId)
|
||||||
targetId
|
|
||||||
)
|
|
||||||
|
|
||||||
if (targetElement != null) {
|
if (targetElement != null) {
|
||||||
this.scrollTo(0, targetElement.offsetTop)
|
this.scrollTo(0, targetElement.offsetTop)
|
||||||
@@ -1138,9 +1160,10 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.openExternal(href)
|
this.openExternal(href)
|
||||||
}
|
}
|
||||||
|
|
||||||
openExternal (href) {
|
openExternal(href) {
|
||||||
try {
|
try {
|
||||||
const success = shell.openExternal(href) || shell.openExternal(decodeURI(href))
|
const success =
|
||||||
|
shell.openExternal(href) || shell.openExternal(decodeURI(href))
|
||||||
if (!success) console.error('failed to open url ' + href)
|
if (!success) console.error('failed to open url ' + href)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// URI Error threw from decodeURI
|
// URI Error threw from decodeURI
|
||||||
@@ -1148,7 +1171,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { className, style, tabIndex } = this.props
|
const { className, style, tabIndex } = this.props
|
||||||
return (
|
return (
|
||||||
<iframe
|
<iframe
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import styles from './MarkdownSplitEditor.styl'
|
|||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
class MarkdownSplitEditor extends React.Component {
|
class MarkdownSplitEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.value = props.value
|
this.value = props.value
|
||||||
this.focus = () => this.refs.code.focus()
|
this.focus = () => this.refs.code.focus()
|
||||||
@@ -20,19 +20,22 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue (value) {
|
setValue(value) {
|
||||||
this.refs.code.setValue(value)
|
this.refs.code.setValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOnChange (e) {
|
handleOnChange(e) {
|
||||||
this.value = this.refs.code.value
|
this.value = this.refs.code.value
|
||||||
this.props.onChange(e)
|
this.props.onChange(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScroll (e) {
|
handleScroll(e) {
|
||||||
if (!this.props.config.preview.scrollSync) return
|
if (!this.props.config.preview.scrollSync) return
|
||||||
|
|
||||||
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
|
const previewDoc = _.get(
|
||||||
|
this,
|
||||||
|
'refs.preview.refs.root.contentWindow.document'
|
||||||
|
)
|
||||||
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
||||||
let srcTop, srcHeight, targetTop, targetHeight
|
let srcTop, srcHeight, targetTop, targetHeight
|
||||||
|
|
||||||
@@ -49,7 +52,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
targetHeight = _.get(codeDoc, 'height')
|
targetHeight = _.get(codeDoc, 'height')
|
||||||
}
|
}
|
||||||
|
|
||||||
const distance = (targetHeight * srcTop / srcHeight) - targetTop
|
const distance = (targetHeight * srcTop) / srcHeight - targetTop
|
||||||
const framerate = 1000 / 60
|
const framerate = 1000 / 60
|
||||||
const frames = 20
|
const frames = 20
|
||||||
const refractory = frames * framerate
|
const refractory = frames * framerate
|
||||||
@@ -60,21 +63,29 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
let scrollPos, time
|
let scrollPos, time
|
||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
time = frame / frames
|
time = frame / frames
|
||||||
scrollPos = time < 0.5
|
scrollPos =
|
||||||
? 2 * time * time // ease in
|
time < 0.5
|
||||||
: -1 + (4 - 2 * time) * time // ease out
|
? 2 * time * time // ease in
|
||||||
if (e.doc) _.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
|
: -1 + (4 - 2 * time) * time // ease out
|
||||||
else _.get(this, 'refs.code.editor').scrollTo(0, targetTop + scrollPos * distance)
|
if (e.doc)
|
||||||
|
_.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
|
||||||
|
else
|
||||||
|
_.get(this, 'refs.code.editor').scrollTo(
|
||||||
|
0,
|
||||||
|
targetTop + scrollPos * distance
|
||||||
|
)
|
||||||
if (frame >= frames) {
|
if (frame >= frames) {
|
||||||
clearInterval(timer)
|
clearInterval(timer)
|
||||||
setTimeout(() => { this.userScroll = true }, refractory)
|
setTimeout(() => {
|
||||||
|
this.userScroll = true
|
||||||
|
}, refractory)
|
||||||
}
|
}
|
||||||
frame++
|
frame++
|
||||||
}, framerate)
|
}, framerate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCheckboxClick (e) {
|
handleCheckboxClick(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
const idMatch = /checkbox-([0-9]+)/
|
const idMatch = /checkbox-([0-9]+)/
|
||||||
@@ -83,9 +94,9 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
const checkReplace = /\[x]/i
|
const checkReplace = /\[x]/i
|
||||||
const uncheckReplace = /\[ ]/
|
const uncheckReplace = /\[ ]/
|
||||||
if (idMatch.test(e.target.getAttribute('id'))) {
|
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||||
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
const lineIndex =
|
||||||
const lines = this.refs.code.value
|
parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||||
.split('\n')
|
const lines = this.refs.code.value.split('\n')
|
||||||
|
|
||||||
const targetLine = lines[lineIndex]
|
const targetLine = lines[lineIndex]
|
||||||
let newLine = targetLine
|
let newLine = targetLine
|
||||||
@@ -100,12 +111,12 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseMove (e) {
|
handleMouseMove(e) {
|
||||||
if (this.state.isSliderFocused) {
|
if (this.state.isSliderFocused) {
|
||||||
const rootRect = this.refs.root.getBoundingClientRect()
|
const rootRect = this.refs.root.getBoundingClientRect()
|
||||||
const rootWidth = rootRect.width
|
const rootWidth = rootRect.width
|
||||||
const offset = rootRect.left
|
const offset = rootRect.left
|
||||||
let newCodeEditorWidthInPercent = (e.pageX - offset) / rootWidth * 100
|
let newCodeEditorWidthInPercent = ((e.pageX - offset) / rootWidth) * 100
|
||||||
|
|
||||||
// limit minSize to 10%, maxSize to 90%
|
// limit minSize to 10%, maxSize to 90%
|
||||||
if (newCodeEditorWidthInPercent <= 10) {
|
if (newCodeEditorWidthInPercent <= 10) {
|
||||||
@@ -122,34 +133,45 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp (e) {
|
handleMouseUp(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.setState({
|
this.setState({
|
||||||
isSliderFocused: false
|
isSliderFocused: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseDown (e) {
|
handleMouseDown(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.setState({
|
this.setState({
|
||||||
isSliderFocused: true
|
isSliderFocused: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const {config, value, storageKey, noteKey, linesHighlighted, RTL} = this.props
|
const {
|
||||||
|
config,
|
||||||
|
value,
|
||||||
|
storageKey,
|
||||||
|
noteKey,
|
||||||
|
linesHighlighted,
|
||||||
|
RTL
|
||||||
|
} = this.props
|
||||||
const storage = findStorage(storageKey)
|
const storage = findStorage(storageKey)
|
||||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
const previewStyle = {}
|
const previewStyle = {}
|
||||||
previewStyle.width = (100 - this.state.codeEditorWidthInPercent) + '%'
|
previewStyle.width = 100 - this.state.codeEditorWidthInPercent + '%'
|
||||||
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused) previewStyle.pointerEvents = 'none'
|
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused)
|
||||||
|
previewStyle.pointerEvents = 'none'
|
||||||
return (
|
return (
|
||||||
<div styleName='root' ref='root'
|
<div
|
||||||
|
styleName='root'
|
||||||
|
ref='root'
|
||||||
onMouseMove={e => this.handleMouseMove(e)}
|
onMouseMove={e => this.handleMouseMove(e)}
|
||||||
onMouseUp={e => this.handleMouseUp(e)}>
|
onMouseUp={e => this.handleMouseUp(e)}
|
||||||
|
>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
ref='code'
|
ref='code'
|
||||||
width={this.state.codeEditorWidthInPercent + '%'}
|
width={this.state.codeEditorWidthInPercent + '%'}
|
||||||
@@ -174,7 +196,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
storageKey={storageKey}
|
storageKey={storageKey}
|
||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
linesHighlighted={linesHighlighted}
|
linesHighlighted={linesHighlighted}
|
||||||
onChange={(e) => this.handleOnChange(e)}
|
onChange={e => this.handleOnChange(e)}
|
||||||
onScroll={this.handleScroll.bind(this)}
|
onScroll={this.handleScroll.bind(this)}
|
||||||
spellCheck={config.editor.spellcheck}
|
spellCheck={config.editor.spellcheck}
|
||||||
enableSmartPaste={config.editor.enableSmartPaste}
|
enableSmartPaste={config.editor.enableSmartPaste}
|
||||||
@@ -184,8 +206,12 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
||||||
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
||||||
RTL={RTL}
|
RTL={RTL}
|
||||||
/>
|
/>
|
||||||
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
<div
|
||||||
|
styleName='slider'
|
||||||
|
style={{ left: this.state.codeEditorWidthInPercent + '%' }}
|
||||||
|
onMouseDown={e => this.handleMouseDown(e)}
|
||||||
|
>
|
||||||
<div styleName='slider-hitbox' />
|
<div styleName='slider-hitbox' />
|
||||||
</div>
|
</div>
|
||||||
<MarkdownPreview
|
<MarkdownPreview
|
||||||
@@ -206,7 +232,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
ref='preview'
|
ref='preview'
|
||||||
tabInde='0'
|
tabInde='0'
|
||||||
value={value}
|
value={value}
|
||||||
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
onCheckboxClick={e => this.handleCheckboxClick(e)}
|
||||||
onScroll={this.handleScroll.bind(this)}
|
onScroll={this.handleScroll.bind(this)}
|
||||||
showCopyNotification={config.ui.showCopyNotification}
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
storagePath={storage.path}
|
storagePath={storage.path}
|
||||||
@@ -215,7 +241,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
allowCustomCSS={config.preview.allowCustomCSS}
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||||
RTL={RTL}
|
RTL={RTL}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ import React from 'react'
|
|||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './ModalEscButton.styl'
|
import styles from './ModalEscButton.styl'
|
||||||
|
|
||||||
const ModalEscButton = ({
|
const ModalEscButton = ({ handleEscButtonClick }) => (
|
||||||
handleEscButtonClick
|
|
||||||
}) => (
|
|
||||||
<button styleName='escButton' onClick={handleEscButtonClick}>
|
<button styleName='escButton' onClick={handleEscButtonClick}>
|
||||||
<div styleName='esc-mark'>×</div>
|
<div styleName='esc-mark'>×</div>
|
||||||
<div>esc</div>
|
<div>esc</div>
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Micro component for toggle SideNav
|
* @fileoverview Micro component for toggle SideNav
|
||||||
*/
|
*/
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styles from './NavToggleButton.styl'
|
import styles from './NavToggleButton.styl'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} isFolded
|
* @param {boolean} isFolded
|
||||||
* @param {Function} handleToggleButtonClick
|
* @param {Function} handleToggleButtonClick
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const NavToggleButton = ({isFolded, handleToggleButtonClick}) => (
|
const NavToggleButton = ({ isFolded, handleToggleButtonClick }) => (
|
||||||
<button styleName='navToggle'
|
<button styleName='navToggle' onClick={e => handleToggleButtonClick(e)}>
|
||||||
onClick={(e) => handleToggleButtonClick(e)}
|
{isFolded ? (
|
||||||
>
|
<i className='fa fa-angle-double-right fa-2x' />
|
||||||
{isFolded
|
) : (
|
||||||
? <i className='fa fa-angle-double-right fa-2x' />
|
<i className='fa fa-angle-double-left fa-2x' />
|
||||||
: <i className='fa fa-angle-double-left fa-2x' />
|
)}
|
||||||
}
|
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,11 @@ const TagElement = ({ tagName, color }) => {
|
|||||||
const style = {}
|
const style = {}
|
||||||
if (color) {
|
if (color) {
|
||||||
style.backgroundColor = color
|
style.backgroundColor = color
|
||||||
style.color = invertColor(color, { black: '#222', white: '#f1f1f1', threshold: 0.3 })
|
style.color = invertColor(color, {
|
||||||
|
black: '#222',
|
||||||
|
white: '#f1f1f1',
|
||||||
|
threshold: 0.3
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<span styleName='item-bottom-tagList-item' key={tagName} style={style}>
|
<span styleName='item-bottom-tagList-item' key={tagName} style={style}>
|
||||||
@@ -44,9 +48,13 @@ const TagElementList = (tags, showTagsAlphabetically, coloredTags) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showTagsAlphabetically) {
|
if (showTagsAlphabetically) {
|
||||||
return sortBy(tags).map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
|
return sortBy(tags).map(tag =>
|
||||||
|
TagElement({ tagName: tag, color: coloredTags[tag] })
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
return tags.map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
|
return tags.map(tag =>
|
||||||
|
TagElement({ tagName: tag, color: coloredTags[tag] })
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,13 +91,17 @@ const NoteItem = ({
|
|||||||
draggable='true'
|
draggable='true'
|
||||||
>
|
>
|
||||||
<div styleName='item-wrapper'>
|
<div styleName='item-wrapper'>
|
||||||
{note.type === 'SNIPPET_NOTE'
|
{note.type === 'SNIPPET_NOTE' ? (
|
||||||
? <i styleName='item-title-icon' className='fa fa-fw fa-code' />
|
<i styleName='item-title-icon' className='fa fa-fw fa-code' />
|
||||||
: <i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />}
|
) : (
|
||||||
|
<i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />
|
||||||
|
)}
|
||||||
<div styleName='item-title'>
|
<div styleName='item-title'>
|
||||||
{note.title.trim().length > 0
|
{note.title.trim().length > 0 ? (
|
||||||
? <Emoji text={note.title} />
|
<Emoji text={note.title} />
|
||||||
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>}
|
) : (
|
||||||
|
<span styleName='item-title-empty'>{i18n.__('Empty note')}</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div styleName='item-middle'>
|
<div styleName='item-middle'>
|
||||||
<div styleName='item-middle-time'>{dateDisplay}</div>
|
<div styleName='item-middle-time'>{dateDisplay}</div>
|
||||||
@@ -98,7 +110,9 @@ const NoteItem = ({
|
|||||||
title={
|
title={
|
||||||
viewType === 'ALL'
|
viewType === 'ALL'
|
||||||
? storageName
|
? storageName
|
||||||
: viewType === 'STORAGE' ? folderName : null
|
: viewType === 'STORAGE'
|
||||||
|
? folderName
|
||||||
|
: null
|
||||||
}
|
}
|
||||||
styleName='item-middle-app-meta-label'
|
styleName='item-middle-app-meta-label'
|
||||||
>
|
>
|
||||||
@@ -109,28 +123,36 @@ const NoteItem = ({
|
|||||||
</div>
|
</div>
|
||||||
<div styleName='item-bottom'>
|
<div styleName='item-bottom'>
|
||||||
<div styleName='item-bottom-tagList'>
|
<div styleName='item-bottom-tagList'>
|
||||||
{note.tags.length > 0
|
{note.tags.length > 0 ? (
|
||||||
? TagElementList(note.tags, showTagsAlphabetically, coloredTags)
|
TagElementList(note.tags, showTagsAlphabetically, coloredTags)
|
||||||
: <span
|
) : (
|
||||||
|
<span
|
||||||
style={{ fontStyle: 'italic', opacity: 0.5 }}
|
style={{ fontStyle: 'italic', opacity: 0.5 }}
|
||||||
styleName='item-bottom-tagList-empty'
|
styleName='item-bottom-tagList-empty'
|
||||||
>
|
>
|
||||||
{i18n.__('No tags')}
|
{i18n.__('No tags')}
|
||||||
</span>}
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{note.isStarred
|
{note.isStarred ? (
|
||||||
? <img
|
<img
|
||||||
styleName='item-star'
|
styleName='item-star'
|
||||||
src='../resources/icon/icon-starred.svg'
|
src='../resources/icon/icon-starred.svg'
|
||||||
/>
|
/>
|
||||||
: ''}
|
) : (
|
||||||
{note.isPinned && !pathname.match(/\/starred|\/trash/)
|
''
|
||||||
? <i styleName='item-pin' className='fa fa-thumb-tack' />
|
)}
|
||||||
: ''}
|
{note.isPinned && !pathname.match(/\/starred|\/trash/) ? (
|
||||||
{note.type === 'MARKDOWN_NOTE'
|
<i styleName='item-pin' className='fa fa-thumb-tack' />
|
||||||
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
|
) : (
|
||||||
: ''}
|
''
|
||||||
|
)}
|
||||||
|
{note.type === 'MARKDOWN_NOTE' ? (
|
||||||
|
<TodoProcess todoStatus={getTodoStatus(note.content)} />
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,10 +25,8 @@ const NoteItemSimple = ({
|
|||||||
pathname,
|
pathname,
|
||||||
storage
|
storage
|
||||||
}) => (
|
}) => (
|
||||||
<div styleName={isActive
|
<div
|
||||||
? 'item-simple--active'
|
styleName={isActive ? 'item-simple--active' : 'item-simple'}
|
||||||
: 'item-simple'
|
|
||||||
}
|
|
||||||
key={note.key}
|
key={note.key}
|
||||||
onClick={e => handleNoteClick(e, note.key)}
|
onClick={e => handleNoteClick(e, note.key)}
|
||||||
onContextMenu={e => handleNoteContextMenu(e, note.key)}
|
onContextMenu={e => handleNoteContextMenu(e, note.key)}
|
||||||
@@ -36,23 +34,29 @@ const NoteItemSimple = ({
|
|||||||
draggable='true'
|
draggable='true'
|
||||||
>
|
>
|
||||||
<div styleName='item-simple-title'>
|
<div styleName='item-simple-title'>
|
||||||
{note.type === 'SNIPPET_NOTE'
|
{note.type === 'SNIPPET_NOTE' ? (
|
||||||
? <i styleName='item-simple-title-icon' className='fa fa-fw fa-code' />
|
<i styleName='item-simple-title-icon' className='fa fa-fw fa-code' />
|
||||||
: <i styleName='item-simple-title-icon' className='fa fa-fw fa-file-text-o' />
|
) : (
|
||||||
}
|
<i
|
||||||
{note.isPinned && !pathname.match(/\/starred|\/trash/)
|
styleName='item-simple-title-icon'
|
||||||
? <i styleName='item-pin' className='fa fa-thumb-tack' />
|
className='fa fa-fw fa-file-text-o'
|
||||||
: ''
|
/>
|
||||||
}
|
)}
|
||||||
{note.title.trim().length > 0
|
{note.isPinned && !pathname.match(/\/starred|\/trash/) ? (
|
||||||
? note.title
|
<i styleName='item-pin' className='fa fa-thumb-tack' />
|
||||||
: <span styleName='item-simple-title-empty'>{i18n.__('Empty note')}</span>
|
) : (
|
||||||
}
|
''
|
||||||
{isAllNotesView && <div styleName='item-simple-right'>
|
)}
|
||||||
<span styleName='item-simple-right-storageName'>
|
{note.title.trim().length > 0 ? (
|
||||||
{storage.name}
|
note.title
|
||||||
</span>
|
) : (
|
||||||
</div>}
|
<span styleName='item-simple-title-empty'>{i18n.__('Empty note')}</span>
|
||||||
|
)}
|
||||||
|
{isAllNotesView && (
|
||||||
|
<div styleName='item-simple-right'>
|
||||||
|
<span styleName='item-simple-right-storageName'>{storage.name}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const electron = require('electron')
|
|||||||
const { shell } = electron
|
const { shell } = electron
|
||||||
|
|
||||||
class RealtimeNotification extends React.Component {
|
class RealtimeNotification extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -14,38 +14,46 @@ class RealtimeNotification extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
this.fetchNotifications()
|
this.fetchNotifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchNotifications () {
|
fetchNotifications() {
|
||||||
const notificationsUrl = 'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
|
const notificationsUrl =
|
||||||
|
'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
|
||||||
fetch(notificationsUrl)
|
fetch(notificationsUrl)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then(json => {
|
.then(json => {
|
||||||
this.setState({notifications: json.notifications})
|
this.setState({ notifications: json.notifications })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLinkClick (e) {
|
handleLinkClick(e) {
|
||||||
shell.openExternal(e.currentTarget.href)
|
shell.openExternal(e.currentTarget.href)
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { notifications } = this.state
|
const { notifications } = this.state
|
||||||
const link = notifications.length > 0
|
const link =
|
||||||
? <a styleName='notification-link' href={notifications[0].linkUrl}
|
notifications.length > 0 ? (
|
||||||
onClick={(e) => this.handleLinkClick(e)}
|
<a
|
||||||
>
|
styleName='notification-link'
|
||||||
Info: {notifications[0].text}
|
href={notifications[0].linkUrl}
|
||||||
</a>
|
onClick={e => this.handleLinkClick(e)}
|
||||||
: ''
|
>
|
||||||
|
Info: {notifications[0].text}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div styleName='notification-area' style={this.props.style}>{link}</div>
|
<div styleName='notification-area' style={this.props.style}>
|
||||||
|
{link}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,54 +16,70 @@ import i18n from 'browser/lib/i18n'
|
|||||||
* @return {React.Component}
|
* @return {React.Component}
|
||||||
*/
|
*/
|
||||||
const SideNavFilter = ({
|
const SideNavFilter = ({
|
||||||
isFolded, isHomeActive, handleAllNotesButtonClick,
|
isFolded,
|
||||||
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote,
|
isHomeActive,
|
||||||
counterTotalNote, counterStarredNote, handleFilterButtonContextMenu
|
handleAllNotesButtonClick,
|
||||||
|
isStarredActive,
|
||||||
|
handleStarredButtonClick,
|
||||||
|
isTrashedActive,
|
||||||
|
handleTrashedButtonClick,
|
||||||
|
counterDelNote,
|
||||||
|
counterTotalNote,
|
||||||
|
counterStarredNote,
|
||||||
|
handleFilterButtonContextMenu
|
||||||
}) => (
|
}) => (
|
||||||
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
|
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
|
||||||
|
<button
|
||||||
<button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
|
styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
|
||||||
onClick={handleAllNotesButtonClick}
|
onClick={handleAllNotesButtonClick}
|
||||||
>
|
>
|
||||||
<div styleName='iconWrap'>
|
<div styleName='iconWrap'>
|
||||||
<img src={isHomeActive
|
<img
|
||||||
? '../resources/icon/icon-all-active.svg'
|
src={
|
||||||
: '../resources/icon/icon-all.svg'
|
isHomeActive
|
||||||
}
|
? '../resources/icon/icon-all-active.svg'
|
||||||
|
: '../resources/icon/icon-all.svg'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span styleName='menu-button-label'>{i18n.__('All Notes')}</span>
|
<span styleName='menu-button-label'>{i18n.__('All Notes')}</span>
|
||||||
<span styleName='counters'>{counterTotalNote}</span>
|
<span styleName='counters'>{counterTotalNote}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
|
<button
|
||||||
|
styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
|
||||||
onClick={handleStarredButtonClick}
|
onClick={handleStarredButtonClick}
|
||||||
>
|
>
|
||||||
<div styleName='iconWrap'>
|
<div styleName='iconWrap'>
|
||||||
<img src={isStarredActive
|
<img
|
||||||
? '../resources/icon/icon-star-active.svg'
|
src={
|
||||||
: '../resources/icon/icon-star-sidenav.svg'
|
isStarredActive
|
||||||
}
|
? '../resources/icon/icon-star-active.svg'
|
||||||
|
: '../resources/icon/icon-star-sidenav.svg'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span styleName='menu-button-label'>{i18n.__('Starred')}</span>
|
<span styleName='menu-button-label'>{i18n.__('Starred')}</span>
|
||||||
<span styleName='counters'>{counterStarredNote}</span>
|
<span styleName='counters'>{counterStarredNote}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
|
<button
|
||||||
onClick={handleTrashedButtonClick} onContextMenu={handleFilterButtonContextMenu}
|
styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
|
||||||
|
onClick={handleTrashedButtonClick}
|
||||||
|
onContextMenu={handleFilterButtonContextMenu}
|
||||||
>
|
>
|
||||||
<div styleName='iconWrap'>
|
<div styleName='iconWrap'>
|
||||||
<img src={isTrashedActive
|
<img
|
||||||
? '../resources/icon/icon-trash-active.svg'
|
src={
|
||||||
: '../resources/icon/icon-trash-sidenav.svg'
|
isTrashedActive
|
||||||
}
|
? '../resources/icon/icon-trash-active.svg'
|
||||||
|
: '../resources/icon/icon-trash-sidenav.svg'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span styleName='menu-button-label'>{i18n.__('Trash')}</span>
|
<span styleName='menu-button-label'>{i18n.__('Trash')}</span>
|
||||||
<span styleName='counters'>{counterDelNote}</span>
|
<span styleName='counters'>{counterDelNote}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import context from 'browser/lib/context'
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
class SnippetTab extends React.Component {
|
class SnippetTab extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -14,7 +14,7 @@ class SnippetTab extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUpdate (nextProps) {
|
componentWillUpdate(nextProps) {
|
||||||
if (nextProps.snippet.name !== this.props.snippet.name) {
|
if (nextProps.snippet.name !== this.props.snippet.name) {
|
||||||
this.setState({
|
this.setState({
|
||||||
name: nextProps.snippet.name
|
name: nextProps.snippet.name
|
||||||
@@ -22,34 +22,34 @@ class SnippetTab extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick (e) {
|
handleClick(e) {
|
||||||
this.props.onClick(e)
|
this.props.onClick(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContextMenu (e) {
|
handleContextMenu(e) {
|
||||||
context.popup([
|
context.popup([
|
||||||
{
|
{
|
||||||
label: i18n.__('Rename'),
|
label: i18n.__('Rename'),
|
||||||
click: (e) => this.handleRenameClick(e)
|
click: e => this.handleRenameClick(e)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRenameClick (e) {
|
handleRenameClick(e) {
|
||||||
this.startRenaming()
|
this.startRenaming()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNameInputBlur (e) {
|
handleNameInputBlur(e) {
|
||||||
this.handleRename()
|
this.handleRename()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNameInputChange (e) {
|
handleNameInputChange(e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
name: e.target.value
|
name: e.target.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNameInputKeyDown (e) {
|
handleNameInputKeyDown(e) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case 13:
|
case 13:
|
||||||
this.handleRename()
|
this.handleRename()
|
||||||
@@ -63,84 +63,87 @@ class SnippetTab extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRename () {
|
handleRename() {
|
||||||
this.setState({
|
this.setState(
|
||||||
isRenaming: false
|
{
|
||||||
}, () => {
|
isRenaming: false
|
||||||
if (this.props.snippet.name !== this.state.name) {
|
},
|
||||||
this.props.onRename(this.state.name)
|
() => {
|
||||||
|
if (this.props.snippet.name !== this.state.name) {
|
||||||
|
this.props.onRename(this.state.name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteButtonClick (e) {
|
handleDeleteButtonClick(e) {
|
||||||
this.props.onDelete(e)
|
this.props.onDelete(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
startRenaming () {
|
startRenaming() {
|
||||||
this.setState({
|
this.setState(
|
||||||
isRenaming: true
|
{
|
||||||
}, () => {
|
isRenaming: true
|
||||||
this.refs.name.focus()
|
},
|
||||||
this.refs.name.select()
|
() => {
|
||||||
})
|
this.refs.name.focus()
|
||||||
|
this.refs.name.select()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDragStart (e) {
|
handleDragStart(e) {
|
||||||
e.dataTransfer.dropEffect = 'move'
|
e.dataTransfer.dropEffect = 'move'
|
||||||
this.props.onDragStart(e)
|
this.props.onDragStart(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDrop (e) {
|
handleDrop(e) {
|
||||||
this.props.onDrop(e)
|
this.props.onDrop(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { isActive, snippet, isDeletable } = this.props
|
const { isActive, snippet, isDeletable } = this.props
|
||||||
return (
|
return (
|
||||||
<div styleName={isActive
|
<div styleName={isActive ? 'root--active' : 'root'}>
|
||||||
? 'root--active'
|
{!this.state.isRenaming ? (
|
||||||
: 'root'
|
<button
|
||||||
}
|
styleName='button'
|
||||||
>
|
onClick={e => this.handleClick(e)}
|
||||||
{!this.state.isRenaming
|
onDoubleClick={e => this.handleRenameClick(e)}
|
||||||
? <button styleName='button'
|
onContextMenu={e => this.handleContextMenu(e)}
|
||||||
onClick={(e) => this.handleClick(e)}
|
onDragStart={e => this.handleDragStart(e)}
|
||||||
onDoubleClick={(e) => this.handleRenameClick(e)}
|
onDrop={e => this.handleDrop(e)}
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
|
||||||
onDragStart={(e) => this.handleDragStart(e)}
|
|
||||||
onDrop={(e) => this.handleDrop(e)}
|
|
||||||
draggable='true'
|
draggable='true'
|
||||||
>
|
>
|
||||||
{snippet.name.trim().length > 0
|
{snippet.name.trim().length > 0 ? (
|
||||||
? snippet.name
|
snippet.name
|
||||||
: <span>
|
) : (
|
||||||
{i18n.__('Unnamed')}
|
<span>{i18n.__('Unnamed')}</span>
|
||||||
</span>
|
)}
|
||||||
}
|
|
||||||
</button>
|
</button>
|
||||||
: <input styleName='input'
|
) : (
|
||||||
|
<input
|
||||||
|
styleName='input'
|
||||||
ref='name'
|
ref='name'
|
||||||
value={this.state.name}
|
value={this.state.name}
|
||||||
onChange={(e) => this.handleNameInputChange(e)}
|
onChange={e => this.handleNameInputChange(e)}
|
||||||
onBlur={(e) => this.handleNameInputBlur(e)}
|
onBlur={e => this.handleNameInputBlur(e)}
|
||||||
onKeyDown={(e) => this.handleNameInputKeyDown(e)}
|
onKeyDown={e => this.handleNameInputKeyDown(e)}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
{isDeletable &&
|
{isDeletable && (
|
||||||
<button styleName='deleteButton'
|
<button
|
||||||
onClick={(e) => this.handleDeleteButtonClick(e)}
|
styleName='deleteButton'
|
||||||
|
onClick={e => this.handleDeleteButtonClick(e)}
|
||||||
>
|
>
|
||||||
<i className='fa fa-times' />
|
<i className='fa fa-times' />
|
||||||
</button>
|
</button>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SnippetTab.propTypes = {
|
SnippetTab.propTypes = {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CSSModules(SnippetTab, styles)
|
export default CSSModules(SnippetTab, styles)
|
||||||
|
|||||||
@@ -54,8 +54,9 @@ const StorageItem = ({
|
|||||||
onDragEnter={handleDragEnter}
|
onDragEnter={handleDragEnter}
|
||||||
onDragLeave={handleDragLeave}
|
onDragLeave={handleDragLeave}
|
||||||
>
|
>
|
||||||
{!isFolded &&
|
{!isFolded && (
|
||||||
<DraggableIcon className={styles['folderList-item-reorder']} />}
|
<DraggableIcon className={styles['folderList-item-reorder']} />
|
||||||
|
)}
|
||||||
<span
|
<span
|
||||||
styleName={
|
styleName={
|
||||||
isFolded ? 'folderList-item-name--folded' : 'folderList-item-name'
|
isFolded ? 'folderList-item-name--folded' : 'folderList-item-name'
|
||||||
@@ -70,11 +71,12 @@ const StorageItem = ({
|
|||||||
? _.truncate(folderName, { length: 1, omission: '' })
|
? _.truncate(folderName, { length: 1, omission: '' })
|
||||||
: folderName}
|
: folderName}
|
||||||
</span>
|
</span>
|
||||||
{!isFolded &&
|
{!isFolded && _.isNumber(noteCount) && (
|
||||||
_.isNumber(noteCount) &&
|
<span styleName='folderList-item-noteCount'>{noteCount}</span>
|
||||||
<span styleName='folderList-item-noteCount'>{noteCount}</span>}
|
)}
|
||||||
{isFolded &&
|
{isFolded && (
|
||||||
<span styleName='folderList-item-tooltip'>{folderName}</span>}
|
<span styleName='folderList-item-tooltip'>{folderName}</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Micro component for showing StorageList
|
* @fileoverview Micro component for showing StorageList
|
||||||
*/
|
*/
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styles from './StorageList.styl'
|
import styles from './StorageList.styl'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Array} storageList
|
* @param {Array} storageList
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const StorageList = ({storageList, isFolded}) => (
|
const StorageList = ({ storageList, isFolded }) => (
|
||||||
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
|
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
|
||||||
{storageList.length > 0 ? storageList : (
|
{storageList.length > 0 ? (
|
||||||
|
storageList
|
||||||
|
) : (
|
||||||
<div styleName='storageList-empty'>No storage mount.</div>
|
<div styleName='storageList-empty'>No storage mount.</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,30 +1,58 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Micro component for showing TagList.
|
* @fileoverview Micro component for showing TagList.
|
||||||
*/
|
*/
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styles from './TagListItem.styl'
|
import styles from './TagListItem.styl'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {Function} handleClickTagListItem
|
* @param {Function} handleClickTagListItem
|
||||||
* @param {Function} handleClickNarrowToTag
|
* @param {Function} handleClickNarrowToTag
|
||||||
* @param {boolean} isActive
|
* @param {boolean} isActive
|
||||||
* @param {boolean} isRelated
|
* @param {boolean} isRelated
|
||||||
* @param {string} bgColor tab backgroundColor
|
* @param {string} bgColor tab backgroundColor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count, color}) => (
|
const TagListItem = ({
|
||||||
<div styleName='tagList-itemContainer' onContextMenu={e => handleContextMenu(e, name)}>
|
name,
|
||||||
{isRelated
|
handleClickTagListItem,
|
||||||
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
|
handleClickNarrowToTag,
|
||||||
|
handleContextMenu,
|
||||||
|
isActive,
|
||||||
|
isRelated,
|
||||||
|
count,
|
||||||
|
color
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
styleName='tagList-itemContainer'
|
||||||
|
onContextMenu={e => handleContextMenu(e, name)}
|
||||||
|
>
|
||||||
|
{isRelated ? (
|
||||||
|
<button
|
||||||
|
styleName={
|
||||||
|
isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'
|
||||||
|
}
|
||||||
|
onClick={() => handleClickNarrowToTag(name)}
|
||||||
|
>
|
||||||
<i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
|
<i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
|
||||||
</button>
|
</button>
|
||||||
: <div styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} />
|
) : (
|
||||||
}
|
<div
|
||||||
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
|
styleName={
|
||||||
<span styleName='tagList-item-color' style={{backgroundColor: color || 'transparent'}} />
|
isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
styleName={isActive ? 'tagList-item-active' : 'tagList-item'}
|
||||||
|
onClick={() => handleClickTagListItem(name)}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
styleName='tagList-item-color'
|
||||||
|
style={{ backgroundColor: color || 'transparent' }}
|
||||||
|
/>
|
||||||
<span styleName='tagList-item-name'>
|
<span styleName='tagList-item-name'>
|
||||||
{`# ${name}`}
|
{`# ${name}`}
|
||||||
<span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span>
|
<span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span>
|
||||||
|
|||||||
@@ -11,17 +11,20 @@ import styles from './TodoListPercentage.styl'
|
|||||||
* @param {number} percentageOfTodo
|
* @param {number} percentageOfTodo
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const TodoListPercentage = ({
|
const TodoListPercentage = ({ percentageOfTodo, onClearCheckboxClick }) => (
|
||||||
percentageOfTodo, onClearCheckboxClick
|
<div
|
||||||
}) => (
|
styleName='percentageBar'
|
||||||
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
|
style={{ display: isNaN(percentageOfTodo) ? 'none' : '' }}
|
||||||
<div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}>
|
>
|
||||||
|
<div styleName='progressBar' style={{ width: `${percentageOfTodo}%` }}>
|
||||||
<div styleName='progressBarInner'>
|
<div styleName='progressBarInner'>
|
||||||
<p styleName='percentageText'>{percentageOfTodo}%</p>
|
<p styleName='percentageText'>{percentageOfTodo}%</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='todoClear'>
|
<div styleName='todoClear'>
|
||||||
<p styleName='todoClearText' onClick={(e) => onClearCheckboxClick(e)}>clear</p>
|
<p styleName='todoClearText' onClick={e => onClearCheckboxClick(e)}>
|
||||||
|
clear
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,18 +8,21 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './TodoProcess.styl'
|
import styles from './TodoProcess.styl'
|
||||||
|
|
||||||
const TodoProcess = ({
|
const TodoProcess = ({
|
||||||
todoStatus: {
|
todoStatus: { total: totalTodo, completed: completedTodo }
|
||||||
total: totalTodo,
|
|
||||||
completed: completedTodo
|
|
||||||
}
|
|
||||||
}) => (
|
}) => (
|
||||||
<div styleName='todo-process' style={{display: totalTodo > 0 ? '' : 'none'}}>
|
<div
|
||||||
|
styleName='todo-process'
|
||||||
|
style={{ display: totalTodo > 0 ? '' : 'none' }}
|
||||||
|
>
|
||||||
<div styleName='todo-process-text'>
|
<div styleName='todo-process-text'>
|
||||||
<i className='fa fa-fw fa-check-square-o' />
|
<i className='fa fa-fw fa-check-square-o' />
|
||||||
{completedTodo} of {totalTodo}
|
{completedTodo} of {totalTodo}
|
||||||
</div>
|
</div>
|
||||||
<div styleName='todo-process-bar'>
|
<div styleName='todo-process-bar'>
|
||||||
<div styleName='todo-process-bar--inner' style={{width: parseInt(completedTodo / totalTodo * 100) + '%'}} />
|
<div
|
||||||
|
styleName='todo-process-bar--inner'
|
||||||
|
style={{ width: parseInt((completedTodo / totalTodo) * 100) + '%' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ const darkThemeStyling = `
|
|||||||
fill: white;
|
fill: white;
|
||||||
}`
|
}`
|
||||||
|
|
||||||
function getRandomInt (min, max) {
|
function getRandomInt(min, max) {
|
||||||
return Math.floor(Math.random() * (max - min)) + min
|
return Math.floor(Math.random() * (max - min)) + min
|
||||||
}
|
}
|
||||||
|
|
||||||
function getId () {
|
function getId() {
|
||||||
const pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
const pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||||
let id = 'm-'
|
let id = 'm-'
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
@@ -20,7 +20,7 @@ function getId () {
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
function render (element, content, theme, enableHTMLLabel) {
|
function render(element, content, theme, enableHTMLLabel) {
|
||||||
try {
|
try {
|
||||||
const height = element.attributes.getNamedItem('data-height')
|
const height = element.attributes.getNamedItem('data-height')
|
||||||
const isPredefined = height && height.value !== 'undefined'
|
const isPredefined = height && height.value !== 'undefined'
|
||||||
@@ -29,7 +29,9 @@ function render (element, content, theme, enableHTMLLabel) {
|
|||||||
element.style.height = height.value + 'vh'
|
element.style.height = height.value + 'vh'
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDarkTheme = uiThemes.some(item => item.name === theme && item.isDark)
|
const isDarkTheme = uiThemes.some(
|
||||||
|
item => item.name === theme && item.isDark
|
||||||
|
)
|
||||||
|
|
||||||
mermaidAPI.initialize({
|
mermaidAPI.initialize({
|
||||||
theme: isDarkTheme ? 'dark' : 'default',
|
theme: isDarkTheme ? 'dark' : 'default',
|
||||||
@@ -42,7 +44,7 @@ function render (element, content, theme, enableHTMLLabel) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
mermaidAPI.render(getId(), content, (svgGraph) => {
|
mermaidAPI.render(getId(), content, svgGraph => {
|
||||||
element.innerHTML = svgGraph
|
element.innerHTML = svgGraph
|
||||||
|
|
||||||
if (!isPredefined) {
|
if (!isPredefined) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import CSSModules from 'react-css-modules'
|
import CSSModules from 'react-css-modules'
|
||||||
|
|
||||||
export default function (component, styles) {
|
export default function(component, styles) {
|
||||||
return CSSModules(component, styles, {handleNotFoundStyleName: 'log'})
|
return CSSModules(component, styles, { handleNotFoundStyleName: 'log' })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,13 +78,13 @@ const languages = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getLocales () {
|
getLocales() {
|
||||||
return languages.reduce(function (localeList, locale) {
|
return languages.reduce(function(localeList, locale) {
|
||||||
localeList.push(locale.locale)
|
localeList.push(locale.locale)
|
||||||
return localeList
|
return localeList
|
||||||
}, [])
|
}, [])
|
||||||
},
|
},
|
||||||
getLanguages () {
|
getLanguages() {
|
||||||
return languages
|
return languages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,43 @@
|
|||||||
class MutableMap {
|
class MutableMap {
|
||||||
constructor (iterable) {
|
constructor(iterable) {
|
||||||
this._map = new Map(iterable)
|
this._map = new Map(iterable)
|
||||||
Object.defineProperty(this, 'size', {
|
Object.defineProperty(this, 'size', {
|
||||||
get: () => this._map.size,
|
get: () => this._map.size,
|
||||||
set: function (value) {
|
set: function(value) {
|
||||||
this['size'] = value
|
this['size'] = value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
get (...args) {
|
get(...args) {
|
||||||
return this._map.get(...args)
|
return this._map.get(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
set (...args) {
|
set(...args) {
|
||||||
return this._map.set(...args)
|
return this._map.set(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete (...args) {
|
delete(...args) {
|
||||||
return this._map.delete(...args)
|
return this._map.delete(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
has (...args) {
|
has(...args) {
|
||||||
return this._map.has(...args)
|
return this._map.has(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
clear (...args) {
|
clear(...args) {
|
||||||
return this._map.clear(...args)
|
return this._map.clear(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
forEach (...args) {
|
forEach(...args) {
|
||||||
return this._map.forEach(...args)
|
return this._map.forEach(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
[Symbol.iterator] () {
|
[Symbol.iterator]() {
|
||||||
return this._map[Symbol.iterator]()
|
return this._map[Symbol.iterator]()
|
||||||
}
|
}
|
||||||
|
|
||||||
map (cb) {
|
map(cb) {
|
||||||
const result = []
|
const result = []
|
||||||
for (const [key, value] of this._map) {
|
for (const [key, value] of this._map) {
|
||||||
result.push(cb(value, key))
|
result.push(cb(value, key))
|
||||||
@@ -45,7 +45,7 @@ class MutableMap {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
toJS () {
|
toJS() {
|
||||||
const result = {}
|
const result = {}
|
||||||
for (let [key, value] of this._map) {
|
for (let [key, value] of this._map) {
|
||||||
if (value instanceof MutableSet || value instanceof MutableMap) {
|
if (value instanceof MutableSet || value instanceof MutableMap) {
|
||||||
@@ -58,42 +58,42 @@ class MutableMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MutableSet {
|
class MutableSet {
|
||||||
constructor (iterable) {
|
constructor(iterable) {
|
||||||
this._set = new Set(iterable)
|
this._set = new Set(iterable)
|
||||||
Object.defineProperty(this, 'size', {
|
Object.defineProperty(this, 'size', {
|
||||||
get: () => this._set.size,
|
get: () => this._set.size,
|
||||||
set: function (value) {
|
set: function(value) {
|
||||||
this['size'] = value
|
this['size'] = value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
add (...args) {
|
add(...args) {
|
||||||
return this._set.add(...args)
|
return this._set.add(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete (...args) {
|
delete(...args) {
|
||||||
return this._set.delete(...args)
|
return this._set.delete(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
forEach (...args) {
|
forEach(...args) {
|
||||||
return this._set.forEach(...args)
|
return this._set.forEach(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
[Symbol.iterator] () {
|
[Symbol.iterator]() {
|
||||||
return this._set[Symbol.iterator]()
|
return this._set[Symbol.iterator]()
|
||||||
}
|
}
|
||||||
|
|
||||||
map (cb) {
|
map(cb) {
|
||||||
const result = []
|
const result = []
|
||||||
this._set.forEach(function (value, key) {
|
this._set.forEach(function(value, key) {
|
||||||
result.push(cb(value, key))
|
result.push(cb(value, key))
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
toJS () {
|
toJS() {
|
||||||
return Array.from(this._set)
|
return Array.from(this._set)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ const BOOSTNOTERC = '.boostnoterc'
|
|||||||
const homePath = global.process.env.HOME || global.process.env.USERPROFILE
|
const homePath = global.process.env.HOME || global.process.env.USERPROFILE
|
||||||
const _boostnotercPath = path.join(homePath, BOOSTNOTERC)
|
const _boostnotercPath = path.join(homePath, BOOSTNOTERC)
|
||||||
|
|
||||||
export function parse (boostnotercPath = _boostnotercPath) {
|
export function parse(boostnotercPath = _boostnotercPath) {
|
||||||
if (!sander.existsSync(boostnotercPath)) return {}
|
if (!sander.existsSync(boostnotercPath)) return {}
|
||||||
try {
|
try {
|
||||||
return JSON.parse(sander.readFileSync(boostnotercPath).toString())
|
return JSON.parse(sander.readFileSync(boostnotercPath).toString())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e)
|
console.warn(e)
|
||||||
console.warn('Your .boostnoterc is broken so it\'s not used.')
|
console.warn("Your .boostnoterc is broken so it's not used.")
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import fs from 'fs'
|
|||||||
import consts from './consts'
|
import consts from './consts'
|
||||||
|
|
||||||
class SnippetManager {
|
class SnippetManager {
|
||||||
constructor () {
|
constructor() {
|
||||||
this.defaultSnippet = [
|
this.defaultSnippet = [
|
||||||
{
|
{
|
||||||
id: crypto.randomBytes(16).toString('hex'),
|
id: crypto.randomBytes(16).toString('hex'),
|
||||||
name: 'Dummy text',
|
name: 'Dummy text',
|
||||||
prefix: ['lorem', 'ipsum'],
|
prefix: ['lorem', 'ipsum'],
|
||||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
content:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
this.snippets = []
|
this.snippets = []
|
||||||
@@ -18,7 +19,7 @@ class SnippetManager {
|
|||||||
this.assignSnippets = this.assignSnippets.bind(this)
|
this.assignSnippets = this.assignSnippets.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
init () {
|
init() {
|
||||||
if (fs.existsSync(consts.SNIPPET_FILE)) {
|
if (fs.existsSync(consts.SNIPPET_FILE)) {
|
||||||
try {
|
try {
|
||||||
this.snippets = JSON.parse(
|
this.snippets = JSON.parse(
|
||||||
@@ -37,11 +38,11 @@ class SnippetManager {
|
|||||||
this.snippets = this.defaultSnippet
|
this.snippets = this.defaultSnippet
|
||||||
}
|
}
|
||||||
|
|
||||||
assignSnippets (snippets) {
|
assignSnippets(snippets) {
|
||||||
this.snippets = snippets
|
this.snippets = snippets
|
||||||
}
|
}
|
||||||
|
|
||||||
expandSnippet (wordBeforeCursor, cursor, cm) {
|
expandSnippet(wordBeforeCursor, cursor, cm) {
|
||||||
const templateCursorString = ':{}'
|
const templateCursorString = ':{}'
|
||||||
for (let i = 0; i < this.snippets.length; i++) {
|
for (let i = 0; i < this.snippets.length; i++) {
|
||||||
if (this.snippets[i].prefix.indexOf(wordBeforeCursor.text) === -1) {
|
if (this.snippets[i].prefix.indexOf(wordBeforeCursor.text) === -1) {
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
import { Point } from '@susisu/mte-kernel'
|
import { Point } from '@susisu/mte-kernel'
|
||||||
|
|
||||||
export default class TextEditorInterface {
|
export default class TextEditorInterface {
|
||||||
constructor (editor) {
|
constructor(editor) {
|
||||||
this.editor = editor
|
this.editor = editor
|
||||||
this.doc = editor.getDoc()
|
this.doc = editor.getDoc()
|
||||||
this.transaction = false
|
this.transaction = false
|
||||||
}
|
}
|
||||||
|
|
||||||
getCursorPosition () {
|
getCursorPosition() {
|
||||||
const { line, ch } = this.doc.getCursor()
|
const { line, ch } = this.doc.getCursor()
|
||||||
return new Point(line, ch)
|
return new Point(line, ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
setCursorPosition (pos) {
|
setCursorPosition(pos) {
|
||||||
this.doc.setCursor({
|
this.doc.setCursor({
|
||||||
line: pos.row,
|
line: pos.row,
|
||||||
ch: pos.column
|
ch: pos.column
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectionRange (range) {
|
setSelectionRange(range) {
|
||||||
this.doc.setSelection(
|
this.doc.setSelection(
|
||||||
{ line: range.start.row, ch: range.start.column },
|
{ line: range.start.row, ch: range.start.column },
|
||||||
{ line: range.end.row, ch: range.end.column }
|
{ line: range.end.row, ch: range.end.column }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
getLastRow () {
|
getLastRow() {
|
||||||
return this.doc.lineCount() - 1
|
return this.doc.lineCount() - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptsTableEdit () {
|
acceptsTableEdit() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
getLine (row) {
|
getLine(row) {
|
||||||
return this.doc.getLine(row)
|
return this.doc.getLine(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
insertLine (row, line) {
|
insertLine(row, line) {
|
||||||
const lastRow = this.getLastRow()
|
const lastRow = this.getLastRow()
|
||||||
if (row > lastRow) {
|
if (row > lastRow) {
|
||||||
const lastLine = this.getLine(lastRow)
|
const lastLine = this.getLine(lastRow)
|
||||||
@@ -56,7 +56,7 @@ export default class TextEditorInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteLine (row) {
|
deleteLine(row) {
|
||||||
const lastRow = this.getLastRow()
|
const lastRow = this.getLastRow()
|
||||||
if (row >= lastRow) {
|
if (row >= lastRow) {
|
||||||
if (lastRow > 0) {
|
if (lastRow > 0) {
|
||||||
@@ -76,15 +76,11 @@ export default class TextEditorInterface {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.doc.replaceRange(
|
this.doc.replaceRange('', { line: row, ch: 0 }, { line: row + 1, ch: 0 })
|
||||||
'',
|
|
||||||
{ line: row, ch: 0 },
|
|
||||||
{ line: row + 1, ch: 0 }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceLines (startRow, endRow, lines) {
|
replaceLines(startRow, endRow, lines) {
|
||||||
const lastRow = this.getLastRow()
|
const lastRow = this.getLastRow()
|
||||||
if (endRow > lastRow) {
|
if (endRow > lastRow) {
|
||||||
const lastLine = this.getLine(lastRow)
|
const lastLine = this.getLine(lastRow)
|
||||||
@@ -102,7 +98,7 @@ export default class TextEditorInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transact (func) {
|
transact(func) {
|
||||||
this.transaction = true
|
this.transaction = true
|
||||||
func()
|
func()
|
||||||
this.transaction = false
|
this.transaction = false
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import i18n from 'browser/lib/i18n'
|
|||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
const { dialog } = remote
|
const { dialog } = remote
|
||||||
|
|
||||||
export function confirmDeleteNote (confirmDeletion, permanent) {
|
export function confirmDeleteNote(confirmDeletion, permanent) {
|
||||||
if (confirmDeletion || permanent) {
|
if (confirmDeletion || permanent) {
|
||||||
const alertConfig = {
|
const alertConfig = {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
@@ -13,7 +13,8 @@ export function confirmDeleteNote (confirmDeletion, permanent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dialogButtonIndex = dialog.showMessageBox(
|
const dialogButtonIndex = dialog.showMessageBox(
|
||||||
remote.getCurrentWindow(), alertConfig
|
remote.getCurrentWindow(),
|
||||||
|
alertConfig
|
||||||
)
|
)
|
||||||
|
|
||||||
return dialogButtonIndex === 0
|
return dialogButtonIndex === 0
|
||||||
|
|||||||
@@ -9,41 +9,53 @@ const CODEMIRROR_EXTRA_THEME_PATH = 'extra_scripts/codemirror/theme'
|
|||||||
const isProduction = process.env.NODE_ENV === 'production'
|
const isProduction = process.env.NODE_ENV === 'production'
|
||||||
|
|
||||||
const paths = [
|
const paths = [
|
||||||
isProduction ? path.join(app.getAppPath(), CODEMIRROR_THEME_PATH) : path.resolve(CODEMIRROR_THEME_PATH),
|
isProduction
|
||||||
isProduction ? path.join(app.getAppPath(), CODEMIRROR_EXTRA_THEME_PATH) : path.resolve(CODEMIRROR_EXTRA_THEME_PATH)
|
? path.join(app.getAppPath(), CODEMIRROR_THEME_PATH)
|
||||||
|
: path.resolve(CODEMIRROR_THEME_PATH),
|
||||||
|
isProduction
|
||||||
|
? path.join(app.getAppPath(), CODEMIRROR_EXTRA_THEME_PATH)
|
||||||
|
: path.resolve(CODEMIRROR_EXTRA_THEME_PATH)
|
||||||
]
|
]
|
||||||
|
|
||||||
const themes = paths
|
const themes = paths
|
||||||
.map(directory => fs.readdirSync(directory).map(file => {
|
.map(directory =>
|
||||||
const name = file.substring(0, file.lastIndexOf('.'))
|
fs.readdirSync(directory).map(file => {
|
||||||
|
const name = file.substring(0, file.lastIndexOf('.'))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
path: path.join(directory, file),
|
path: path.join(directory, file),
|
||||||
className: `cm-s-${name}`
|
className: `cm-s-${name}`
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
.reduce((accumulator, value) => accumulator.concat(value), [])
|
.reduce((accumulator, value) => accumulator.concat(value), [])
|
||||||
.sort((a, b) => a.name.localeCompare(b.name))
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
|
||||||
themes.splice(themes.findIndex(({ name }) => name === 'solarized'), 1, {
|
themes.splice(
|
||||||
name: 'solarized dark',
|
themes.findIndex(({ name }) => name === 'solarized'),
|
||||||
path: path.join(paths[0], 'solarized.css'),
|
1,
|
||||||
className: `cm-s-solarized cm-s-dark`
|
{
|
||||||
}, {
|
name: 'solarized dark',
|
||||||
name: 'solarized light',
|
path: path.join(paths[0], 'solarized.css'),
|
||||||
path: path.join(paths[0], 'solarized.css'),
|
className: `cm-s-solarized cm-s-dark`
|
||||||
className: `cm-s-solarized cm-s-light`
|
},
|
||||||
})
|
{
|
||||||
|
name: 'solarized light',
|
||||||
|
path: path.join(paths[0], 'solarized.css'),
|
||||||
|
className: `cm-s-solarized cm-s-light`
|
||||||
|
}
|
||||||
|
)
|
||||||
themes.splice(0, 0, {
|
themes.splice(0, 0, {
|
||||||
name: 'default',
|
name: 'default',
|
||||||
path: path.join(paths[0], 'elegant.css'),
|
path: path.join(paths[0], 'elegant.css'),
|
||||||
className: `cm-s-default`
|
className: `cm-s-default`
|
||||||
})
|
})
|
||||||
|
|
||||||
const snippetFile = process.env.NODE_ENV !== 'test'
|
const snippetFile =
|
||||||
? path.join(app.getPath('userData'), 'snippets.json')
|
process.env.NODE_ENV !== 'test'
|
||||||
: '' // return nothing as we specified different path to snippets.json in test
|
? path.join(app.getPath('userData'), 'snippets.json')
|
||||||
|
: '' // return nothing as we specified different path to snippets.json in test
|
||||||
|
|
||||||
const consts = {
|
const consts = {
|
||||||
FOLDER_COLORS: [
|
FOLDER_COLORS: [
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, MenuItem } = remote
|
const { Menu, MenuItem } = remote
|
||||||
|
|
||||||
function popup (templates) {
|
function popup(templates) {
|
||||||
const menu = new Menu()
|
const menu = new Menu()
|
||||||
templates.forEach((item) => {
|
templates.forEach(item => {
|
||||||
menu.append(new MenuItem(item))
|
menu.append(new MenuItem(item))
|
||||||
})
|
})
|
||||||
menu.popup(remote.getCurrentWindow())
|
menu.popup(remote.getCurrentWindow())
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
|
||||||
const {remote} = require('electron')
|
const { remote } = require('electron')
|
||||||
const {Menu} = remote.require('electron')
|
const { Menu } = remote.require('electron')
|
||||||
const {clipboard} = remote.require('electron')
|
const { clipboard } = remote.require('electron')
|
||||||
const {shell} = remote.require('electron')
|
const { shell } = remote.require('electron')
|
||||||
const spellcheck = require('./spellcheck')
|
const spellcheck = require('./spellcheck')
|
||||||
const uri2path = require('file-uri-to-path')
|
const uri2path = require('file-uri-to-path')
|
||||||
|
|
||||||
@@ -16,11 +16,16 @@ const uri2path = require('file-uri-to-path')
|
|||||||
* @param {MouseEvent} event that has triggered the creation of the context menu
|
* @param {MouseEvent} event that has triggered the creation of the context menu
|
||||||
* @returns {Electron.Menu} The created electron context menu
|
* @returns {Electron.Menu} The created electron context menu
|
||||||
*/
|
*/
|
||||||
const buildEditorContextMenu = function (editor, event) {
|
const buildEditorContextMenu = function(editor, event) {
|
||||||
if (editor == null || event == null || event.pageX == null || event.pageY == null) {
|
if (
|
||||||
|
editor == null ||
|
||||||
|
event == null ||
|
||||||
|
event.pageX == null ||
|
||||||
|
event.pageY == null
|
||||||
|
) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const cursor = editor.coordsChar({left: event.pageX, top: event.pageY})
|
const cursor = editor.coordsChar({ left: event.pageX, top: event.pageY })
|
||||||
const wordRange = editor.findWordAt(cursor)
|
const wordRange = editor.findWordAt(cursor)
|
||||||
const word = editor.getRange(wordRange.anchor, wordRange.head)
|
const word = editor.getRange(wordRange.anchor, wordRange.head)
|
||||||
const existingMarks = editor.findMarks(wordRange.anchor, wordRange.head) || []
|
const existingMarks = editor.findMarks(wordRange.anchor, wordRange.head) || []
|
||||||
@@ -40,30 +45,44 @@ const buildEditorContextMenu = function (editor, event) {
|
|||||||
isMisspelled: isMisspelled,
|
isMisspelled: isMisspelled,
|
||||||
spellingSuggestions: suggestion
|
spellingSuggestions: suggestion
|
||||||
}
|
}
|
||||||
const template = [{
|
const template = [
|
||||||
role: 'cut'
|
{
|
||||||
}, {
|
role: 'cut'
|
||||||
role: 'copy'
|
},
|
||||||
}, {
|
{
|
||||||
role: 'paste'
|
role: 'copy'
|
||||||
}, {
|
},
|
||||||
role: 'selectall'
|
{
|
||||||
}]
|
role: 'paste'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'selectall'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
if (selection.isMisspelled) {
|
if (selection.isMisspelled) {
|
||||||
const suggestions = selection.spellingSuggestions
|
const suggestions = selection.spellingSuggestions
|
||||||
template.unshift.apply(template, suggestions.map(function (suggestion) {
|
template.unshift.apply(
|
||||||
return {
|
template,
|
||||||
label: suggestion,
|
suggestions
|
||||||
click: function (suggestion) {
|
.map(function(suggestion) {
|
||||||
if (editor != null) {
|
return {
|
||||||
editor.replaceRange(suggestion.label, wordRange.anchor, wordRange.head)
|
label: suggestion,
|
||||||
|
click: function(suggestion) {
|
||||||
|
if (editor != null) {
|
||||||
|
editor.replaceRange(
|
||||||
|
suggestion.label,
|
||||||
|
wordRange.anchor,
|
||||||
|
wordRange.head
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
.concat({
|
||||||
}).concat({
|
type: 'separator'
|
||||||
type: 'separator'
|
})
|
||||||
}))
|
)
|
||||||
}
|
}
|
||||||
return Menu.buildFromTemplate(template)
|
return Menu.buildFromTemplate(template)
|
||||||
}
|
}
|
||||||
@@ -74,19 +93,30 @@ const buildEditorContextMenu = function (editor, event) {
|
|||||||
* @param {MouseEvent} event that has triggered the creation of the context menu
|
* @param {MouseEvent} event that has triggered the creation of the context menu
|
||||||
* @returns {Electron.Menu} The created electron context menu
|
* @returns {Electron.Menu} The created electron context menu
|
||||||
*/
|
*/
|
||||||
const buildMarkdownPreviewContextMenu = function (markdownPreview, event) {
|
const buildMarkdownPreviewContextMenu = function(markdownPreview, event) {
|
||||||
if (markdownPreview == null || event == null || event.pageX == null || event.pageY == null) {
|
if (
|
||||||
|
markdownPreview == null ||
|
||||||
|
event == null ||
|
||||||
|
event.pageX == null ||
|
||||||
|
event.pageY == null
|
||||||
|
) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default context menu inclusions
|
// Default context menu inclusions
|
||||||
const template = [{
|
const template = [
|
||||||
role: 'copy'
|
{
|
||||||
}, {
|
role: 'copy'
|
||||||
role: 'selectall'
|
},
|
||||||
}]
|
{
|
||||||
|
role: 'selectall'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
if (event.target.tagName.toLowerCase() === 'a' && event.target.getAttribute('href')) {
|
if (
|
||||||
|
event.target.tagName.toLowerCase() === 'a' &&
|
||||||
|
event.target.getAttribute('href')
|
||||||
|
) {
|
||||||
// Link opener for files on the local system pointed to by href
|
// Link opener for files on the local system pointed to by href
|
||||||
const href = event.target.href
|
const href = event.target.href
|
||||||
const isLocalFile = href.startsWith('file:')
|
const isLocalFile = href.startsWith('file:')
|
||||||
@@ -94,31 +124,29 @@ const buildMarkdownPreviewContextMenu = function (markdownPreview, event) {
|
|||||||
const absPath = uri2path(href)
|
const absPath = uri2path(href)
|
||||||
try {
|
try {
|
||||||
if (fs.lstatSync(absPath).isFile()) {
|
if (fs.lstatSync(absPath).isFile()) {
|
||||||
template.push(
|
template.push({
|
||||||
{
|
label: i18n.__('Show in explorer'),
|
||||||
label: i18n.__('Show in explorer'),
|
click: e => shell.showItemInFolder(absPath)
|
||||||
click: (e) => shell.showItemInFolder(absPath)
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('Error while evaluating if the file is locally available', e)
|
console.log(
|
||||||
|
'Error while evaluating if the file is locally available',
|
||||||
|
e
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add option to context menu to copy url
|
// Add option to context menu to copy url
|
||||||
template.push(
|
template.push({
|
||||||
{
|
label: i18n.__('Copy Url'),
|
||||||
label: i18n.__('Copy Url'),
|
click: e => clipboard.writeText(href)
|
||||||
click: (e) => clipboard.writeText(href)
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return Menu.buildFromTemplate(template)
|
return Menu.buildFromTemplate(template)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports =
|
module.exports = {
|
||||||
{
|
|
||||||
buildEditorContextMenu: buildEditorContextMenu,
|
buildEditorContextMenu: buildEditorContextMenu,
|
||||||
buildMarkdownPreviewContextMenu: buildMarkdownPreviewContextMenu
|
buildMarkdownPreviewContextMenu: buildMarkdownPreviewContextMenu
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export default function convertModeName (name) {
|
export default function convertModeName(name) {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'ejs':
|
case 'ejs':
|
||||||
return 'Embedded Javascript'
|
return 'Embedded Javascript'
|
||||||
|
|||||||
@@ -3,8 +3,19 @@ import 'codemirror-mode-elixir'
|
|||||||
|
|
||||||
const stylusCodeInfo = CodeMirror.modeInfo.find(info => info.name === 'Stylus')
|
const stylusCodeInfo = CodeMirror.modeInfo.find(info => info.name === 'Stylus')
|
||||||
if (stylusCodeInfo == null) {
|
if (stylusCodeInfo == null) {
|
||||||
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
|
CodeMirror.modeInfo.push({
|
||||||
|
name: 'Stylus',
|
||||||
|
mime: 'text/x-styl',
|
||||||
|
mode: 'stylus',
|
||||||
|
ext: ['styl'],
|
||||||
|
alias: ['styl']
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
stylusCodeInfo.alias = ['styl']
|
stylusCodeInfo.alias = ['styl']
|
||||||
}
|
}
|
||||||
CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']})
|
CodeMirror.modeInfo.push({
|
||||||
|
name: 'Elixir',
|
||||||
|
mime: 'text/x-elixir',
|
||||||
|
mode: 'elixir',
|
||||||
|
ext: ['ex']
|
||||||
|
})
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import moment from 'moment'
|
|||||||
* @param {mixed}
|
* @param {mixed}
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
export function formatDate (date) {
|
export function formatDate(date) {
|
||||||
const m = moment(date)
|
const m = moment(date)
|
||||||
if (!m.isValid()) {
|
if (!m.isValid()) {
|
||||||
throw Error('Invalid argument.')
|
throw Error('Invalid argument.')
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleField = 'title') {
|
export function findNoteTitle(
|
||||||
|
value,
|
||||||
|
enableFrontMatterTitle,
|
||||||
|
frontMatterTitleField = 'title'
|
||||||
|
) {
|
||||||
const splitted = value.split('\n')
|
const splitted = value.split('\n')
|
||||||
let title = null
|
let title = null
|
||||||
let isInsideCodeBlock = false
|
let isInsideCodeBlock = false
|
||||||
@@ -6,8 +10,13 @@ export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleFi
|
|||||||
if (splitted[0] === '---') {
|
if (splitted[0] === '---') {
|
||||||
let line = 0
|
let line = 0
|
||||||
while (++line < splitted.length) {
|
while (++line < splitted.length) {
|
||||||
if (enableFrontMatterTitle && splitted[line].startsWith(frontMatterTitleField + ':')) {
|
if (
|
||||||
title = splitted[line].substring(frontMatterTitleField.length + 1).trim()
|
enableFrontMatterTitle &&
|
||||||
|
splitted[line].startsWith(frontMatterTitleField + ':')
|
||||||
|
) {
|
||||||
|
title = splitted[line]
|
||||||
|
.substring(frontMatterTitleField.length + 1)
|
||||||
|
.trim()
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -22,11 +31,15 @@ export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleFi
|
|||||||
if (title === null) {
|
if (title === null) {
|
||||||
splitted.some((line, index) => {
|
splitted.some((line, index) => {
|
||||||
const trimmedLine = line.trim()
|
const trimmedLine = line.trim()
|
||||||
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
|
const trimmedNextLine =
|
||||||
|
splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
|
||||||
if (trimmedLine.match('```')) {
|
if (trimmedLine.match('```')) {
|
||||||
isInsideCodeBlock = !isInsideCodeBlock
|
isInsideCodeBlock = !isInsideCodeBlock
|
||||||
}
|
}
|
||||||
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
|
if (
|
||||||
|
isInsideCodeBlock === false &&
|
||||||
|
(trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))
|
||||||
|
) {
|
||||||
title = trimmedLine
|
title = trimmedLine
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -35,7 +48,7 @@ export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleFi
|
|||||||
|
|
||||||
if (title === null) {
|
if (title === null) {
|
||||||
title = ''
|
title = ''
|
||||||
splitted.some((line) => {
|
splitted.some(line => {
|
||||||
if (line.trim().length > 0) {
|
if (line.trim().length > 0) {
|
||||||
title = line.trim()
|
title = line.trim()
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
|
|
||||||
export function findStorage (storageKey) {
|
export function findStorage(storageKey) {
|
||||||
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
if (!_.isArray(cachedStorageList))
|
||||||
const storage = _.find(cachedStorageList, {key: storageKey})
|
throw new Error("Target storage doesn't exist.")
|
||||||
if (storage === undefined) throw new Error('Target storage doesn\'t exist.')
|
const storage = _.find(cachedStorageList, { key: storageKey })
|
||||||
|
if (storage === undefined) throw new Error("Target storage doesn't exist.")
|
||||||
|
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
export function getTodoStatus (content) {
|
export function getTodoStatus(content) {
|
||||||
const splitted = content.split('\n')
|
const splitted = content.split('\n')
|
||||||
let numberOfTodo = 0
|
let numberOfTodo = 0
|
||||||
let numberOfCompletedTodo = 0
|
let numberOfCompletedTodo = 0
|
||||||
|
|
||||||
splitted.forEach((line) => {
|
splitted.forEach(line => {
|
||||||
const trimmedLine = line.trim().replace(/^(>\s*)*/, '')
|
const trimmedLine = line.trim().replace(/^(>\s*)*/, '')
|
||||||
if (trimmedLine.match(/^[+\-*] \[(\s|x)] ./i)) {
|
if (trimmedLine.match(/^[+\-*] \[(\s|x)] ./i)) {
|
||||||
numberOfTodo++
|
numberOfTodo++
|
||||||
@@ -19,7 +19,7 @@ export function getTodoStatus (content) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTodoPercentageOfCompleted (content) {
|
export function getTodoPercentageOfCompleted(content) {
|
||||||
const state = getTodoStatus(content)
|
const state = getTodoStatus(content)
|
||||||
return Math.floor(state.completed / state.total * 100)
|
return Math.floor((state.completed / state.total) * 100)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function decodeEntities (text) {
|
export function decodeEntities(text) {
|
||||||
var entities = [
|
var entities = [
|
||||||
['apos', '\''],
|
['apos', "'"],
|
||||||
['amp', '&'],
|
['amp', '&'],
|
||||||
['lt', '<'],
|
['lt', '<'],
|
||||||
['gt', '>'],
|
['gt', '>'],
|
||||||
@@ -24,16 +24,16 @@ export function decodeEntities (text) {
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encodeEntities (text) {
|
export function encodeEntities(text) {
|
||||||
const entities = [
|
const entities = [
|
||||||
['\'', 'apos'],
|
["'", 'apos'],
|
||||||
['<', 'lt'],
|
['<', 'lt'],
|
||||||
['>', 'gt'],
|
['>', 'gt'],
|
||||||
['\\?', '#63'],
|
['\\?', '#63'],
|
||||||
['\\$', '#36']
|
['\\$', '#36']
|
||||||
]
|
]
|
||||||
|
|
||||||
entities.forEach((entity) => {
|
entities.forEach(entity => {
|
||||||
text = text.replace(new RegExp(entity[0], 'g'), `&${entity[1]};`)
|
text = text.replace(new RegExp(entity[0], 'g'), `&${entity[1]};`)
|
||||||
})
|
})
|
||||||
return text
|
return text
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ const i18n = new (require('i18n-2'))({
|
|||||||
// setup some locales - other locales default to the first locale
|
// setup some locales - other locales default to the first locale
|
||||||
locales: getLocales(),
|
locales: getLocales(),
|
||||||
extension: '.json',
|
extension: '.json',
|
||||||
directory: process.env.NODE_ENV === 'production'
|
directory:
|
||||||
? path.join(app.getAppPath(), './locales')
|
process.env.NODE_ENV === 'production'
|
||||||
: path.resolve('./locales'),
|
? path.join(app.getAppPath(), './locales')
|
||||||
|
: path.resolve('./locales'),
|
||||||
devMode: false
|
devMode: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const uuidv4 = require('uuid/v4')
|
const uuidv4 = require('uuid/v4')
|
||||||
|
|
||||||
module.exports = function (uuid) {
|
module.exports = function(uuid) {
|
||||||
if (typeof uuid === typeof true && uuid) {
|
if (typeof uuid === typeof true && uuid) {
|
||||||
return uuidv4()
|
return uuidv4()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,44 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
module.exports = function definitionListPlugin (md) {
|
module.exports = function definitionListPlugin(md) {
|
||||||
var isSpace = md.utils.isSpace
|
var isSpace = md.utils.isSpace
|
||||||
|
|
||||||
// Search `[:~][\n ]`, returns next pos after marker on success
|
// Search `[:~][\n ]`, returns next pos after marker on success
|
||||||
// or -1 on fail.
|
// or -1 on fail.
|
||||||
function skipMarker (state, line) {
|
function skipMarker(state, line) {
|
||||||
let start = state.bMarks[line] + state.tShift[line]
|
let start = state.bMarks[line] + state.tShift[line]
|
||||||
const max = state.eMarks[line]
|
const max = state.eMarks[line]
|
||||||
|
|
||||||
if (start >= max) { return -1 }
|
if (start >= max) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
// Check bullet
|
// Check bullet
|
||||||
const marker = state.src.charCodeAt(start++)
|
const marker = state.src.charCodeAt(start++)
|
||||||
if (marker !== 0x7E/* ~ */ && marker !== 0x3A/* : */) { return -1 }
|
if (marker !== 0x7e /* ~ */ && marker !== 0x3a /* : */) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
const pos = state.skipSpaces(start)
|
const pos = state.skipSpaces(start)
|
||||||
|
|
||||||
// require space after ":"
|
// require space after ":"
|
||||||
if (start === pos) { return -1 }
|
if (start === pos) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
return start
|
return start
|
||||||
}
|
}
|
||||||
|
|
||||||
function markTightParagraphs (state, idx) {
|
function markTightParagraphs(state, idx) {
|
||||||
const level = state.level + 2
|
const level = state.level + 2
|
||||||
|
|
||||||
let i
|
let i
|
||||||
let l
|
let l
|
||||||
for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
|
for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
|
||||||
if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
|
if (
|
||||||
|
state.tokens[i].level === level &&
|
||||||
|
state.tokens[i].type === 'paragraph_open'
|
||||||
|
) {
|
||||||
state.tokens[i + 2].hidden = true
|
state.tokens[i + 2].hidden = true
|
||||||
state.tokens[i].hidden = true
|
state.tokens[i].hidden = true
|
||||||
i += 2
|
i += 2
|
||||||
@@ -37,7 +46,7 @@ module.exports = function definitionListPlugin (md) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deflist (state, startLine, endLine, silent) {
|
function deflist(state, startLine, endLine, silent) {
|
||||||
var ch,
|
var ch,
|
||||||
contentStart,
|
contentStart,
|
||||||
ddLine,
|
ddLine,
|
||||||
@@ -63,28 +72,38 @@ module.exports = function definitionListPlugin (md) {
|
|||||||
|
|
||||||
if (silent) {
|
if (silent) {
|
||||||
// quirk: validation mode validates a dd block only, not a whole deflist
|
// quirk: validation mode validates a dd block only, not a whole deflist
|
||||||
if (state.ddIndent < 0) { return false }
|
if (state.ddIndent < 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return skipMarker(state, startLine) >= 0
|
return skipMarker(state, startLine) >= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
nextLine = startLine + 1
|
nextLine = startLine + 1
|
||||||
if (nextLine >= endLine) { return false }
|
if (nextLine >= endLine) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (state.isEmpty(nextLine)) {
|
if (state.isEmpty(nextLine)) {
|
||||||
nextLine++
|
nextLine++
|
||||||
if (nextLine >= endLine) { return false }
|
if (nextLine >= endLine) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.sCount[nextLine] < state.blkIndent) { return false }
|
if (state.sCount[nextLine] < state.blkIndent) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
contentStart = skipMarker(state, nextLine)
|
contentStart = skipMarker(state, nextLine)
|
||||||
if (contentStart < 0) { return false }
|
if (contentStart < 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Start list
|
// Start list
|
||||||
listTokIdx = state.tokens.length
|
listTokIdx = state.tokens.length
|
||||||
tight = true
|
tight = true
|
||||||
|
|
||||||
token = state.push('dl_open', 'dl', 1)
|
token = state.push('dl_open', 'dl', 1)
|
||||||
token.map = listLines = [ startLine, 0 ]
|
token.map = listLines = [startLine, 0]
|
||||||
|
|
||||||
//
|
//
|
||||||
// Iterate list items
|
// Iterate list items
|
||||||
@@ -100,34 +119,38 @@ module.exports = function definitionListPlugin (md) {
|
|||||||
// needed to break out of the second one
|
// needed to break out of the second one
|
||||||
//
|
//
|
||||||
/* eslint no-labels:0,block-scoped-var:0 */
|
/* eslint no-labels:0,block-scoped-var:0 */
|
||||||
OUTER:
|
OUTER: for (;;) {
|
||||||
for (;;) {
|
|
||||||
prevEmptyEnd = false
|
prevEmptyEnd = false
|
||||||
|
|
||||||
token = state.push('dt_open', 'dt', 1)
|
token = state.push('dt_open', 'dt', 1)
|
||||||
token.map = [ dtLine, dtLine ]
|
token.map = [dtLine, dtLine]
|
||||||
|
|
||||||
token = state.push('inline', '', 0)
|
token = state.push('inline', '', 0)
|
||||||
token.map = [ dtLine, dtLine ]
|
token.map = [dtLine, dtLine]
|
||||||
token.content = state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim()
|
token.content = state
|
||||||
|
.getLines(dtLine, dtLine + 1, state.blkIndent, false)
|
||||||
|
.trim()
|
||||||
token.children = []
|
token.children = []
|
||||||
|
|
||||||
token = state.push('dt_close', 'dt', -1)
|
token = state.push('dt_close', 'dt', -1)
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
token = state.push('dd_open', 'dd', 1)
|
token = state.push('dd_open', 'dd', 1)
|
||||||
token.map = itemLines = [ ddLine, 0 ]
|
token.map = itemLines = [ddLine, 0]
|
||||||
|
|
||||||
pos = contentStart
|
pos = contentStart
|
||||||
max = state.eMarks[ddLine]
|
max = state.eMarks[ddLine]
|
||||||
offset = state.sCount[ddLine] + contentStart - (state.bMarks[ddLine] + state.tShift[ddLine])
|
offset =
|
||||||
|
state.sCount[ddLine] +
|
||||||
|
contentStart -
|
||||||
|
(state.bMarks[ddLine] + state.tShift[ddLine])
|
||||||
|
|
||||||
while (pos < max) {
|
while (pos < max) {
|
||||||
ch = state.src.charCodeAt(pos)
|
ch = state.src.charCodeAt(pos)
|
||||||
|
|
||||||
if (isSpace(ch)) {
|
if (isSpace(ch)) {
|
||||||
if (ch === 0x09) {
|
if (ch === 0x09) {
|
||||||
offset += 4 - offset % 4
|
offset += 4 - (offset % 4)
|
||||||
} else {
|
} else {
|
||||||
offset++
|
offset++
|
||||||
}
|
}
|
||||||
@@ -153,8 +176,11 @@ module.exports = function definitionListPlugin (md) {
|
|||||||
state.parentType = 'deflist'
|
state.parentType = 'deflist'
|
||||||
|
|
||||||
newEndLine = ddLine
|
newEndLine = ddLine
|
||||||
while (++newEndLine < endLine && (state.sCount[newEndLine] >= state.sCount[ddLine] || state.isEmpty(newEndLine))) {
|
while (
|
||||||
}
|
++newEndLine < endLine &&
|
||||||
|
(state.sCount[newEndLine] >= state.sCount[ddLine] ||
|
||||||
|
state.isEmpty(newEndLine))
|
||||||
|
) {}
|
||||||
|
|
||||||
oldLineMax = state.lineMax
|
oldLineMax = state.lineMax
|
||||||
state.lineMax = newEndLine
|
state.lineMax = newEndLine
|
||||||
@@ -169,7 +195,7 @@ module.exports = function definitionListPlugin (md) {
|
|||||||
}
|
}
|
||||||
// Item become loose if finish with empty line,
|
// Item become loose if finish with empty line,
|
||||||
// but we should filter last element, because it means list finish
|
// but we should filter last element, because it means list finish
|
||||||
prevEmptyEnd = (state.line - ddLine) > 1 && state.isEmpty(state.line - 1)
|
prevEmptyEnd = state.line - ddLine > 1 && state.isEmpty(state.line - 1)
|
||||||
|
|
||||||
state.tShift[ddLine] = oldTShift
|
state.tShift[ddLine] = oldTShift
|
||||||
state.sCount[ddLine] = oldSCount
|
state.sCount[ddLine] = oldSCount
|
||||||
@@ -182,11 +208,17 @@ module.exports = function definitionListPlugin (md) {
|
|||||||
|
|
||||||
itemLines[1] = nextLine = state.line
|
itemLines[1] = nextLine = state.line
|
||||||
|
|
||||||
if (nextLine >= endLine) { break OUTER }
|
if (nextLine >= endLine) {
|
||||||
|
break OUTER
|
||||||
|
}
|
||||||
|
|
||||||
if (state.sCount[nextLine] < state.blkIndent) { break OUTER }
|
if (state.sCount[nextLine] < state.blkIndent) {
|
||||||
|
break OUTER
|
||||||
|
}
|
||||||
contentStart = skipMarker(state, nextLine)
|
contentStart = skipMarker(state, nextLine)
|
||||||
if (contentStart < 0) { break }
|
if (contentStart < 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
ddLine = nextLine
|
ddLine = nextLine
|
||||||
|
|
||||||
@@ -194,20 +226,36 @@ module.exports = function definitionListPlugin (md) {
|
|||||||
// insert DD tag and repeat checking
|
// insert DD tag and repeat checking
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextLine >= endLine) { break }
|
if (nextLine >= endLine) {
|
||||||
|
break
|
||||||
|
}
|
||||||
dtLine = nextLine
|
dtLine = nextLine
|
||||||
|
|
||||||
if (state.isEmpty(dtLine)) { break }
|
if (state.isEmpty(dtLine)) {
|
||||||
if (state.sCount[dtLine] < state.blkIndent) { break }
|
break
|
||||||
|
}
|
||||||
|
if (state.sCount[dtLine] < state.blkIndent) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
ddLine = dtLine + 1
|
ddLine = dtLine + 1
|
||||||
if (ddLine >= endLine) { break }
|
if (ddLine >= endLine) {
|
||||||
if (state.isEmpty(ddLine)) { ddLine++ }
|
break
|
||||||
if (ddLine >= endLine) { break }
|
}
|
||||||
|
if (state.isEmpty(ddLine)) {
|
||||||
|
ddLine++
|
||||||
|
}
|
||||||
|
if (ddLine >= endLine) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
if (state.sCount[ddLine] < state.blkIndent) { break }
|
if (state.sCount[ddLine] < state.blkIndent) {
|
||||||
|
break
|
||||||
|
}
|
||||||
contentStart = skipMarker(state, ddLine)
|
contentStart = skipMarker(state, ddLine)
|
||||||
if (contentStart < 0) { break }
|
if (contentStart < 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
// go to the next loop iteration:
|
// go to the next loop iteration:
|
||||||
// insert DT and DD tags and repeat checking
|
// insert DT and DD tags and repeat checking
|
||||||
@@ -228,5 +276,7 @@ module.exports = function definitionListPlugin (md) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
md.block.ruler.before('paragraph', 'deflist', deflist, { alt: [ 'paragraph', 'reference' ] })
|
md.block.ruler.before('paragraph', 'deflist', deflist, {
|
||||||
|
alt: ['paragraph', 'reference']
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
module.exports = function (md, renderers, defaultRenderer) {
|
module.exports = function(md, renderers, defaultRenderer) {
|
||||||
const paramsRE = /^[ \t]*([\w+#-]+)?(?:\(((?:\s*\w[-\w]*(?:=(?:'(?:.*?[^\\])?'|"(?:.*?[^\\])?"|(?:[^'"][^\s]*)))?)*)\))?(?::([^:]*)(?::(\d+))?)?\s*$/
|
const paramsRE = /^[ \t]*([\w+#-]+)?(?:\(((?:\s*\w[-\w]*(?:=(?:'(?:.*?[^\\])?'|"(?:.*?[^\\])?"|(?:[^'"][^\s]*)))?)*)\))?(?::([^:]*)(?::(\d+))?)?\s*$/
|
||||||
|
|
||||||
function fence (state, startLine, endLine, silent) {
|
function fence(state, startLine, endLine, silent) {
|
||||||
let pos = state.bMarks[startLine] + state.tShift[startLine]
|
let pos = state.bMarks[startLine] + state.tShift[startLine]
|
||||||
let max = state.eMarks[startLine]
|
let max = state.eMarks[startLine]
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ module.exports = function (md, renderers, defaultRenderer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const marker = state.src.charCodeAt(pos)
|
const marker = state.src.charCodeAt(pos)
|
||||||
if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) {
|
if (marker !== 0x7e /* ~ */ && marker !== 0x60 /* ` */) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,10 @@ module.exports = function (md, renderers, defaultRenderer) {
|
|||||||
if (pos < max && state.sCount[nextLine] < state.blkIndent) {
|
if (pos < max && state.sCount[nextLine] < state.blkIndent) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (state.src.charCodeAt(pos) !== marker || state.sCount[nextLine] - state.blkIndent >= 4) {
|
if (
|
||||||
|
state.src.charCodeAt(pos) !== marker ||
|
||||||
|
state.sCount[nextLine] - state.blkIndent >= 4
|
||||||
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,10 +130,12 @@ module.exports = function (md, renderers, defaultRenderer) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
for (const name in renderers) {
|
for (const name in renderers) {
|
||||||
md.renderer.rules[`${name}_fence`] = (tokens, index) => renderers[name](tokens[index])
|
md.renderer.rules[`${name}_fence`] = (tokens, index) =>
|
||||||
|
renderers[name](tokens[index])
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defaultRenderer) {
|
if (defaultRenderer) {
|
||||||
md.renderer.rules['_fence'] = (tokens, index) => defaultRenderer(tokens[index])
|
md.renderer.rules['_fence'] = (tokens, index) =>
|
||||||
|
defaultRenderer(tokens[index])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
module.exports = function frontMatterPlugin (md) {
|
module.exports = function frontMatterPlugin(md) {
|
||||||
function frontmatter (state, startLine, endLine, silent) {
|
function frontmatter(state, startLine, endLine, silent) {
|
||||||
if (startLine !== 0 || state.src.substr(startLine, state.eMarks[0]) !== '---') {
|
if (
|
||||||
|
startLine !== 0 ||
|
||||||
|
state.src.substr(startLine, state.eMarks[0]) !== '---'
|
||||||
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
let line = 0
|
let line = 0
|
||||||
while (++line < state.lineMax) {
|
while (++line < state.lineMax) {
|
||||||
if (state.src.substring(state.bMarks[line], state.eMarks[line]) === '---') {
|
if (
|
||||||
|
state.src.substring(state.bMarks[line], state.eMarks[line]) === '---'
|
||||||
|
) {
|
||||||
state.line = line + 1
|
state.line = line + 1
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -19,6 +24,6 @@ module.exports = function frontMatterPlugin (md) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
md.block.ruler.before('table', 'frontmatter', frontmatter, {
|
md.block.ruler.before('table', 'frontmatter', frontmatter, {
|
||||||
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
|
alt: ['paragraph', 'reference', 'blockquote', 'list']
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import sanitizeHtml from 'sanitize-html'
|
|||||||
import { escapeHtmlCharacters } from './utils'
|
import { escapeHtmlCharacters } from './utils'
|
||||||
import url from 'url'
|
import url from 'url'
|
||||||
|
|
||||||
module.exports = function sanitizePlugin (md, options) {
|
module.exports = function sanitizePlugin(md, options) {
|
||||||
options = options || {}
|
options = options || {}
|
||||||
|
|
||||||
md.core.ruler.after('linkify', 'sanitize_inline', state => {
|
md.core.ruler.after('linkify', 'sanitize_inline', state => {
|
||||||
@@ -38,15 +38,20 @@ module.exports = function sanitizePlugin (md, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:=("|')(?:[^\3]+?)\3)?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i
|
const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:=("|')(?:[^\3]+?)\3)?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i
|
||||||
const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/ig
|
const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/gi
|
||||||
|
|
||||||
function sanitizeInline (html, options) {
|
function sanitizeInline(html, options) {
|
||||||
let match = tagRegex.exec(html)
|
let match = tagRegex.exec(html)
|
||||||
if (!match) {
|
if (!match) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const { allowedTags, allowedAttributes, selfClosing, allowedSchemesAppliedToAttributes } = options
|
const {
|
||||||
|
allowedTags,
|
||||||
|
allowedAttributes,
|
||||||
|
selfClosing,
|
||||||
|
allowedSchemesAppliedToAttributes
|
||||||
|
} = options
|
||||||
|
|
||||||
if (match[1] !== undefined) {
|
if (match[1] !== undefined) {
|
||||||
// opening tag
|
// opening tag
|
||||||
@@ -65,9 +70,17 @@ function sanitizeInline (html, options) {
|
|||||||
name = match[1].toLowerCase()
|
name = match[1].toLowerCase()
|
||||||
value = match[3]
|
value = match[3]
|
||||||
|
|
||||||
if (allowedAttributes['*'].indexOf(name) !== -1 || (allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)) {
|
if (
|
||||||
|
allowedAttributes['*'].indexOf(name) !== -1 ||
|
||||||
|
(allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)
|
||||||
|
) {
|
||||||
if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) {
|
if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) {
|
||||||
if (naughtyHRef(value, options) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value, options))) {
|
if (
|
||||||
|
naughtyHRef(value, options) ||
|
||||||
|
(tag === 'iframe' &&
|
||||||
|
name === 'src' &&
|
||||||
|
naughtyIFrame(value, options))
|
||||||
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +107,7 @@ function sanitizeInline (html, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function naughtyHRef (href, options) {
|
function naughtyHRef(href, options) {
|
||||||
// href = href.replace(/[\x00-\x20]+/g, '')
|
// href = href.replace(/[\x00-\x20]+/g, '')
|
||||||
if (!href) {
|
if (!href) {
|
||||||
// No href
|
// No href
|
||||||
@@ -117,7 +130,7 @@ function naughtyHRef (href, options) {
|
|||||||
return options.allowedSchemes.indexOf(scheme) === -1
|
return options.allowedSchemes.indexOf(scheme) === -1
|
||||||
}
|
}
|
||||||
|
|
||||||
function naughtyIFrame (src, options) {
|
function naughtyIFrame(src, options) {
|
||||||
try {
|
try {
|
||||||
const parsed = url.parse(src, false, true)
|
const parsed = url.parse(src, false, true)
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const hasProp = Object.prototype.hasOwnProperty
|
|||||||
/**
|
/**
|
||||||
* From @enyaxu/markdown-it-anchor
|
* From @enyaxu/markdown-it-anchor
|
||||||
*/
|
*/
|
||||||
function uniqueSlug (slug, slugs, opts) {
|
function uniqueSlug(slug, slugs, opts) {
|
||||||
let uniq = slug
|
let uniq = slug
|
||||||
let i = opts.uniqueSlugStartIndex
|
let i = opts.uniqueSlugStartIndex
|
||||||
while (hasProp.call(slugs, uniq)) uniq = `${slug}-${i++}`
|
while (hasProp.call(slugs, uniq)) uniq = `${slug}-${i++}`
|
||||||
@@ -20,7 +20,7 @@ function uniqueSlug (slug, slugs, opts) {
|
|||||||
return uniq
|
return uniq
|
||||||
}
|
}
|
||||||
|
|
||||||
function linkify (token) {
|
function linkify(token) {
|
||||||
token.content = mdlink(token.content, `#${decodeURI(token.slug)}`)
|
token.content = mdlink(token.content, `#${decodeURI(token.slug)}`)
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
@@ -36,8 +36,8 @@ const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
|
|||||||
* Otherwise,TOC is updated in place.
|
* Otherwise,TOC is updated in place.
|
||||||
* @param editor CodeMirror editor to be updated with TOC
|
* @param editor CodeMirror editor to be updated with TOC
|
||||||
*/
|
*/
|
||||||
export function generateInEditor (editor) {
|
export function generateInEditor(editor) {
|
||||||
function updateExistingToc () {
|
function updateExistingToc() {
|
||||||
const toc = generate(editor.getValue())
|
const toc = generate(editor.getValue())
|
||||||
const search = editor.getSearchCursor(tocRegex)
|
const search = editor.getSearchCursor(tocRegex)
|
||||||
while (search.findNext()) {
|
while (search.findNext()) {
|
||||||
@@ -45,8 +45,10 @@ export function generateInEditor (editor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTocAtCursorPosition () {
|
function addTocAtCursorPosition() {
|
||||||
const toc = generate(editor.getRange(editor.getCursor(), {line: Infinity}))
|
const toc = generate(
|
||||||
|
editor.getRange(editor.getCursor(), { line: Infinity })
|
||||||
|
)
|
||||||
editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor())
|
editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +59,7 @@ export function generateInEditor (editor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tocExistsInEditor (editor) {
|
export function tocExistsInEditor(editor) {
|
||||||
return tocRegex.test(editor.getValue())
|
return tocRegex.test(editor.getValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +68,7 @@ export function tocExistsInEditor (editor) {
|
|||||||
* @param markdownText MD document
|
* @param markdownText MD document
|
||||||
* @returns generatedTOC String containing generated TOC
|
* @returns generatedTOC String containing generated TOC
|
||||||
*/
|
*/
|
||||||
export function generate (markdownText) {
|
export function generate(markdownText) {
|
||||||
const slugs = {}
|
const slugs = {}
|
||||||
const opts = {
|
const opts = {
|
||||||
uniqueSlugStartIndex: 1
|
uniqueSlugStartIndex: 1
|
||||||
@@ -86,9 +88,12 @@ export function generate (markdownText) {
|
|||||||
return TOC_MARKER_START + EOL + EOL + md + EOL + EOL + TOC_MARKER_END
|
return TOC_MARKER_START + EOL + EOL + md + EOL + EOL + TOC_MARKER_END
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapTocWithEol (toc, editor) {
|
function wrapTocWithEol(toc, editor) {
|
||||||
const leftWrap = editor.getCursor().ch === 0 ? '' : EOL
|
const leftWrap = editor.getCursor().ch === 0 ? '' : EOL
|
||||||
const rightWrap = editor.getLine(editor.getCursor().line).length === editor.getCursor().ch ? '' : EOL
|
const rightWrap =
|
||||||
|
editor.getLine(editor.getCursor().line).length === editor.getCursor().ch
|
||||||
|
? ''
|
||||||
|
: EOL
|
||||||
return leftWrap + toc + rightWrap
|
return leftWrap + toc + rightWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,18 +10,20 @@ import ConfigManager from 'browser/main/lib/ConfigManager'
|
|||||||
import katex from 'katex'
|
import katex from 'katex'
|
||||||
import { lastFindInArray } from './utils'
|
import { lastFindInArray } from './utils'
|
||||||
|
|
||||||
function createGutter (str, firstLineNumber) {
|
function createGutter(str, firstLineNumber) {
|
||||||
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
||||||
const lastLineNumber = (str.match(/\n/g) || []).length + firstLineNumber - 1
|
const lastLineNumber = (str.match(/\n/g) || []).length + firstLineNumber - 1
|
||||||
const lines = []
|
const lines = []
|
||||||
for (let i = firstLineNumber; i <= lastLineNumber; i++) {
|
for (let i = firstLineNumber; i <= lastLineNumber; i++) {
|
||||||
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
|
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
|
||||||
}
|
}
|
||||||
return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
|
return (
|
||||||
|
'<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Markdown {
|
class Markdown {
|
||||||
constructor (options = {}) {
|
constructor(options = {}) {
|
||||||
const config = ConfigManager.get()
|
const config = ConfigManager.get()
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
typographer: config.preview.smartQuotes,
|
typographer: config.preview.smartQuotes,
|
||||||
@@ -37,29 +39,129 @@ class Markdown {
|
|||||||
this.md.linkify.set({ fuzzyLink: false })
|
this.md.linkify.set({ fuzzyLink: false })
|
||||||
|
|
||||||
if (updatedOptions.sanitize !== 'NONE') {
|
if (updatedOptions.sanitize !== 'NONE') {
|
||||||
const allowedTags = ['iframe', 'input', 'b',
|
const allowedTags = [
|
||||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'br', 'b', 'i', 'strong', 'em', 'a', 'pre', 'code', 'img', 'tt',
|
'iframe',
|
||||||
'div', 'ins', 'del', 'sup', 'sub', 'p', 'ol', 'ul', 'table', 'thead', 'tbody', 'tfoot', 'blockquote',
|
'input',
|
||||||
'dl', 'dt', 'dd', 'kbd', 'q', 'samp', 'var', 'hr', 'ruby', 'rt', 'rp', 'li', 'tr', 'td', 'th', 's', 'strike', 'summary', 'details'
|
'b',
|
||||||
|
'h1',
|
||||||
|
'h2',
|
||||||
|
'h3',
|
||||||
|
'h4',
|
||||||
|
'h5',
|
||||||
|
'h6',
|
||||||
|
'h7',
|
||||||
|
'h8',
|
||||||
|
'br',
|
||||||
|
'b',
|
||||||
|
'i',
|
||||||
|
'strong',
|
||||||
|
'em',
|
||||||
|
'a',
|
||||||
|
'pre',
|
||||||
|
'code',
|
||||||
|
'img',
|
||||||
|
'tt',
|
||||||
|
'div',
|
||||||
|
'ins',
|
||||||
|
'del',
|
||||||
|
'sup',
|
||||||
|
'sub',
|
||||||
|
'p',
|
||||||
|
'ol',
|
||||||
|
'ul',
|
||||||
|
'table',
|
||||||
|
'thead',
|
||||||
|
'tbody',
|
||||||
|
'tfoot',
|
||||||
|
'blockquote',
|
||||||
|
'dl',
|
||||||
|
'dt',
|
||||||
|
'dd',
|
||||||
|
'kbd',
|
||||||
|
'q',
|
||||||
|
'samp',
|
||||||
|
'var',
|
||||||
|
'hr',
|
||||||
|
'ruby',
|
||||||
|
'rt',
|
||||||
|
'rp',
|
||||||
|
'li',
|
||||||
|
'tr',
|
||||||
|
'td',
|
||||||
|
'th',
|
||||||
|
's',
|
||||||
|
'strike',
|
||||||
|
'summary',
|
||||||
|
'details'
|
||||||
]
|
]
|
||||||
const allowedAttributes = [
|
const allowedAttributes = [
|
||||||
'abbr', 'accept', 'accept-charset',
|
'abbr',
|
||||||
'accesskey', 'action', 'align', 'alt', 'axis',
|
'accept',
|
||||||
'border', 'cellpadding', 'cellspacing', 'char',
|
'accept-charset',
|
||||||
'charoff', 'charset', 'checked',
|
'accesskey',
|
||||||
'clear', 'cols', 'colspan', 'color',
|
'action',
|
||||||
'compact', 'coords', 'datetime', 'dir',
|
'align',
|
||||||
'disabled', 'enctype', 'for', 'frame',
|
'alt',
|
||||||
'headers', 'height', 'hreflang',
|
'axis',
|
||||||
'hspace', 'ismap', 'label', 'lang',
|
'border',
|
||||||
'maxlength', 'media', 'method',
|
'cellpadding',
|
||||||
'multiple', 'name', 'nohref', 'noshade',
|
'cellspacing',
|
||||||
'nowrap', 'open', 'prompt', 'readonly', 'rel', 'rev',
|
'char',
|
||||||
'rows', 'rowspan', 'rules', 'scope',
|
'charoff',
|
||||||
'selected', 'shape', 'size', 'span',
|
'charset',
|
||||||
'start', 'summary', 'tabindex', 'target',
|
'checked',
|
||||||
'title', 'type', 'usemap', 'valign', 'value',
|
'clear',
|
||||||
'vspace', 'width', 'itemprop'
|
'cols',
|
||||||
|
'colspan',
|
||||||
|
'color',
|
||||||
|
'compact',
|
||||||
|
'coords',
|
||||||
|
'datetime',
|
||||||
|
'dir',
|
||||||
|
'disabled',
|
||||||
|
'enctype',
|
||||||
|
'for',
|
||||||
|
'frame',
|
||||||
|
'headers',
|
||||||
|
'height',
|
||||||
|
'hreflang',
|
||||||
|
'hspace',
|
||||||
|
'ismap',
|
||||||
|
'label',
|
||||||
|
'lang',
|
||||||
|
'maxlength',
|
||||||
|
'media',
|
||||||
|
'method',
|
||||||
|
'multiple',
|
||||||
|
'name',
|
||||||
|
'nohref',
|
||||||
|
'noshade',
|
||||||
|
'nowrap',
|
||||||
|
'open',
|
||||||
|
'prompt',
|
||||||
|
'readonly',
|
||||||
|
'rel',
|
||||||
|
'rev',
|
||||||
|
'rows',
|
||||||
|
'rowspan',
|
||||||
|
'rules',
|
||||||
|
'scope',
|
||||||
|
'selected',
|
||||||
|
'shape',
|
||||||
|
'size',
|
||||||
|
'span',
|
||||||
|
'start',
|
||||||
|
'summary',
|
||||||
|
'tabindex',
|
||||||
|
'target',
|
||||||
|
'title',
|
||||||
|
'type',
|
||||||
|
'usemap',
|
||||||
|
'valign',
|
||||||
|
'value',
|
||||||
|
'vspace',
|
||||||
|
'width',
|
||||||
|
'itemprop'
|
||||||
]
|
]
|
||||||
|
|
||||||
if (updatedOptions.sanitize === 'ALLOW_STYLES') {
|
if (updatedOptions.sanitize === 'ALLOW_STYLES') {
|
||||||
@@ -72,20 +174,20 @@ class Markdown {
|
|||||||
allowedTags,
|
allowedTags,
|
||||||
allowedAttributes: {
|
allowedAttributes: {
|
||||||
'*': allowedAttributes,
|
'*': allowedAttributes,
|
||||||
'a': ['href'],
|
a: ['href'],
|
||||||
'div': ['itemscope', 'itemtype'],
|
div: ['itemscope', 'itemtype'],
|
||||||
'blockquote': ['cite'],
|
blockquote: ['cite'],
|
||||||
'del': ['cite'],
|
del: ['cite'],
|
||||||
'ins': ['cite'],
|
ins: ['cite'],
|
||||||
'q': ['cite'],
|
q: ['cite'],
|
||||||
'img': ['src', 'width', 'height'],
|
img: ['src', 'width', 'height'],
|
||||||
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
iframe: ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
||||||
'input': ['type', 'id', 'checked']
|
input: ['type', 'id', 'checked']
|
||||||
},
|
},
|
||||||
allowedIframeHostnames: ['www.youtube.com'],
|
allowedIframeHostnames: ['www.youtube.com'],
|
||||||
selfClosing: [ 'img', 'br', 'hr', 'input' ],
|
selfClosing: ['img', 'br', 'hr', 'input'],
|
||||||
allowedSchemes: [ 'http', 'https', 'ftp', 'mailto' ],
|
allowedSchemes: ['http', 'https', 'ftp', 'mailto'],
|
||||||
allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ],
|
allowedSchemesAppliedToAttributes: ['href', 'src', 'cite'],
|
||||||
allowProtocolRelative: true
|
allowProtocolRelative: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -98,7 +200,7 @@ class Markdown {
|
|||||||
inlineClose: config.preview.latexInlineClose,
|
inlineClose: config.preview.latexInlineClose,
|
||||||
blockOpen: config.preview.latexBlockOpen,
|
blockOpen: config.preview.latexBlockOpen,
|
||||||
blockClose: config.preview.latexBlockClose,
|
blockClose: config.preview.latexBlockClose,
|
||||||
inlineRenderer: function (str) {
|
inlineRenderer: function(str) {
|
||||||
let output = ''
|
let output = ''
|
||||||
try {
|
try {
|
||||||
output = katex.renderToString(str.trim())
|
output = katex.renderToString(str.trim())
|
||||||
@@ -107,7 +209,7 @@ class Markdown {
|
|||||||
}
|
}
|
||||||
return output
|
return output
|
||||||
},
|
},
|
||||||
blockRenderer: function (str) {
|
blockRenderer: function(str) {
|
||||||
let output = ''
|
let output = ''
|
||||||
try {
|
try {
|
||||||
output = katex.renderToString(str.trim(), { displayMode: true })
|
output = katex.renderToString(str.trim(), { displayMode: true })
|
||||||
@@ -124,7 +226,19 @@ class Markdown {
|
|||||||
slugify: require('./slugify')
|
slugify: require('./slugify')
|
||||||
})
|
})
|
||||||
this.md.use(require('markdown-it-kbd'))
|
this.md.use(require('markdown-it-kbd'))
|
||||||
this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error', 'quote', 'abstract', 'question']})
|
this.md.use(require('markdown-it-admonition'), {
|
||||||
|
types: [
|
||||||
|
'note',
|
||||||
|
'hint',
|
||||||
|
'attention',
|
||||||
|
'caution',
|
||||||
|
'danger',
|
||||||
|
'error',
|
||||||
|
'quote',
|
||||||
|
'abstract',
|
||||||
|
'question'
|
||||||
|
]
|
||||||
|
})
|
||||||
this.md.use(require('markdown-it-abbr'))
|
this.md.use(require('markdown-it-abbr'))
|
||||||
this.md.use(require('markdown-it-sub'))
|
this.md.use(require('markdown-it-sub'))
|
||||||
this.md.use(require('markdown-it-sup'))
|
this.md.use(require('markdown-it-sup'))
|
||||||
@@ -144,63 +258,86 @@ class Markdown {
|
|||||||
this.md.use(require('./markdown-it-deflist'))
|
this.md.use(require('./markdown-it-deflist'))
|
||||||
this.md.use(require('./markdown-it-frontmatter'))
|
this.md.use(require('./markdown-it-frontmatter'))
|
||||||
|
|
||||||
this.md.use(require('./markdown-it-fence'), {
|
this.md.use(
|
||||||
chart: token => {
|
require('./markdown-it-fence'),
|
||||||
if (token.parameters.hasOwnProperty('yaml')) {
|
{
|
||||||
token.parameters.format = 'yaml'
|
chart: token => {
|
||||||
}
|
if (token.parameters.hasOwnProperty('yaml')) {
|
||||||
|
token.parameters.format = 'yaml'
|
||||||
return `<pre class="fence" data-line="${token.map[0]}">
|
|
||||||
<span class="filename">${token.fileName}</span>
|
|
||||||
<div class="chart" data-height="${token.parameters.height}" data-format="${token.parameters.format || 'json'}">${token.content}</div>
|
|
||||||
</pre>`
|
|
||||||
},
|
|
||||||
flowchart: token => {
|
|
||||||
return `<pre class="fence" data-line="${token.map[0]}">
|
|
||||||
<span class="filename">${token.fileName}</span>
|
|
||||||
<div class="flowchart" data-height="${token.parameters.height}">${token.content}</div>
|
|
||||||
</pre>`
|
|
||||||
},
|
|
||||||
gallery: token => {
|
|
||||||
const content = token.content.split('\n').slice(0, -1).map(line => {
|
|
||||||
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
|
|
||||||
if (match) {
|
|
||||||
return mdurl.encode(match[1])
|
|
||||||
} else {
|
|
||||||
return mdurl.encode(line)
|
|
||||||
}
|
}
|
||||||
}).join('\n')
|
|
||||||
|
|
||||||
return `<pre class="fence" data-line="${token.map[0]}">
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
<span class="filename">${token.fileName}</span>
|
<span class="filename">${token.fileName}</span>
|
||||||
<div class="gallery" data-autoplay="${token.parameters.autoplay}" data-height="${token.parameters.height}">${content}</div>
|
<div class="chart" data-height="${
|
||||||
|
token.parameters.height
|
||||||
|
}" data-format="${token.parameters.format || 'json'}">${
|
||||||
|
token.content
|
||||||
|
}</div>
|
||||||
</pre>`
|
</pre>`
|
||||||
|
},
|
||||||
|
flowchart: token => {
|
||||||
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
|
<span class="filename">${token.fileName}</span>
|
||||||
|
<div class="flowchart" data-height="${token.parameters.height}">${
|
||||||
|
token.content
|
||||||
|
}</div>
|
||||||
|
</pre>`
|
||||||
|
},
|
||||||
|
gallery: token => {
|
||||||
|
const content = token.content
|
||||||
|
.split('\n')
|
||||||
|
.slice(0, -1)
|
||||||
|
.map(line => {
|
||||||
|
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
|
||||||
|
if (match) {
|
||||||
|
return mdurl.encode(match[1])
|
||||||
|
} else {
|
||||||
|
return mdurl.encode(line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
|
<span class="filename">${token.fileName}</span>
|
||||||
|
<div class="gallery" data-autoplay="${
|
||||||
|
token.parameters.autoplay
|
||||||
|
}" data-height="${token.parameters.height}">${content}</div>
|
||||||
|
</pre>`
|
||||||
|
},
|
||||||
|
mermaid: token => {
|
||||||
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
|
<span class="filename">${token.fileName}</span>
|
||||||
|
<div class="mermaid" data-height="${token.parameters.height}">${
|
||||||
|
token.content
|
||||||
|
}</div>
|
||||||
|
</pre>`
|
||||||
|
},
|
||||||
|
sequence: token => {
|
||||||
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
|
<span class="filename">${token.fileName}</span>
|
||||||
|
<div class="sequence" data-height="${token.parameters.height}">${
|
||||||
|
token.content
|
||||||
|
}</div>
|
||||||
|
</pre>`
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mermaid: token => {
|
token => {
|
||||||
return `<pre class="fence" data-line="${token.map[0]}">
|
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
|
||||||
<span class="filename">${token.fileName}</span>
|
|
||||||
<div class="mermaid" data-height="${token.parameters.height}">${token.content}</div>
|
|
||||||
</pre>`
|
|
||||||
},
|
|
||||||
sequence: token => {
|
|
||||||
return `<pre class="fence" data-line="${token.map[0]}">
|
|
||||||
<span class="filename">${token.fileName}</span>
|
|
||||||
<div class="sequence" data-height="${token.parameters.height}">${token.content}</div>
|
|
||||||
</pre>`
|
|
||||||
}
|
|
||||||
}, token => {
|
|
||||||
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
|
|
||||||
<span class="filename">${token.fileName}</span>
|
<span class="filename">${token.fileName}</span>
|
||||||
${createGutter(token.content, token.firstLineNumber)}
|
${createGutter(token.content, token.firstLineNumber)}
|
||||||
<code class="${token.langType}">${token.content}</code>
|
<code class="${token.langType}">${token.content}</code>
|
||||||
</pre>`
|
</pre>`
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const deflate = require('markdown-it-plantuml/lib/deflate')
|
const deflate = require('markdown-it-plantuml/lib/deflate')
|
||||||
const plantuml = require('markdown-it-plantuml')
|
const plantuml = require('markdown-it-plantuml')
|
||||||
const plantUmlStripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
|
const plantUmlStripTrailingSlash = url =>
|
||||||
const plantUmlServerAddress = plantUmlStripTrailingSlash(config.preview.plantUMLServerAddress)
|
url.endsWith('/') ? url.slice(0, -1) : url
|
||||||
const parsePlantUml = function (umlCode, openMarker, closeMarker, type) {
|
const plantUmlServerAddress = plantUmlStripTrailingSlash(
|
||||||
|
config.preview.plantUMLServerAddress
|
||||||
|
)
|
||||||
|
const parsePlantUml = function(umlCode, openMarker, closeMarker, type) {
|
||||||
const s = unescape(encodeURIComponent(umlCode))
|
const s = unescape(encodeURIComponent(umlCode))
|
||||||
const zippedCode = deflate.encode64(
|
const zippedCode = deflate.encode64(
|
||||||
deflate.zip_deflate(`${openMarker}\n${s}\n${closeMarker}`, 9)
|
deflate.zip_deflate(`${openMarker}\n${s}\n${closeMarker}`, 9)
|
||||||
@@ -209,39 +346,47 @@ class Markdown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.md.use(plantuml, {
|
this.md.use(plantuml, {
|
||||||
generateSource: (umlCode) => parsePlantUml(umlCode, '@startuml', '@enduml', 'svg')
|
generateSource: umlCode =>
|
||||||
|
parsePlantUml(umlCode, '@startuml', '@enduml', 'svg')
|
||||||
})
|
})
|
||||||
|
|
||||||
// Ditaa support. PlantUML server doesn't support Ditaa in SVG, so we set the format as PNG at the moment.
|
// Ditaa support. PlantUML server doesn't support Ditaa in SVG, so we set the format as PNG at the moment.
|
||||||
this.md.use(plantuml, {
|
this.md.use(plantuml, {
|
||||||
openMarker: '@startditaa',
|
openMarker: '@startditaa',
|
||||||
closeMarker: '@endditaa',
|
closeMarker: '@endditaa',
|
||||||
generateSource: (umlCode) => parsePlantUml(umlCode, '@startditaa', '@endditaa', 'png')
|
generateSource: umlCode =>
|
||||||
|
parsePlantUml(umlCode, '@startditaa', '@endditaa', 'png')
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mindmap support
|
// Mindmap support
|
||||||
this.md.use(plantuml, {
|
this.md.use(plantuml, {
|
||||||
openMarker: '@startmindmap',
|
openMarker: '@startmindmap',
|
||||||
closeMarker: '@endmindmap',
|
closeMarker: '@endmindmap',
|
||||||
generateSource: (umlCode) => parsePlantUml(umlCode, '@startmindmap', '@endmindmap', 'svg')
|
generateSource: umlCode =>
|
||||||
|
parsePlantUml(umlCode, '@startmindmap', '@endmindmap', 'svg')
|
||||||
})
|
})
|
||||||
|
|
||||||
// WBS support
|
// WBS support
|
||||||
this.md.use(plantuml, {
|
this.md.use(plantuml, {
|
||||||
openMarker: '@startwbs',
|
openMarker: '@startwbs',
|
||||||
closeMarker: '@endwbs',
|
closeMarker: '@endwbs',
|
||||||
generateSource: (umlCode) => parsePlantUml(umlCode, '@startwbs', '@endwbs', 'svg')
|
generateSource: umlCode =>
|
||||||
|
parsePlantUml(umlCode, '@startwbs', '@endwbs', 'svg')
|
||||||
})
|
})
|
||||||
|
|
||||||
// Gantt support
|
// Gantt support
|
||||||
this.md.use(plantuml, {
|
this.md.use(plantuml, {
|
||||||
openMarker: '@startgantt',
|
openMarker: '@startgantt',
|
||||||
closeMarker: '@endgantt',
|
closeMarker: '@endgantt',
|
||||||
generateSource: (umlCode) => parsePlantUml(umlCode, '@startgantt', '@endgantt', 'svg')
|
generateSource: umlCode =>
|
||||||
|
parsePlantUml(umlCode, '@startgantt', '@endgantt', 'svg')
|
||||||
})
|
})
|
||||||
|
|
||||||
// Override task item
|
// Override task item
|
||||||
this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
this.md.block.ruler.at('paragraph', function(
|
||||||
|
state,
|
||||||
|
startLine /*, endLine */
|
||||||
|
) {
|
||||||
let content, terminate, i, l, token
|
let content, terminate, i, l, token
|
||||||
let nextLine = startLine + 1
|
let nextLine = startLine + 1
|
||||||
const terminatorRules = state.md.block.ruler.getRules('paragraph')
|
const terminatorRules = state.md.block.ruler.getRules('paragraph')
|
||||||
@@ -251,10 +396,14 @@ class Markdown {
|
|||||||
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
|
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
|
||||||
// this would be a code block normally, but after paragraph
|
// this would be a code block normally, but after paragraph
|
||||||
// it's considered a lazy continuation regardless of what's there
|
// it's considered a lazy continuation regardless of what's there
|
||||||
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
|
if (state.sCount[nextLine] - state.blkIndent > 3) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// quirk for blockquotes, this line should already be checked by that rule
|
// quirk for blockquotes, this line should already be checked by that rule
|
||||||
if (state.sCount[nextLine] < 0) { continue }
|
if (state.sCount[nextLine] < 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Some tags can terminate paragraph without empty line.
|
// Some tags can terminate paragraph without empty line.
|
||||||
terminate = false
|
terminate = false
|
||||||
@@ -264,10 +413,14 @@ class Markdown {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (terminate) { break }
|
if (terminate) {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
|
content = state
|
||||||
|
.getLines(startLine, nextLine, state.blkIndent, false)
|
||||||
|
.trim()
|
||||||
|
|
||||||
state.line = nextLine
|
state.line = nextLine
|
||||||
|
|
||||||
@@ -277,18 +430,31 @@ class Markdown {
|
|||||||
if (state.parentType === 'list') {
|
if (state.parentType === 'list') {
|
||||||
const match = content.match(/^\[( |x)\] ?(.+)/i)
|
const match = content.match(/^\[( |x)\] ?(.+)/i)
|
||||||
if (match) {
|
if (match) {
|
||||||
const liToken = lastFindInArray(state.tokens, token => token.type === 'list_item_open')
|
const liToken = lastFindInArray(
|
||||||
|
state.tokens,
|
||||||
|
token => token.type === 'list_item_open'
|
||||||
|
)
|
||||||
if (liToken) {
|
if (liToken) {
|
||||||
if (!liToken.attrs) {
|
if (!liToken.attrs) {
|
||||||
liToken.attrs = []
|
liToken.attrs = []
|
||||||
}
|
}
|
||||||
if (config.preview.lineThroughCheckbox) {
|
if (config.preview.lineThroughCheckbox) {
|
||||||
liToken.attrs.push(['class', `taskListItem${match[1] !== ' ' ? ' checked' : ''}`])
|
liToken.attrs.push([
|
||||||
|
'class',
|
||||||
|
`taskListItem${match[1] !== ' ' ? ' checked' : ''}`
|
||||||
|
])
|
||||||
} else {
|
} else {
|
||||||
liToken.attrs.push(['class', 'taskListItem'])
|
liToken.attrs.push(['class', 'taskListItem'])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
|
content = `<label class='taskListItem${
|
||||||
|
match[1] !== ' ' ? ' checked' : ''
|
||||||
|
}' for='checkbox-${startLine + 1}'><input type='checkbox'${
|
||||||
|
match[1] !== ' ' ? ' checked' : ''
|
||||||
|
} id='checkbox-${startLine + 1}'/> ${content.substring(
|
||||||
|
4,
|
||||||
|
content.length
|
||||||
|
)}</label>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +475,7 @@ class Markdown {
|
|||||||
// Add line number attribute for scrolling
|
// Add line number attribute for scrolling
|
||||||
const originalRender = this.md.renderer.render
|
const originalRender = this.md.renderer.render
|
||||||
this.md.renderer.render = (tokens, options, env) => {
|
this.md.renderer.render = (tokens, options, env) => {
|
||||||
tokens.forEach((token) => {
|
tokens.forEach(token => {
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case 'blockquote_open':
|
case 'blockquote_open':
|
||||||
case 'dd_open':
|
case 'dd_open':
|
||||||
@@ -330,7 +496,7 @@ class Markdown {
|
|||||||
window.md = this.md
|
window.md = this.md
|
||||||
}
|
}
|
||||||
|
|
||||||
render (content) {
|
render(content) {
|
||||||
if (!_.isString(content)) content = ''
|
if (!_.isString(content)) content = ''
|
||||||
return this.md.render(content)
|
return this.md.render(content)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* @param {string} input
|
* @param {string} input
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
export function strip (input) {
|
export function strip(input) {
|
||||||
let output = input
|
let output = input
|
||||||
try {
|
try {
|
||||||
output = output
|
output = output
|
||||||
|
|||||||
@@ -4,12 +4,22 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
|||||||
import queryString from 'query-string'
|
import queryString from 'query-string'
|
||||||
import { push } from 'connected-react-router'
|
import { push } from 'connected-react-router'
|
||||||
|
|
||||||
export function createMarkdownNote (storage, folder, dispatch, location, params, config) {
|
export function createMarkdownNote(
|
||||||
|
storage,
|
||||||
|
folder,
|
||||||
|
dispatch,
|
||||||
|
location,
|
||||||
|
params,
|
||||||
|
config
|
||||||
|
) {
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
||||||
|
|
||||||
let tags = []
|
let tags = []
|
||||||
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
|
if (
|
||||||
|
config.ui.tagNewNoteWithFilteringTags &&
|
||||||
|
location.pathname.match(/\/tags/)
|
||||||
|
) {
|
||||||
tags = params.tagname.split(' ')
|
tags = params.tagname.split(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,25 +39,40 @@ export function createMarkdownNote (storage, folder, dispatch, location, params,
|
|||||||
note: note
|
note: note
|
||||||
})
|
})
|
||||||
|
|
||||||
dispatch(push({
|
dispatch(
|
||||||
pathname: location.pathname,
|
push({
|
||||||
search: queryString.stringify({ key: noteHash })
|
pathname: location.pathname,
|
||||||
}))
|
search: queryString.stringify({ key: noteHash })
|
||||||
|
})
|
||||||
|
)
|
||||||
ee.emit('list:jump', noteHash)
|
ee.emit('list:jump', noteHash)
|
||||||
ee.emit('detail:focus')
|
ee.emit('detail:focus')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSnippetNote (storage, folder, dispatch, location, params, config) {
|
export function createSnippetNote(
|
||||||
|
storage,
|
||||||
|
folder,
|
||||||
|
dispatch,
|
||||||
|
location,
|
||||||
|
params,
|
||||||
|
config
|
||||||
|
) {
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
||||||
|
|
||||||
let tags = []
|
let tags = []
|
||||||
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
|
if (
|
||||||
|
config.ui.tagNewNoteWithFilteringTags &&
|
||||||
|
location.pathname.match(/\/tags/)
|
||||||
|
) {
|
||||||
tags = params.tagname.split(' ')
|
tags = params.tagname.split(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultLanguage = config.editor.snippetDefaultLanguage === 'Auto Detect' ? null : config.editor.snippetDefaultLanguage
|
const defaultLanguage =
|
||||||
|
config.editor.snippetDefaultLanguage === 'Auto Detect'
|
||||||
|
? null
|
||||||
|
: config.editor.snippetDefaultLanguage
|
||||||
|
|
||||||
return dataApi
|
return dataApi
|
||||||
.createNote(storage, {
|
.createNote(storage, {
|
||||||
@@ -71,10 +96,12 @@ export function createSnippetNote (storage, folder, dispatch, location, params,
|
|||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
})
|
})
|
||||||
dispatch(push({
|
dispatch(
|
||||||
pathname: location.pathname,
|
push({
|
||||||
search: queryString.stringify({ key: noteHash })
|
pathname: location.pathname,
|
||||||
}))
|
search: queryString.stringify({ key: noteHash })
|
||||||
|
})
|
||||||
|
)
|
||||||
ee.emit('list:jump', noteHash)
|
ee.emit('list:jump', noteHash)
|
||||||
ee.emit('detail:focus')
|
ee.emit('detail:focus')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import consts from 'browser/lib/consts'
|
import consts from 'browser/lib/consts'
|
||||||
import isString from 'lodash/isString'
|
import isString from 'lodash/isString'
|
||||||
|
|
||||||
export default function normalizeEditorFontFamily (fontFamily) {
|
export default function normalizeEditorFontFamily(fontFamily) {
|
||||||
const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
|
const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
|
||||||
return isString(fontFamily) && fontFamily.length > 0
|
return isString(fontFamily) && fontFamily.length > 0
|
||||||
? [fontFamily].concat(defaultEditorFontFamily).join(', ')
|
? [fontFamily].concat(defaultEditorFontFamily).join(', ')
|
||||||
|
|||||||
@@ -1,31 +1,38 @@
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
export default function searchFromNotes (notes, search) {
|
export default function searchFromNotes(notes, search) {
|
||||||
if (search.trim().length === 0) return []
|
if (search.trim().length === 0) return []
|
||||||
const searchBlocks = search.split(' ').filter(block => { return block !== '' })
|
const searchBlocks = search.split(' ').filter(block => {
|
||||||
|
return block !== ''
|
||||||
|
})
|
||||||
|
|
||||||
let foundNotes = notes
|
let foundNotes = notes
|
||||||
searchBlocks.forEach((block) => {
|
searchBlocks.forEach(block => {
|
||||||
foundNotes = findByWordOrTag(foundNotes, block)
|
foundNotes = findByWordOrTag(foundNotes, block)
|
||||||
})
|
})
|
||||||
return foundNotes
|
return foundNotes
|
||||||
}
|
}
|
||||||
|
|
||||||
function findByWordOrTag (notes, block) {
|
function findByWordOrTag(notes, block) {
|
||||||
let tag = block
|
let tag = block
|
||||||
if (tag.match(/^#.+/)) {
|
if (tag.match(/^#.+/)) {
|
||||||
tag = tag.match(/#(.+)/)[1]
|
tag = tag.match(/#(.+)/)[1]
|
||||||
}
|
}
|
||||||
const tagRegExp = new RegExp(_.escapeRegExp(tag), 'i')
|
const tagRegExp = new RegExp(_.escapeRegExp(tag), 'i')
|
||||||
const wordRegExp = new RegExp(_.escapeRegExp(block), 'i')
|
const wordRegExp = new RegExp(_.escapeRegExp(block), 'i')
|
||||||
return notes.filter((note) => {
|
return notes.filter(note => {
|
||||||
if (_.isArray(note.tags) && note.tags.some((_tag) => _tag.match(tagRegExp))) {
|
if (_.isArray(note.tags) && note.tags.some(_tag => _tag.match(tagRegExp))) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (note.type === 'SNIPPET_NOTE') {
|
if (note.type === 'SNIPPET_NOTE') {
|
||||||
return note.description.match(wordRegExp) || note.snippets.some((snippet) => {
|
return (
|
||||||
return snippet.name.match(wordRegExp) || snippet.content.match(wordRegExp)
|
note.description.match(wordRegExp) ||
|
||||||
})
|
note.snippets.some(snippet => {
|
||||||
|
return (
|
||||||
|
snippet.name.match(wordRegExp) || snippet.content.match(wordRegExp)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
} else if (note.type === 'MARKDOWN_NOTE') {
|
} else if (note.type === 'MARKDOWN_NOTE') {
|
||||||
return note.content.match(wordRegExp)
|
return note.content.match(wordRegExp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
module.exports = function slugify (title) {
|
module.exports = function slugify(title) {
|
||||||
const slug = encodeURI(
|
const slug = encodeURI(
|
||||||
title.trim()
|
title
|
||||||
|
.trim()
|
||||||
.replace(/^\s+/, '')
|
.replace(/^\s+/, '')
|
||||||
.replace(/\s+$/, '')
|
.replace(/\s+$/, '')
|
||||||
.replace(/\s+/g, '-')
|
.replace(/\s+/g, '-')
|
||||||
.replace(/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`]/g, '')
|
.replace(
|
||||||
|
/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`]/g,
|
||||||
|
''
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return slug
|
return slug
|
||||||
|
|||||||
@@ -12,19 +12,19 @@ const MILLISECONDS_TILL_LIVECHECK = 500
|
|||||||
let dictionary = null
|
let dictionary = null
|
||||||
let self
|
let self
|
||||||
|
|
||||||
function getAvailableDictionaries () {
|
function getAvailableDictionaries() {
|
||||||
return [
|
return [
|
||||||
{label: i18n.__('Spellcheck disabled'), value: SPELLCHECK_DISABLED},
|
{ label: i18n.__('Spellcheck disabled'), value: SPELLCHECK_DISABLED },
|
||||||
{label: i18n.__('English'), value: 'en_GB'},
|
{ label: i18n.__('English'), value: 'en_GB' },
|
||||||
{label: i18n.__('German'), value: 'de_DE'},
|
{ label: i18n.__('German'), value: 'de_DE' },
|
||||||
{label: i18n.__('French'), value: 'fr_FR'}
|
{ label: i18n.__('French'), value: 'fr_FR' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only to be used in the tests :)
|
* Only to be used in the tests :)
|
||||||
*/
|
*/
|
||||||
function setDictionaryForTestsOnly (newDictionary) {
|
function setDictionaryForTestsOnly(newDictionary) {
|
||||||
dictionary = newDictionary
|
dictionary = newDictionary
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ function setDictionaryForTestsOnly (newDictionary) {
|
|||||||
* @param {Codemirror} editor CodeMirror-Editor
|
* @param {Codemirror} editor CodeMirror-Editor
|
||||||
* @param {String} lang on of the values from getAvailableDictionaries()-Method
|
* @param {String} lang on of the values from getAvailableDictionaries()-Method
|
||||||
*/
|
*/
|
||||||
function setLanguage (editor, lang) {
|
function setLanguage(editor, lang) {
|
||||||
self = this
|
self = this
|
||||||
dictionary = null
|
dictionary = null
|
||||||
|
|
||||||
@@ -50,8 +50,7 @@ function setLanguage (editor, lang) {
|
|||||||
dictionary = new Typo(lang, false, false, {
|
dictionary = new Typo(lang, false, false, {
|
||||||
dictionaryPath: DICTIONARY_PATH,
|
dictionaryPath: DICTIONARY_PATH,
|
||||||
asyncLoad: true,
|
asyncLoad: true,
|
||||||
loadedCallback: () =>
|
loadedCallback: () => checkWholeDocument(editor)
|
||||||
checkWholeDocument(editor)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,12 +59,12 @@ function setLanguage (editor, lang) {
|
|||||||
* Checks the whole content of the editor for typos
|
* Checks the whole content of the editor for typos
|
||||||
* @param {Codemirror} editor CodeMirror-Editor
|
* @param {Codemirror} editor CodeMirror-Editor
|
||||||
*/
|
*/
|
||||||
function checkWholeDocument (editor) {
|
function checkWholeDocument(editor) {
|
||||||
const lastLine = editor.lineCount() - 1
|
const lastLine = editor.lineCount() - 1
|
||||||
const textOfLastLine = editor.getLine(lastLine) || ''
|
const textOfLastLine = editor.getLine(lastLine) || ''
|
||||||
const lastChar = textOfLastLine.length
|
const lastChar = textOfLastLine.length
|
||||||
const from = {line: 0, ch: 0}
|
const from = { line: 0, ch: 0 }
|
||||||
const to = {line: lastLine, ch: lastChar}
|
const to = { line: lastLine, ch: lastChar }
|
||||||
checkMultiLineRange(editor, from, to)
|
checkMultiLineRange(editor, from, to)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,15 +74,18 @@ function checkWholeDocument (editor) {
|
|||||||
* @param {line, ch} from starting position of the spellcheck
|
* @param {line, ch} from starting position of the spellcheck
|
||||||
* @param {line, ch} to end position of the spellcheck
|
* @param {line, ch} to end position of the spellcheck
|
||||||
*/
|
*/
|
||||||
function checkMultiLineRange (editor, from, to) {
|
function checkMultiLineRange(editor, from, to) {
|
||||||
function sortRange (pos1, pos2) {
|
function sortRange(pos1, pos2) {
|
||||||
if (pos1.line > pos2.line || (pos1.line === pos2.line && pos1.ch > pos2.ch)) {
|
if (
|
||||||
return {from: pos2, to: pos1}
|
pos1.line > pos2.line ||
|
||||||
|
(pos1.line === pos2.line && pos1.ch > pos2.ch)
|
||||||
|
) {
|
||||||
|
return { from: pos2, to: pos1 }
|
||||||
}
|
}
|
||||||
return {from: pos1, to: pos2}
|
return { from: pos1, to: pos2 }
|
||||||
}
|
}
|
||||||
|
|
||||||
const {from: smallerPos, to: higherPos} = sortRange(from, to)
|
const { from: smallerPos, to: higherPos } = sortRange(from, to)
|
||||||
for (let l = smallerPos.line; l <= higherPos.line; l++) {
|
for (let l = smallerPos.line; l <= higherPos.line; l++) {
|
||||||
const line = editor.getLine(l) || ''
|
const line = editor.getLine(l) || ''
|
||||||
let w = 0
|
let w = 0
|
||||||
@@ -95,9 +97,9 @@ function checkMultiLineRange (editor, from, to) {
|
|||||||
wEnd = higherPos.ch
|
wEnd = higherPos.ch
|
||||||
}
|
}
|
||||||
while (w <= wEnd) {
|
while (w <= wEnd) {
|
||||||
const wordRange = editor.findWordAt({line: l, ch: w})
|
const wordRange = editor.findWordAt({ line: l, ch: w })
|
||||||
self.checkWord(editor, wordRange)
|
self.checkWord(editor, wordRange)
|
||||||
w += (wordRange.head.ch - wordRange.anchor.ch) + 1
|
w += wordRange.head.ch - wordRange.anchor.ch + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,13 +112,15 @@ function checkMultiLineRange (editor, from, to) {
|
|||||||
* @param wordRange Object specifying the range that should be checked.
|
* @param wordRange Object specifying the range that should be checked.
|
||||||
* Having the following structure: <code>{anchor: {line: integer, ch: integer}, head: {line: integer, ch: integer}}</code>
|
* Having the following structure: <code>{anchor: {line: integer, ch: integer}, head: {line: integer, ch: integer}}</code>
|
||||||
*/
|
*/
|
||||||
function checkWord (editor, wordRange) {
|
function checkWord(editor, wordRange) {
|
||||||
const word = editor.getRange(wordRange.anchor, wordRange.head)
|
const word = editor.getRange(wordRange.anchor, wordRange.head)
|
||||||
if (word == null || word.length <= 3) {
|
if (word == null || word.length <= 3) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!dictionary.check(word)) {
|
if (!dictionary.check(word)) {
|
||||||
editor.markText(wordRange.anchor, wordRange.head, {className: styles[CSS_ERROR_CLASS]})
|
editor.markText(wordRange.anchor, wordRange.head, {
|
||||||
|
className: styles[CSS_ERROR_CLASS]
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,32 +130,40 @@ function checkWord (editor, wordRange) {
|
|||||||
* @param fromChangeObject codeMirror changeObject describing the start of the editing
|
* @param fromChangeObject codeMirror changeObject describing the start of the editing
|
||||||
* @param toChangeObject codeMirror changeObject describing the end of the editing
|
* @param toChangeObject codeMirror changeObject describing the end of the editing
|
||||||
*/
|
*/
|
||||||
function checkChangeRange (editor, fromChangeObject, toChangeObject) {
|
function checkChangeRange(editor, fromChangeObject, toChangeObject) {
|
||||||
/**
|
/**
|
||||||
* Calculate the smallest respectively largest position as a start, resp. end, position and return it
|
* Calculate the smallest respectively largest position as a start, resp. end, position and return it
|
||||||
* @param start CodeMirror change object
|
* @param start CodeMirror change object
|
||||||
* @param end CodeMirror change object
|
* @param end CodeMirror change object
|
||||||
* @returns {{start: {line: *, ch: *}, end: {line: *, ch: *}}}
|
* @returns {{start: {line: *, ch: *}, end: {line: *, ch: *}}}
|
||||||
*/
|
*/
|
||||||
function getStartAndEnd (start, end) {
|
function getStartAndEnd(start, end) {
|
||||||
const possiblePositions = [start.from, start.to, end.from, end.to]
|
const possiblePositions = [start.from, start.to, end.from, end.to]
|
||||||
let smallest = start.from
|
let smallest = start.from
|
||||||
let biggest = end.to
|
let biggest = end.to
|
||||||
for (const currentPos of possiblePositions) {
|
for (const currentPos of possiblePositions) {
|
||||||
if (currentPos.line < smallest.line || (currentPos.line === smallest.line && currentPos.ch < smallest.ch)) {
|
if (
|
||||||
|
currentPos.line < smallest.line ||
|
||||||
|
(currentPos.line === smallest.line && currentPos.ch < smallest.ch)
|
||||||
|
) {
|
||||||
smallest = currentPos
|
smallest = currentPos
|
||||||
}
|
}
|
||||||
if (currentPos.line > biggest.line || (currentPos.line === biggest.line && currentPos.ch > biggest.ch)) {
|
if (
|
||||||
|
currentPos.line > biggest.line ||
|
||||||
|
(currentPos.line === biggest.line && currentPos.ch > biggest.ch)
|
||||||
|
) {
|
||||||
biggest = currentPos
|
biggest = currentPos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {start: smallest, end: biggest}
|
return { start: smallest, end: biggest }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dictionary === null || editor == null) { return }
|
if (dictionary === null || editor == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {start, end} = getStartAndEnd(fromChangeObject, toChangeObject)
|
const { start, end } = getStartAndEnd(fromChangeObject, toChangeObject)
|
||||||
|
|
||||||
// Expand the range to include words after/before whitespaces
|
// Expand the range to include words after/before whitespaces
|
||||||
start.ch = Math.max(start.ch - 1, 0)
|
start.ch = Math.max(start.ch - 1, 0)
|
||||||
@@ -165,29 +177,40 @@ function checkChangeRange (editor, fromChangeObject, toChangeObject) {
|
|||||||
|
|
||||||
self.checkMultiLineRange(editor, start, end)
|
self.checkMultiLineRange(editor, start, end)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.info('Error during the spell check. It might be due to problems figuring out the range of the new text..', e)
|
console.info(
|
||||||
|
'Error during the spell check. It might be due to problems figuring out the range of the new text..',
|
||||||
|
e
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveLiveSpellCheckFrom (changeObject) {
|
function saveLiveSpellCheckFrom(changeObject) {
|
||||||
liveSpellCheckFrom = changeObject
|
liveSpellCheckFrom = changeObject
|
||||||
}
|
}
|
||||||
let liveSpellCheckFrom
|
let liveSpellCheckFrom
|
||||||
const debouncedSpellCheckLeading = _.debounce(saveLiveSpellCheckFrom, MILLISECONDS_TILL_LIVECHECK, {
|
const debouncedSpellCheckLeading = _.debounce(
|
||||||
'leading': true,
|
saveLiveSpellCheckFrom,
|
||||||
'trailing': false
|
MILLISECONDS_TILL_LIVECHECK,
|
||||||
})
|
{
|
||||||
const debouncedSpellCheck = _.debounce(checkChangeRange, MILLISECONDS_TILL_LIVECHECK, {
|
leading: true,
|
||||||
'leading': false,
|
trailing: false
|
||||||
'trailing': true
|
}
|
||||||
})
|
)
|
||||||
|
const debouncedSpellCheck = _.debounce(
|
||||||
|
checkChangeRange,
|
||||||
|
MILLISECONDS_TILL_LIVECHECK,
|
||||||
|
{
|
||||||
|
leading: false,
|
||||||
|
trailing: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a keystroke. Buffers the input and performs a live spell check after a certain time. Uses _debounce from lodash to buffer the input
|
* Handles a keystroke. Buffers the input and performs a live spell check after a certain time. Uses _debounce from lodash to buffer the input
|
||||||
* @param {Codemirror} editor CodeMirror-Editor
|
* @param {Codemirror} editor CodeMirror-Editor
|
||||||
* @param changeObject codeMirror changeObject
|
* @param changeObject codeMirror changeObject
|
||||||
*/
|
*/
|
||||||
function handleChange (editor, changeObject) {
|
function handleChange(editor, changeObject) {
|
||||||
if (dictionary === null) {
|
if (dictionary === null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -201,7 +224,7 @@ function handleChange (editor, changeObject) {
|
|||||||
* @param word word to be checked
|
* @param word word to be checked
|
||||||
* @returns {String[]} Array of suggestions
|
* @returns {String[]} Array of suggestions
|
||||||
*/
|
*/
|
||||||
function getSpellingSuggestion (word) {
|
function getSpellingSuggestion(word) {
|
||||||
if (dictionary == null || word == null) {
|
if (dictionary == null || word == null) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -211,7 +234,7 @@ function getSpellingSuggestion (word) {
|
|||||||
/**
|
/**
|
||||||
* Returns the name of the CSS class used for errors
|
* Returns the name of the CSS class used for errors
|
||||||
*/
|
*/
|
||||||
function getCSSClassName () {
|
function getCSSClassName() {
|
||||||
return styles[CSS_ERROR_CLASS]
|
return styles[CSS_ERROR_CLASS]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const TurndownService = require('turndown')
|
const TurndownService = require('turndown')
|
||||||
const { gfm } = require('turndown-plugin-gfm')
|
const { gfm } = require('turndown-plugin-gfm')
|
||||||
|
|
||||||
export const createTurndownService = function () {
|
export const createTurndownService = function() {
|
||||||
const turndown = new TurndownService()
|
const turndown = new TurndownService()
|
||||||
turndown.use(gfm)
|
turndown.use(gfm)
|
||||||
turndown.remove('script')
|
turndown.remove('script')
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export function lastFindInArray (array, callback) {
|
export function lastFindInArray(array, callback) {
|
||||||
for (let i = array.length - 1; i >= 0; --i) {
|
for (let i = array.length - 1; i >= 0; --i) {
|
||||||
if (callback(array[i], i, array)) {
|
if (callback(array[i], i, array)) {
|
||||||
return array[i]
|
return array[i]
|
||||||
@@ -6,7 +6,7 @@ export function lastFindInArray (array, callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function escapeHtmlCharacters (
|
export function escapeHtmlCharacters(
|
||||||
html,
|
html,
|
||||||
opt = { detectCodeBlock: false, skipSingleQuote: false }
|
opt = { detectCodeBlock: false, skipSingleQuote: false }
|
||||||
) {
|
) {
|
||||||
@@ -115,7 +115,7 @@ export function escapeHtmlCharacters (
|
|||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isObjectEqual (a, b) {
|
export function isObjectEqual(a, b) {
|
||||||
const aProps = Object.getOwnPropertyNames(a)
|
const aProps = Object.getOwnPropertyNames(a)
|
||||||
const bProps = Object.getOwnPropertyNames(b)
|
const bProps = Object.getOwnPropertyNames(b)
|
||||||
|
|
||||||
@@ -132,11 +132,13 @@ export function isObjectEqual (a, b) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isMarkdownTitleURL (str) {
|
export function isMarkdownTitleURL(str) {
|
||||||
return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
|
return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(
|
||||||
|
str
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function humanFileSize (bytes) {
|
export function humanFileSize(bytes) {
|
||||||
const threshold = 1000
|
const threshold = 1000
|
||||||
if (Math.abs(bytes) < threshold) {
|
if (Math.abs(bytes) < threshold) {
|
||||||
return bytes + ' B'
|
return bytes + ' B'
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import _ from 'lodash'
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
class FolderSelect extends React.Component {
|
class FolderSelect extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -16,24 +16,27 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
this.value = this.props.value
|
this.value = this.props.value
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
componentDidUpdate() {
|
||||||
this.value = this.props.value
|
this.value = this.props.value
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick (e) {
|
handleClick(e) {
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'SEARCH',
|
{
|
||||||
optionIndex: -1
|
status: 'SEARCH',
|
||||||
}, () => {
|
optionIndex: -1
|
||||||
this.refs.search.focus()
|
},
|
||||||
})
|
() => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFocus (e) {
|
handleFocus(e) {
|
||||||
if (this.state.status === 'IDLE') {
|
if (this.state.status === 'IDLE') {
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'FOCUS'
|
status: 'FOCUS'
|
||||||
@@ -41,7 +44,7 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBlur (e) {
|
handleBlur(e) {
|
||||||
if (this.state.status === 'FOCUS') {
|
if (this.state.status === 'FOCUS') {
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'IDLE'
|
status: 'IDLE'
|
||||||
@@ -49,40 +52,49 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown (e) {
|
handleKeyDown(e) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case 13:
|
case 13:
|
||||||
if (this.state.status === 'FOCUS') {
|
if (this.state.status === 'FOCUS') {
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'SEARCH',
|
{
|
||||||
optionIndex: -1
|
status: 'SEARCH',
|
||||||
}, () => {
|
optionIndex: -1
|
||||||
this.refs.search.focus()
|
},
|
||||||
})
|
() => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 40:
|
case 40:
|
||||||
case 38:
|
case 38:
|
||||||
if (this.state.status === 'FOCUS') {
|
if (this.state.status === 'FOCUS') {
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'SEARCH',
|
{
|
||||||
optionIndex: 0
|
status: 'SEARCH',
|
||||||
}, () => {
|
optionIndex: 0
|
||||||
this.refs.search.focus()
|
},
|
||||||
})
|
() => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 9:
|
case 9:
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const tabbable = document.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])')
|
const tabbable = document.querySelectorAll(
|
||||||
const previousEl = tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
|
'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])'
|
||||||
|
)
|
||||||
|
const previousEl =
|
||||||
|
tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
|
||||||
if (previousEl != null) previousEl.focus()
|
if (previousEl != null) previousEl.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchInputBlur (e) {
|
handleSearchInputBlur(e) {
|
||||||
if (e.relatedTarget !== this.refs.root) {
|
if (e.relatedTarget !== this.refs.root) {
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'IDLE'
|
status: 'IDLE'
|
||||||
@@ -90,14 +102,17 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchInputChange (e) {
|
handleSearchInputChange(e) {
|
||||||
const { folders } = this.props
|
const { folders } = this.props
|
||||||
const search = this.refs.search.value
|
const search = this.refs.search.value
|
||||||
const optionIndex = search.length > 0
|
const optionIndex =
|
||||||
? _.findIndex(folders, (folder) => {
|
search.length > 0
|
||||||
return folder.name.match(new RegExp('^' + _.escapeRegExp(search), 'i'))
|
? _.findIndex(folders, folder => {
|
||||||
})
|
return folder.name.match(
|
||||||
: -1
|
new RegExp('^' + _.escapeRegExp(search), 'i')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
: -1
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
search: this.refs.search.value,
|
search: this.refs.search.value,
|
||||||
@@ -105,7 +120,7 @@ class FolderSelect extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchInputKeyDown (e) {
|
handleSearchInputKeyDown(e) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case 40:
|
case 40:
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
@@ -121,15 +136,18 @@ class FolderSelect extends React.Component {
|
|||||||
break
|
break
|
||||||
case 27:
|
case 27:
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'FOCUS'
|
{
|
||||||
}, () => {
|
status: 'FOCUS'
|
||||||
this.refs.root.focus()
|
},
|
||||||
})
|
() => {
|
||||||
|
this.refs.root.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextOption () {
|
nextOption() {
|
||||||
let { optionIndex } = this.state
|
let { optionIndex } = this.state
|
||||||
const { folders } = this.props
|
const { folders } = this.props
|
||||||
|
|
||||||
@@ -141,7 +159,7 @@ class FolderSelect extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
previousOption () {
|
previousOption() {
|
||||||
const { folders } = this.props
|
const { folders } = this.props
|
||||||
let { optionIndex } = this.state
|
let { optionIndex } = this.state
|
||||||
|
|
||||||
@@ -153,46 +171,52 @@ class FolderSelect extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
selectOption () {
|
selectOption() {
|
||||||
const { folders } = this.props
|
const { folders } = this.props
|
||||||
const optionIndex = this.state.optionIndex
|
const optionIndex = this.state.optionIndex
|
||||||
|
|
||||||
const folder = folders[optionIndex]
|
const folder = folders[optionIndex]
|
||||||
if (folder != null) {
|
if (folder != null) {
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'FOCUS'
|
{
|
||||||
}, () => {
|
status: 'FOCUS'
|
||||||
this.setValue(folder.key)
|
},
|
||||||
this.refs.root.focus()
|
() => {
|
||||||
})
|
this.setValue(folder.key)
|
||||||
|
this.refs.root.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOptionClick (storageKey, folderKey) {
|
handleOptionClick(storageKey, folderKey) {
|
||||||
return (e) => {
|
return e => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'FOCUS'
|
{
|
||||||
}, () => {
|
status: 'FOCUS'
|
||||||
this.setValue(storageKey + '-' + folderKey)
|
},
|
||||||
this.refs.root.focus()
|
() => {
|
||||||
})
|
this.setValue(storageKey + '-' + folderKey)
|
||||||
|
this.refs.root.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue (value) {
|
setValue(value) {
|
||||||
this.value = value
|
this.value = value
|
||||||
this.props.onChange()
|
this.props.onChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { className, data, value } = this.props
|
const { className, data, value } = this.props
|
||||||
const splitted = value.split('-')
|
const splitted = value.split('-')
|
||||||
const storageKey = splitted.shift()
|
const storageKey = splitted.shift()
|
||||||
const folderKey = splitted.shift()
|
const folderKey = splitted.shift()
|
||||||
let options = []
|
let options = []
|
||||||
data.storageMap.forEach((storage, index) => {
|
data.storageMap.forEach((storage, index) => {
|
||||||
storage.folders.forEach((folder) => {
|
storage.folders.forEach(folder => {
|
||||||
options.push({
|
options.push({
|
||||||
storage: storage,
|
storage: storage,
|
||||||
folder: folder
|
folder: folder
|
||||||
@@ -200,68 +224,78 @@ class FolderSelect extends React.Component {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
const currentOption = options.filter(
|
||||||
|
option =>
|
||||||
|
option.storage.key === storageKey && option.folder.key === folderKey
|
||||||
|
)[0]
|
||||||
|
|
||||||
if (this.state.search.trim().length > 0) {
|
if (this.state.search.trim().length > 0) {
|
||||||
const filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
|
const filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
|
||||||
options = options.filter((option) => filter.test(option.folder.name))
|
options = options.filter(option => filter.test(option.folder.name))
|
||||||
}
|
}
|
||||||
|
|
||||||
const optionList = options
|
const optionList = options.map((option, index) => {
|
||||||
.map((option, index) => {
|
return (
|
||||||
return (
|
<div
|
||||||
<div styleName={index === this.state.optionIndex
|
styleName={
|
||||||
|
index === this.state.optionIndex
|
||||||
? 'search-optionList-item--active'
|
? 'search-optionList-item--active'
|
||||||
: 'search-optionList-item'
|
: 'search-optionList-item'
|
||||||
}
|
}
|
||||||
key={option.storage.key + '-' + option.folder.key}
|
key={option.storage.key + '-' + option.folder.key}
|
||||||
onClick={(e) => this.handleOptionClick(option.storage.key, option.folder.key)(e)}
|
onClick={e =>
|
||||||
|
this.handleOptionClick(option.storage.key, option.folder.key)(e)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
styleName='search-optionList-item-name'
|
||||||
|
style={{ borderColor: option.folder.color }}
|
||||||
>
|
>
|
||||||
<span styleName='search-optionList-item-name'
|
{option.folder.name}
|
||||||
style={{borderColor: option.folder.color}}
|
<span styleName='search-optionList-item-name-surfix'>
|
||||||
>
|
in {option.storage.name}
|
||||||
{option.folder.name}
|
|
||||||
<span styleName='search-optionList-item-name-surfix'>in {option.storage.name}</span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</span>
|
||||||
)
|
</div>
|
||||||
})
|
)
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={_.isString(className)
|
<div
|
||||||
? 'FolderSelect ' + className
|
className={
|
||||||
: 'FolderSelect'
|
_.isString(className) ? 'FolderSelect ' + className : 'FolderSelect'
|
||||||
}
|
}
|
||||||
styleName={this.state.status === 'SEARCH'
|
styleName={
|
||||||
? 'root--search'
|
this.state.status === 'SEARCH'
|
||||||
: this.state.status === 'FOCUS'
|
? 'root--search'
|
||||||
? 'root--focus'
|
: this.state.status === 'FOCUS'
|
||||||
: 'root'
|
? 'root--focus'
|
||||||
|
: 'root'
|
||||||
}
|
}
|
||||||
ref='root'
|
ref='root'
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
onClick={(e) => this.handleClick(e)}
|
onClick={e => this.handleClick(e)}
|
||||||
onFocus={(e) => this.handleFocus(e)}
|
onFocus={e => this.handleFocus(e)}
|
||||||
onBlur={(e) => this.handleBlur(e)}
|
onBlur={e => this.handleBlur(e)}
|
||||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
onKeyDown={e => this.handleKeyDown(e)}
|
||||||
>
|
>
|
||||||
{this.state.status === 'SEARCH'
|
{this.state.status === 'SEARCH' ? (
|
||||||
? <div styleName='search'>
|
<div styleName='search'>
|
||||||
<input styleName='search-input'
|
<input
|
||||||
|
styleName='search-input'
|
||||||
ref='search'
|
ref='search'
|
||||||
value={this.state.search}
|
value={this.state.search}
|
||||||
placeholder={i18n.__('Folder...')}
|
placeholder={i18n.__('Folder...')}
|
||||||
onChange={(e) => this.handleSearchInputChange(e)}
|
onChange={e => this.handleSearchInputChange(e)}
|
||||||
onBlur={(e) => this.handleSearchInputBlur(e)}
|
onBlur={e => this.handleSearchInputBlur(e)}
|
||||||
onKeyDown={(e) => this.handleSearchInputKeyDown(e)}
|
onKeyDown={e => this.handleSearchInputKeyDown(e)}
|
||||||
/>
|
/>
|
||||||
<div styleName='search-optionList'
|
<div styleName='search-optionList' ref='optionList'>
|
||||||
ref='optionList'
|
|
||||||
>
|
|
||||||
{optionList}
|
{optionList}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
: <div styleName='idle' style={{color: currentOption.folder.color}}>
|
) : (
|
||||||
|
<div styleName='idle' style={{ color: currentOption.folder.color }}>
|
||||||
<div styleName='idle-label'>
|
<div styleName='idle-label'>
|
||||||
<i className='fa fa-folder' />
|
<i className='fa fa-folder' />
|
||||||
<span styleName='idle-label-name'>
|
<span styleName='idle-label-name'>
|
||||||
@@ -269,8 +303,7 @@ class FolderSelect extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -280,11 +313,13 @@ FolderSelect.propTypes = {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
folders: PropTypes.arrayOf(PropTypes.shape({
|
folders: PropTypes.arrayOf(
|
||||||
key: PropTypes.string,
|
PropTypes.shape({
|
||||||
name: PropTypes.string,
|
key: PropTypes.string,
|
||||||
color: PropTypes.string
|
name: PropTypes.string,
|
||||||
}))
|
color: PropTypes.string
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(FolderSelect, styles)
|
export default CSSModules(FolderSelect, styles)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import _ from 'lodash'
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
class FromUrlButton extends React.Component {
|
class FromUrlButton extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -14,44 +14,46 @@ class FromUrlButton extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseDown (e) {
|
handleMouseDown(e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isActive: true
|
isActive: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp (e) {
|
handleMouseUp(e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isActive: false
|
isActive: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseLeave (e) {
|
handleMouseLeave(e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isActive: false
|
isActive: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { className } = this.props
|
const { className } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className={_.isString(className)
|
<button
|
||||||
? 'FromUrlButton ' + className
|
className={
|
||||||
: 'FromUrlButton'
|
_.isString(className) ? 'FromUrlButton ' + className : 'FromUrlButton'
|
||||||
}
|
}
|
||||||
styleName={this.state.isActive || this.props.isActive
|
styleName={
|
||||||
? 'root--active'
|
this.state.isActive || this.props.isActive ? 'root--active' : 'root'
|
||||||
: 'root'
|
|
||||||
}
|
}
|
||||||
onMouseDown={(e) => this.handleMouseDown(e)}
|
onMouseDown={e => this.handleMouseDown(e)}
|
||||||
onMouseUp={(e) => this.handleMouseUp(e)}
|
onMouseUp={e => this.handleMouseUp(e)}
|
||||||
onMouseLeave={(e) => this.handleMouseLeave(e)}
|
onMouseLeave={e => this.handleMouseLeave(e)}
|
||||||
onClick={this.props.onClick}>
|
onClick={this.props.onClick}
|
||||||
<img styleName='icon'
|
>
|
||||||
src={this.state.isActive || this.props.isActive
|
<img
|
||||||
? '../resources/icon/icon-external.svg'
|
styleName='icon'
|
||||||
: '../resources/icon/icon-external.svg'
|
src={
|
||||||
|
this.state.isActive || this.props.isActive
|
||||||
|
? '../resources/icon/icon-external.svg'
|
||||||
|
: '../resources/icon/icon-external.svg'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<span styleName='tooltip'>{i18n.__('Convert URL to Markdown')}</span>
|
<span styleName='tooltip'>{i18n.__('Convert URL to Markdown')}</span>
|
||||||
|
|||||||
@@ -5,14 +5,18 @@ import styles from './FullscreenButton.styl'
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
const FullscreenButton = ({
|
const FullscreenButton = ({ onClick }) => {
|
||||||
onClick
|
|
||||||
}) => {
|
|
||||||
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
|
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
|
||||||
return (
|
return (
|
||||||
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
|
<button
|
||||||
|
styleName='control-fullScreenButton'
|
||||||
|
title={i18n.__('Fullscreen')}
|
||||||
|
onMouseDown={e => onClick(e)}
|
||||||
|
>
|
||||||
<img src='../resources/icon/icon-full.svg' />
|
<img src='../resources/icon/icon-full.svg' />
|
||||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
|
<span lang={i18n.locale} styleName='tooltip'>
|
||||||
|
{i18n.__('Fullscreen')}({hotkey})
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './InfoButton.styl'
|
import styles from './InfoButton.styl'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const InfoButton = ({
|
const InfoButton = ({ onClick }) => (
|
||||||
onClick
|
<button styleName='control-infoButton' onClick={e => onClick(e)}>
|
||||||
}) => (
|
|
||||||
<button styleName='control-infoButton'
|
|
||||||
onClick={(e) => onClick(e)}
|
|
||||||
>
|
|
||||||
<img className='infoButton' src='../resources/icon/icon-info.svg' />
|
<img className='infoButton' src='../resources/icon/icon-info.svg' />
|
||||||
<span styleName='tooltip'>{i18n.__('Info')}</span>
|
<span styleName='tooltip'>{i18n.__('Info')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -6,28 +6,47 @@ import copy from 'copy-to-clipboard'
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
class InfoPanel extends React.Component {
|
class InfoPanel extends React.Component {
|
||||||
copyNoteLink () {
|
copyNoteLink() {
|
||||||
const {noteLink} = this.props
|
const { noteLink } = this.props
|
||||||
this.refs.noteLink.select()
|
this.refs.noteLink.select()
|
||||||
copy(noteLink)
|
copy(noteLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const {
|
const {
|
||||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf, wordCount, letterCount, type, print
|
storageName,
|
||||||
|
folderName,
|
||||||
|
noteLink,
|
||||||
|
updatedAt,
|
||||||
|
createdAt,
|
||||||
|
exportAsMd,
|
||||||
|
exportAsTxt,
|
||||||
|
exportAsHtml,
|
||||||
|
exportAsPdf,
|
||||||
|
wordCount,
|
||||||
|
letterCount,
|
||||||
|
type,
|
||||||
|
print
|
||||||
} = this.props
|
} = this.props
|
||||||
return (
|
return (
|
||||||
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
<div
|
||||||
|
className='infoPanel'
|
||||||
|
styleName='control-infoButton-panel'
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<p styleName='modification-date'>{updatedAt}</p>
|
<p styleName='modification-date'>{updatedAt}</p>
|
||||||
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
|
<p styleName='modification-date-desc'>
|
||||||
|
{i18n.__('MODIFICATION DATE')}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
{type === 'SNIPPET_NOTE'
|
{type === 'SNIPPET_NOTE' ? (
|
||||||
? ''
|
''
|
||||||
: <div styleName='count-wrap'>
|
) : (
|
||||||
|
<div styleName='count-wrap'>
|
||||||
<div styleName='count-number'>
|
<div styleName='count-number'>
|
||||||
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
|
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
|
||||||
<p styleName='infoPanel-sub-count'>{i18n.__('Words')}</p>
|
<p styleName='infoPanel-sub-count'>{i18n.__('Words')}</p>
|
||||||
@@ -37,12 +56,9 @@ class InfoPanel extends React.Component {
|
|||||||
<p styleName='infoPanel-sub-count'>{i18n.__('Letters')}</p>
|
<p styleName='infoPanel-sub-count'>{i18n.__('Letters')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
|
|
||||||
{type === 'SNIPPET_NOTE'
|
{type === 'SNIPPET_NOTE' ? '' : <hr />}
|
||||||
? ''
|
|
||||||
: <hr />
|
|
||||||
}
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p styleName='infoPanel-default'>{storageName}</p>
|
<p styleName='infoPanel-default'>{storageName}</p>
|
||||||
@@ -60,8 +76,18 @@ class InfoPanel extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input styleName='infoPanel-noteLink' ref='noteLink' defaultValue={noteLink} onClick={(e) => { e.target.select() }} />
|
<input
|
||||||
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
|
styleName='infoPanel-noteLink'
|
||||||
|
ref='noteLink'
|
||||||
|
defaultValue={noteLink}
|
||||||
|
onClick={e => {
|
||||||
|
e.target.select()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => this.copyNoteLink()}
|
||||||
|
styleName='infoPanel-copyButton'
|
||||||
|
>
|
||||||
<i className='fa fa-clipboard' />
|
<i className='fa fa-clipboard' />
|
||||||
</button>
|
</button>
|
||||||
<p styleName='infoPanel-sub'>{i18n.__('NOTE LINK')}</p>
|
<p styleName='infoPanel-sub'>{i18n.__('NOTE LINK')}</p>
|
||||||
@@ -70,27 +96,39 @@ class InfoPanel extends React.Component {
|
|||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div id='export-wrap'>
|
<div id='export-wrap'>
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsMd(e, 'export-md')}
|
||||||
|
>
|
||||||
<i className='fa fa-file-code-o' />
|
<i className='fa fa-file-code-o' />
|
||||||
<p>{i18n.__('.md')}</p>
|
<p>{i18n.__('.md')}</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e, 'export-txt')}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsTxt(e, 'export-txt')}
|
||||||
|
>
|
||||||
<i className='fa fa-file-text-o' />
|
<i className='fa fa-file-text-o' />
|
||||||
<p>{i18n.__('.txt')}</p>
|
<p>{i18n.__('.txt')}</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsHtml(e, 'export-html')}
|
||||||
|
>
|
||||||
<i className='fa fa-html5' />
|
<i className='fa fa-html5' />
|
||||||
<p>{i18n.__('.html')}</p>
|
<p>{i18n.__('.html')}</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsPdf(e, 'export-pdf')}
|
||||||
|
>
|
||||||
<i className='fa fa-file-pdf-o' />
|
<i className='fa fa-file-pdf-o' />
|
||||||
<p>{i18n.__('.pdf')}</p>
|
<p>{i18n.__('.pdf')}</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => print(e, 'print')}>
|
<button styleName='export--enable' onClick={e => print(e, 'print')}>
|
||||||
<i className='fa fa-print' />
|
<i className='fa fa-print' />
|
||||||
<p>{i18n.__('Print')}</p>
|
<p>{i18n.__('Print')}</p>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -5,9 +5,20 @@ import styles from './InfoPanel.styl'
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const InfoPanelTrashed = ({
|
const InfoPanelTrashed = ({
|
||||||
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf
|
storageName,
|
||||||
|
folderName,
|
||||||
|
updatedAt,
|
||||||
|
createdAt,
|
||||||
|
exportAsMd,
|
||||||
|
exportAsTxt,
|
||||||
|
exportAsHtml,
|
||||||
|
exportAsPdf
|
||||||
}) => (
|
}) => (
|
||||||
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
|
<div
|
||||||
|
className='infoPanel'
|
||||||
|
styleName='control-infoButton-panel-trash'
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<p styleName='modification-date'>{updatedAt}</p>
|
<p styleName='modification-date'>{updatedAt}</p>
|
||||||
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
|
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
|
||||||
@@ -21,7 +32,10 @@ const InfoPanelTrashed = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p styleName='infoPanel-default'><text styleName='infoPanel-trash'>Trash</text>{folderName}</p>
|
<p styleName='infoPanel-default'>
|
||||||
|
<text styleName='infoPanel-trash'>Trash</text>
|
||||||
|
{folderName}
|
||||||
|
</p>
|
||||||
<p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p>
|
<p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -31,22 +45,34 @@ const InfoPanelTrashed = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id='export-wrap'>
|
<div id='export-wrap'>
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsMd(e, 'export-md')}
|
||||||
|
>
|
||||||
<i className='fa fa-file-code-o' />
|
<i className='fa fa-file-code-o' />
|
||||||
<p>.md</p>
|
<p>.md</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e, 'export-txt')}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsTxt(e, 'export-txt')}
|
||||||
|
>
|
||||||
<i className='fa fa-file-text-o' />
|
<i className='fa fa-file-text-o' />
|
||||||
<p>.txt</p>
|
<p>.txt</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsHtml(e, 'export-html')}
|
||||||
|
>
|
||||||
<i className='fa fa-html5' />
|
<i className='fa fa-html5' />
|
||||||
<p>.html</p>
|
<p>.html</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsPdf(e, 'export-pdf')}
|
||||||
|
>
|
||||||
<i className='fa fa-file-pdf-o' />
|
<i className='fa fa-file-pdf-o' />
|
||||||
<p>.pdf</p>
|
<p>.pdf</p>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -34,16 +34,19 @@ import { replace } from 'connected-react-router'
|
|||||||
import ToggleDirectionButton from 'browser/main/Detail/ToggleDirectionButton'
|
import ToggleDirectionButton from 'browser/main/Detail/ToggleDirectionButton'
|
||||||
|
|
||||||
class MarkdownNoteDetail extends React.Component {
|
class MarkdownNoteDetail extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isMovingNote: false,
|
isMovingNote: false,
|
||||||
note: Object.assign({
|
note: Object.assign(
|
||||||
title: '',
|
{
|
||||||
content: '',
|
title: '',
|
||||||
linesHighlighted: []
|
content: '',
|
||||||
}, props.note),
|
linesHighlighted: []
|
||||||
|
},
|
||||||
|
props.note
|
||||||
|
),
|
||||||
isLockButtonShown: props.config.editor.type !== 'SPLIT',
|
isLockButtonShown: props.config.editor.type !== 'SPLIT',
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
editorType: props.config.editor.type,
|
editorType: props.config.editor.type,
|
||||||
@@ -57,37 +60,42 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
this.generateToc = () => this.handleGenerateToc()
|
this.generateToc = () => this.handleGenerateToc()
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus() {
|
||||||
this.refs.content.focus()
|
this.refs.content.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
ee.on('topbar:toggledirectionbutton', () => this.handleSwitchDirection())
|
ee.on('topbar:toggledirectionbutton', () => this.handleSwitchDirection())
|
||||||
ee.on('topbar:togglemodebutton', () => {
|
ee.on('topbar:togglemodebutton', () => {
|
||||||
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
const reversedType =
|
||||||
|
this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
||||||
this.handleSwitchMode(reversedType)
|
this.handleSwitchMode(reversedType)
|
||||||
})
|
})
|
||||||
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
|
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
|
||||||
ee.on('code:generate-toc', this.generateToc)
|
ee.on('code:generate-toc', this.generateToc)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
const isNewNote = nextProps.note.key !== this.props.note.key
|
const isNewNote = nextProps.note.key !== this.props.note.key
|
||||||
const hasDeletedTags = nextProps.note.tags.length < this.props.note.tags.length
|
const hasDeletedTags =
|
||||||
|
nextProps.note.tags.length < this.props.note.tags.length
|
||||||
if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) {
|
if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) {
|
||||||
if (this.saveQueue != null) this.saveNow()
|
if (this.saveQueue != null) this.saveNow()
|
||||||
this.setState({
|
this.setState(
|
||||||
note: Object.assign({linesHighlighted: []}, nextProps.note)
|
{
|
||||||
}, () => {
|
note: Object.assign({ linesHighlighted: [] }, nextProps.note)
|
||||||
this.refs.content.reload()
|
},
|
||||||
if (this.refs.tags) this.refs.tags.reset()
|
() => {
|
||||||
})
|
this.refs.content.reload()
|
||||||
|
if (this.refs.tags) this.refs.tags.reset()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focus content if using blur or double click
|
// Focus content if using blur or double click
|
||||||
// --> Moved here from componentDidMount so a re-render during search won't set focus to the editor
|
// --> Moved here from componentDidMount so a re-render during search won't set focus to the editor
|
||||||
const {switchPreview} = nextProps.config.editor
|
const { switchPreview } = nextProps.config.editor
|
||||||
|
|
||||||
if (this.state.switchPreview !== switchPreview) {
|
if (this.state.switchPreview !== switchPreview) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -100,24 +108,28 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount() {
|
||||||
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
ee.on('topbar:toggledirectionbutton', this.handleSwitchDirection)
|
ee.on('topbar:toggledirectionbutton', this.handleSwitchDirection)
|
||||||
ee.off('code:generate-toc', this.generateToc)
|
ee.off('code:generate-toc', this.generateToc)
|
||||||
if (this.saveQueue != null) this.saveNow()
|
if (this.saveQueue != null) this.saveNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdateTag () {
|
handleUpdateTag() {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
if (this.refs.tags) note.tags = this.refs.tags.value
|
if (this.refs.tags) note.tags = this.refs.tags.value
|
||||||
this.updateNote(note)
|
this.updateNote(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdateContent () {
|
handleUpdateContent() {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
note.content = this.refs.content.value
|
note.content = this.refs.content.value
|
||||||
|
|
||||||
let title = findNoteTitle(note.content, this.props.config.editor.enableFrontMatterTitle, this.props.config.editor.frontMatterTitleField)
|
let title = findNoteTitle(
|
||||||
|
note.content,
|
||||||
|
this.props.config.editor.enableFrontMatterTitle,
|
||||||
|
this.props.config.editor.frontMatterTitleField
|
||||||
|
)
|
||||||
title = striptags(title)
|
title = striptags(title)
|
||||||
title = markdown.strip(title)
|
title = markdown.strip(title)
|
||||||
note.title = title
|
note.title = title
|
||||||
@@ -125,37 +137,35 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
this.updateNote(note)
|
this.updateNote(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNote (note) {
|
updateNote(note) {
|
||||||
note.updatedAt = new Date()
|
note.updatedAt = new Date()
|
||||||
this.setState({note}, () => {
|
this.setState({ note }, () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
save () {
|
save() {
|
||||||
clearTimeout(this.saveQueue)
|
clearTimeout(this.saveQueue)
|
||||||
this.saveQueue = setTimeout(() => {
|
this.saveQueue = setTimeout(() => {
|
||||||
this.saveNow()
|
this.saveNow()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveNow () {
|
saveNow() {
|
||||||
const { note, dispatch } = this.props
|
const { note, dispatch } = this.props
|
||||||
clearTimeout(this.saveQueue)
|
clearTimeout(this.saveQueue)
|
||||||
this.saveQueue = null
|
this.saveQueue = null
|
||||||
|
|
||||||
dataApi
|
dataApi.updateNote(note.storage, note.key, this.state.note).then(note => {
|
||||||
.updateNote(note.storage, note.key, this.state.note)
|
dispatch({
|
||||||
.then((note) => {
|
type: 'UPDATE_NOTE',
|
||||||
dispatch({
|
note: note
|
||||||
type: 'UPDATE_NOTE',
|
|
||||||
note: note
|
|
||||||
})
|
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
|
||||||
})
|
})
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFolderChange (e) {
|
handleFolderChange(e) {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
const value = this.refs.folder.value
|
const value = this.refs.folder.value
|
||||||
const splitted = value.split('-')
|
const splitted = value.split('-')
|
||||||
@@ -164,64 +174,71 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
dataApi
|
dataApi
|
||||||
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
||||||
.then((newNote) => {
|
.then(newNote => {
|
||||||
this.setState({
|
this.setState(
|
||||||
isMovingNote: true,
|
{
|
||||||
note: Object.assign({}, newNote)
|
isMovingNote: true,
|
||||||
}, () => {
|
note: Object.assign({}, newNote)
|
||||||
const { dispatch, location } = this.props
|
},
|
||||||
dispatch({
|
() => {
|
||||||
type: 'MOVE_NOTE',
|
const { dispatch, location } = this.props
|
||||||
originNote: note,
|
dispatch({
|
||||||
note: newNote
|
type: 'MOVE_NOTE',
|
||||||
})
|
originNote: note,
|
||||||
dispatch(replace({
|
note: newNote
|
||||||
pathname: location.pathname,
|
|
||||||
search: queryString.stringify({
|
|
||||||
key: newNote.key
|
|
||||||
})
|
})
|
||||||
}))
|
dispatch(
|
||||||
this.setState({
|
replace({
|
||||||
isMovingNote: false
|
pathname: location.pathname,
|
||||||
})
|
search: queryString.stringify({
|
||||||
})
|
key: newNote.key
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
this.setState({
|
||||||
|
isMovingNote: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStarButtonClick (e) {
|
handleStarButtonClick(e) {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
|
if (!note.isStarred)
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
|
||||||
|
|
||||||
note.isStarred = !note.isStarred
|
note.isStarred = !note.isStarred
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
note
|
{
|
||||||
}, () => {
|
note
|
||||||
this.save()
|
},
|
||||||
})
|
() => {
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
exportAsFile () {
|
exportAsFile() {}
|
||||||
|
|
||||||
}
|
exportAsMd() {
|
||||||
|
|
||||||
exportAsMd () {
|
|
||||||
ee.emit('export:save-md')
|
ee.emit('export:save-md')
|
||||||
}
|
}
|
||||||
|
|
||||||
exportAsTxt () {
|
exportAsTxt() {
|
||||||
ee.emit('export:save-text')
|
ee.emit('export:save-text')
|
||||||
}
|
}
|
||||||
|
|
||||||
exportAsHtml () {
|
exportAsHtml() {
|
||||||
ee.emit('export:save-html')
|
ee.emit('export:save-html')
|
||||||
}
|
}
|
||||||
|
|
||||||
exportAsPdf () {
|
exportAsPdf() {
|
||||||
ee.emit('export:save-pdf')
|
ee.emit('export:save-pdf')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown (e) {
|
handleKeyDown(e) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
// tab key
|
// tab key
|
||||||
case 9:
|
case 9:
|
||||||
@@ -231,7 +248,11 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
} else if (e.ctrlKey && e.shiftKey) {
|
} else if (e.ctrlKey && e.shiftKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.jumpPrevTab()
|
this.jumpPrevTab()
|
||||||
} else if (!e.ctrlKey && !e.shiftKey && e.target === this.refs.description) {
|
} else if (
|
||||||
|
!e.ctrlKey &&
|
||||||
|
!e.shiftKey &&
|
||||||
|
e.target === this.refs.description
|
||||||
|
) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.focusEditor()
|
this.focusEditor()
|
||||||
}
|
}
|
||||||
@@ -239,9 +260,8 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
// I key
|
// I key
|
||||||
case 73:
|
case 73:
|
||||||
{
|
{
|
||||||
const isSuper = global.process.platform === 'darwin'
|
const isSuper =
|
||||||
? e.metaKey
|
global.process.platform === 'darwin' ? e.metaKey : e.ctrlKey
|
||||||
: e.ctrlKey
|
|
||||||
if (isSuper) {
|
if (isSuper) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.handleInfoButtonClick(e)
|
this.handleInfoButtonClick(e)
|
||||||
@@ -251,17 +271,17 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTrashButtonClick (e) {
|
handleTrashButtonClick(e) {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
const { isTrashed } = note
|
const { isTrashed } = note
|
||||||
const { confirmDeletion } = this.props.config.ui
|
const { confirmDeletion } = this.props.config.ui
|
||||||
|
|
||||||
if (isTrashed) {
|
if (isTrashed) {
|
||||||
if (confirmDeleteNote(confirmDeletion, true)) {
|
if (confirmDeleteNote(confirmDeletion, true)) {
|
||||||
const {note, dispatch} = this.props
|
const { note, dispatch } = this.props
|
||||||
dataApi
|
dataApi
|
||||||
.deleteNote(note.storage, note.key)
|
.deleteNote(note.storage, note.key)
|
||||||
.then((data) => {
|
.then(data => {
|
||||||
const dispatchHandler = () => {
|
const dispatchHandler = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'DELETE_NOTE',
|
type: 'DELETE_NOTE',
|
||||||
@@ -277,109 +297,124 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
if (confirmDeleteNote(confirmDeletion, false)) {
|
if (confirmDeleteNote(confirmDeletion, false)) {
|
||||||
note.isTrashed = true
|
note.isTrashed = true
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
note
|
{
|
||||||
}, () => {
|
note
|
||||||
this.save()
|
},
|
||||||
})
|
() => {
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
ee.emit('list:next')
|
ee.emit('list:next')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUndoButtonClick (e) {
|
handleUndoButtonClick(e) {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
|
|
||||||
note.isTrashed = false
|
note.isTrashed = false
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
note
|
{
|
||||||
}, () => {
|
note
|
||||||
this.save()
|
},
|
||||||
this.refs.content.reload()
|
() => {
|
||||||
ee.emit('list:next')
|
this.save()
|
||||||
})
|
this.refs.content.reload()
|
||||||
|
ee.emit('list:next')
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFullScreenButton (e) {
|
handleFullScreenButton(e) {
|
||||||
ee.emit('editor:fullscreen')
|
ee.emit('editor:fullscreen')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLockButtonMouseDown (e) {
|
handleLockButtonMouseDown(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
ee.emit('editor:lock')
|
ee.emit('editor:lock')
|
||||||
this.setState({ isLocked: !this.state.isLocked })
|
this.setState({ isLocked: !this.state.isLocked })
|
||||||
if (this.state.isLocked) this.focus()
|
if (this.state.isLocked) this.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
getToggleLockButton () {
|
getToggleLockButton() {
|
||||||
return this.state.isLocked ? '../resources/icon/icon-lock.svg' : '../resources/icon/icon-unlock.svg'
|
return this.state.isLocked
|
||||||
|
? '../resources/icon/icon-lock.svg'
|
||||||
|
: '../resources/icon/icon-unlock.svg'
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteKeyDown (e) {
|
handleDeleteKeyDown(e) {
|
||||||
if (e.keyCode === 27) this.handleDeleteCancelButtonClick(e)
|
if (e.keyCode === 27) this.handleDeleteCancelButtonClick(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleToggleLockButton (event, noteStatus) {
|
handleToggleLockButton(event, noteStatus) {
|
||||||
// first argument event is not used
|
// first argument event is not used
|
||||||
if (noteStatus === 'CODE') {
|
if (noteStatus === 'CODE') {
|
||||||
this.setState({isLockButtonShown: true})
|
this.setState({ isLockButtonShown: true })
|
||||||
} else {
|
} else {
|
||||||
this.setState({isLockButtonShown: false})
|
this.setState({ isLockButtonShown: false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleGenerateToc () {
|
handleGenerateToc() {
|
||||||
const editor = this.refs.content.refs.code.editor
|
const editor = this.refs.content.refs.code.editor
|
||||||
markdownToc.generateInEditor(editor)
|
markdownToc.generateInEditor(editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFocus (e) {
|
handleFocus(e) {
|
||||||
this.focus()
|
this.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInfoButtonClick (e) {
|
handleInfoButtonClick(e) {
|
||||||
const infoPanel = document.querySelector('.infoPanel')
|
const infoPanel = document.querySelector('.infoPanel')
|
||||||
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
|
if (infoPanel.style)
|
||||||
|
infoPanel.style.display =
|
||||||
|
infoPanel.style.display === 'none' ? 'inline' : 'none'
|
||||||
}
|
}
|
||||||
|
|
||||||
print (e) {
|
print(e) {
|
||||||
ee.emit('print')
|
ee.emit('print')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSwitchMode (type) {
|
handleSwitchMode(type) {
|
||||||
// If in split mode, hide the lock button
|
// If in split mode, hide the lock button
|
||||||
this.setState({ editorType: type, isLockButtonShown: !(type === 'SPLIT') }, () => {
|
this.setState(
|
||||||
this.focus()
|
{ editorType: type, isLockButtonShown: !(type === 'SPLIT') },
|
||||||
const newConfig = Object.assign({}, this.props.config)
|
() => {
|
||||||
newConfig.editor.type = type
|
this.focus()
|
||||||
ConfigManager.set(newConfig)
|
const newConfig = Object.assign({}, this.props.config)
|
||||||
})
|
newConfig.editor.type = type
|
||||||
|
ConfigManager.set(newConfig)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSwitchDirection () {
|
handleSwitchDirection() {
|
||||||
// If in split mode, hide the lock button
|
// If in split mode, hide the lock button
|
||||||
const direction = this.state.RTL
|
const direction = this.state.RTL
|
||||||
this.setState({ RTL: !direction })
|
this.setState({ RTL: !direction })
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteNote () {
|
handleDeleteNote() {
|
||||||
this.handleTrashButtonClick()
|
this.handleTrashButtonClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClearTodo () {
|
handleClearTodo() {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
const splitted = note.content.split('\n')
|
const splitted = note.content.split('\n')
|
||||||
|
|
||||||
const clearTodoContent = splitted.map((line) => {
|
const clearTodoContent = splitted
|
||||||
const trimmedLine = line.trim()
|
.map(line => {
|
||||||
if (trimmedLine.match(/\[x\]/i)) {
|
const trimmedLine = line.trim()
|
||||||
return line.replace(/\[x\]/i, '[ ]')
|
if (trimmedLine.match(/\[x\]/i)) {
|
||||||
} else {
|
return line.replace(/\[x\]/i, '[ ]')
|
||||||
return line
|
} else {
|
||||||
}
|
return line
|
||||||
}).join('\n')
|
}
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
note.content = clearTodoContent
|
note.content = clearTodoContent
|
||||||
this.refs.content.setValue(note.content)
|
this.refs.content.setValue(note.content)
|
||||||
@@ -387,40 +422,44 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
this.updateNote(note)
|
this.updateNote(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEditor () {
|
renderEditor() {
|
||||||
const { config, ignorePreviewPointerEvents } = this.props
|
const { config, ignorePreviewPointerEvents } = this.props
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
|
|
||||||
if (this.state.editorType === 'EDITOR_PREVIEW') {
|
if (this.state.editorType === 'EDITOR_PREVIEW') {
|
||||||
return <MarkdownEditor
|
return (
|
||||||
ref='content'
|
<MarkdownEditor
|
||||||
styleName='body-noteEditor'
|
ref='content'
|
||||||
config={config}
|
styleName='body-noteEditor'
|
||||||
value={note.content}
|
config={config}
|
||||||
storageKey={note.storage}
|
value={note.content}
|
||||||
noteKey={note.key}
|
storageKey={note.storage}
|
||||||
linesHighlighted={note.linesHighlighted}
|
noteKey={note.key}
|
||||||
onChange={this.handleUpdateContent.bind(this)}
|
linesHighlighted={note.linesHighlighted}
|
||||||
isLocked={this.state.isLocked}
|
onChange={this.handleUpdateContent.bind(this)}
|
||||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
isLocked={this.state.isLocked}
|
||||||
RTL={this.state.RTL}
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
/>
|
RTL={this.state.RTL}
|
||||||
|
/>
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
return <MarkdownSplitEditor
|
return (
|
||||||
ref='content'
|
<MarkdownSplitEditor
|
||||||
config={config}
|
ref='content'
|
||||||
value={note.content}
|
config={config}
|
||||||
storageKey={note.storage}
|
value={note.content}
|
||||||
noteKey={note.key}
|
storageKey={note.storage}
|
||||||
linesHighlighted={note.linesHighlighted}
|
noteKey={note.key}
|
||||||
onChange={this.handleUpdateContent.bind(this)}
|
linesHighlighted={note.linesHighlighted}
|
||||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
onChange={this.handleUpdateContent.bind(this)}
|
||||||
RTL={this.state.RTL}
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
/>
|
RTL={this.state.RTL}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { data, dispatch, location, config } = this.props
|
const { data, dispatch, location, config } = this.props
|
||||||
const { note, editorType } = this.state
|
const { note, editorType } = this.state
|
||||||
const storageKey = note.storage
|
const storageKey = note.storage
|
||||||
@@ -428,123 +467,141 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
const options = []
|
const options = []
|
||||||
data.storageMap.forEach((storage, index) => {
|
data.storageMap.forEach((storage, index) => {
|
||||||
storage.folders.forEach((folder) => {
|
storage.folders.forEach(folder => {
|
||||||
options.push({
|
options.push({
|
||||||
storage: storage,
|
storage: storage,
|
||||||
folder: folder
|
folder: folder
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
const currentOption = options.filter(
|
||||||
|
option =>
|
||||||
|
option.storage.key === storageKey && option.folder.key === folderKey
|
||||||
|
)[0]
|
||||||
|
|
||||||
const trashTopBar = <div styleName='info'>
|
const trashTopBar = (
|
||||||
<div styleName='info-left'>
|
<div styleName='info'>
|
||||||
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
|
<div styleName='info-left'>
|
||||||
</div>
|
<RestoreButton onClick={e => this.handleUndoButtonClick(e)} />
|
||||||
<div styleName='info-right'>
|
</div>
|
||||||
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
<div styleName='info-right'>
|
||||||
<InfoButton
|
<PermanentDeleteButton
|
||||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
onClick={e => this.handleTrashButtonClick(e)}
|
||||||
/>
|
/>
|
||||||
<InfoPanelTrashed
|
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
|
||||||
storageName={currentOption.storage.name}
|
<InfoPanelTrashed
|
||||||
folderName={currentOption.folder.name}
|
storageName={currentOption.storage.name}
|
||||||
updatedAt={formatDate(note.updatedAt)}
|
folderName={currentOption.folder.name}
|
||||||
createdAt={formatDate(note.createdAt)}
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
exportAsHtml={this.exportAsHtml}
|
createdAt={formatDate(note.createdAt)}
|
||||||
exportAsMd={this.exportAsMd}
|
exportAsHtml={this.exportAsHtml}
|
||||||
exportAsTxt={this.exportAsTxt}
|
exportAsMd={this.exportAsMd}
|
||||||
exportAsPdf={this.exportAsPdf}
|
exportAsTxt={this.exportAsTxt}
|
||||||
/>
|
exportAsPdf={this.exportAsPdf}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
const detailTopBar = <div styleName='info'>
|
|
||||||
<div styleName='info-left'>
|
|
||||||
<div>
|
|
||||||
<FolderSelect styleName='info-left-top-folderSelect'
|
|
||||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
|
||||||
ref='folder'
|
|
||||||
data={data}
|
|
||||||
onChange={(e) => this.handleFolderChange(e)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TagSelect
|
|
||||||
ref='tags'
|
|
||||||
value={this.state.note.tags}
|
|
||||||
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
|
||||||
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
|
||||||
data={data}
|
|
||||||
dispatch={dispatch}
|
|
||||||
onChange={this.handleUpdateTag.bind(this)}
|
|
||||||
coloredTags={config.coloredTags}
|
|
||||||
/>
|
|
||||||
<TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
|
||||||
</div>
|
</div>
|
||||||
<div styleName='info-right'>
|
)
|
||||||
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
|
||||||
<ToggleDirectionButton onClick={(e) => this.handleSwitchDirection(e)} isRTL={this.state.RTL} />
|
|
||||||
<StarButton
|
|
||||||
onClick={(e) => this.handleStarButtonClick(e)}
|
|
||||||
isActive={note.isStarred}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{(() => {
|
const detailTopBar = (
|
||||||
const imgSrc = `${this.getToggleLockButton()}`
|
<div styleName='info'>
|
||||||
const lockButtonComponent =
|
<div styleName='info-left'>
|
||||||
<button styleName='control-lockButton'
|
<div>
|
||||||
onFocus={(e) => this.handleFocus(e)}
|
<FolderSelect
|
||||||
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
|
styleName='info-left-top-folderSelect'
|
||||||
>
|
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||||
<img src={imgSrc} />
|
ref='folder'
|
||||||
{this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
|
data={data}
|
||||||
</button>
|
onChange={e => this.handleFolderChange(e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
return (
|
<TagSelect
|
||||||
this.state.isLockButtonShown ? lockButtonComponent : ''
|
ref='tags'
|
||||||
)
|
value={this.state.note.tags}
|
||||||
})()}
|
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
||||||
|
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||||
|
data={data}
|
||||||
|
dispatch={dispatch}
|
||||||
|
onChange={this.handleUpdateTag.bind(this)}
|
||||||
|
coloredTags={config.coloredTags}
|
||||||
|
/>
|
||||||
|
<TodoListPercentage
|
||||||
|
onClearCheckboxClick={e => this.handleClearTodo(e)}
|
||||||
|
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div styleName='info-right'>
|
||||||
|
<ToggleModeButton
|
||||||
|
onClick={e => this.handleSwitchMode(e)}
|
||||||
|
editorType={editorType}
|
||||||
|
/>
|
||||||
|
<ToggleDirectionButton
|
||||||
|
onClick={e => this.handleSwitchDirection(e)}
|
||||||
|
isRTL={this.state.RTL}
|
||||||
|
/>
|
||||||
|
<StarButton
|
||||||
|
onClick={e => this.handleStarButtonClick(e)}
|
||||||
|
isActive={note.isStarred}
|
||||||
|
/>
|
||||||
|
|
||||||
<FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
|
{(() => {
|
||||||
|
const imgSrc = `${this.getToggleLockButton()}`
|
||||||
|
const lockButtonComponent = (
|
||||||
|
<button
|
||||||
|
styleName='control-lockButton'
|
||||||
|
onFocus={e => this.handleFocus(e)}
|
||||||
|
onMouseDown={e => this.handleLockButtonMouseDown(e)}
|
||||||
|
>
|
||||||
|
<img src={imgSrc} />
|
||||||
|
{this.state.isLocked ? (
|
||||||
|
<span styleName='tooltip'>Unlock</span>
|
||||||
|
) : (
|
||||||
|
<span styleName='tooltip'>Lock</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
return this.state.isLockButtonShown ? lockButtonComponent : ''
|
||||||
|
})()}
|
||||||
|
|
||||||
<InfoButton
|
<FullscreenButton onClick={e => this.handleFullScreenButton(e)} />
|
||||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InfoPanel
|
<TrashButton onClick={e => this.handleTrashButtonClick(e)} />
|
||||||
storageName={currentOption.storage.name}
|
|
||||||
folderName={currentOption.folder.name}
|
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
|
||||||
noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`}
|
|
||||||
updatedAt={formatDate(note.updatedAt)}
|
<InfoPanel
|
||||||
createdAt={formatDate(note.createdAt)}
|
storageName={currentOption.storage.name}
|
||||||
exportAsMd={this.exportAsMd}
|
folderName={currentOption.folder.name}
|
||||||
exportAsTxt={this.exportAsTxt}
|
noteLink={`[${note.title}](:note:${
|
||||||
exportAsHtml={this.exportAsHtml}
|
queryString.parse(location.search).key
|
||||||
exportAsPdf={this.exportAsPdf}
|
})`}
|
||||||
wordCount={note.content.trim().split(/\s+/g).length}
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
createdAt={formatDate(note.createdAt)}
|
||||||
type={note.type}
|
exportAsMd={this.exportAsMd}
|
||||||
print={this.print}
|
exportAsTxt={this.exportAsTxt}
|
||||||
/>
|
exportAsHtml={this.exportAsHtml}
|
||||||
|
exportAsPdf={this.exportAsPdf}
|
||||||
|
wordCount={note.content.trim().split(/\s+/g).length}
|
||||||
|
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||||
|
type={note.type}
|
||||||
|
print={this.print}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
</div>
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='NoteDetail'
|
<div
|
||||||
|
className='NoteDetail'
|
||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
styleName='root'
|
styleName='root'
|
||||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
onKeyDown={e => this.handleKeyDown(e)}
|
||||||
>
|
>
|
||||||
|
|
||||||
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
||||||
|
|
||||||
<div styleName='body'>
|
<div styleName='body'>{this.renderEditor()}</div>
|
||||||
{this.renderEditor()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<StatusBar
|
<StatusBar
|
||||||
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
||||||
@@ -558,9 +615,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
MarkdownNoteDetail.propTypes = {
|
MarkdownNoteDetail.propTypes = {
|
||||||
dispatch: PropTypes.func,
|
dispatch: PropTypes.func,
|
||||||
repositories: PropTypes.array,
|
repositories: PropTypes.array,
|
||||||
note: PropTypes.shape({
|
note: PropTypes.shape({}),
|
||||||
|
|
||||||
}),
|
|
||||||
style: PropTypes.shape({
|
style: PropTypes.shape({
|
||||||
left: PropTypes.number
|
left: PropTypes.number
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './TrashButton.styl'
|
import styles from './TrashButton.styl'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const PermanentDeleteButton = ({
|
const PermanentDeleteButton = ({ onClick }) => (
|
||||||
onClick
|
<button styleName='control-trashButton--in-trash' onClick={e => onClick(e)}>
|
||||||
}) => (
|
|
||||||
<button styleName='control-trashButton--in-trash'
|
|
||||||
onClick={(e) => onClick(e)}
|
|
||||||
>
|
|
||||||
<img src='../resources/icon/icon-trash.svg' />
|
<img src='../resources/icon/icon-trash.svg' />
|
||||||
<span styleName='tooltip'>{i18n.__('Permanent Delete')}</span>
|
<span styleName='tooltip'>{i18n.__('Permanent Delete')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './RestoreButton.styl'
|
import styles from './RestoreButton.styl'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const RestoreButton = ({
|
const RestoreButton = ({ onClick }) => (
|
||||||
onClick
|
<button styleName='control-restoreButton' onClick={onClick}>
|
||||||
}) => (
|
|
||||||
<button styleName='control-restoreButton'
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
<i className='fa fa-undo fa-fw' styleName='iconRestore' />
|
<i className='fa fa-undo fa-fw' styleName='iconRestore' />
|
||||||
<span styleName='tooltip'>{i18n.__('Restore')}</span>
|
<span styleName='tooltip'>{i18n.__('Restore')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ import _ from 'lodash'
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
class StarButton extends React.Component {
|
class StarButton extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -14,47 +14,51 @@ class StarButton extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseDown (e) {
|
handleMouseDown(e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isActive: true
|
isActive: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp (e) {
|
handleMouseUp(e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isActive: false
|
isActive: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseLeave (e) {
|
handleMouseLeave(e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isActive: false
|
isActive: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { className } = this.props
|
const { className } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className={_.isString(className)
|
<button
|
||||||
? 'StarButton ' + className
|
className={
|
||||||
: 'StarButton'
|
_.isString(className) ? 'StarButton ' + className : 'StarButton'
|
||||||
}
|
}
|
||||||
styleName={this.state.isActive || this.props.isActive
|
styleName={
|
||||||
? 'root--active'
|
this.state.isActive || this.props.isActive ? 'root--active' : 'root'
|
||||||
: 'root'
|
|
||||||
}
|
}
|
||||||
onMouseDown={(e) => this.handleMouseDown(e)}
|
onMouseDown={e => this.handleMouseDown(e)}
|
||||||
onMouseUp={(e) => this.handleMouseUp(e)}
|
onMouseUp={e => this.handleMouseUp(e)}
|
||||||
onMouseLeave={(e) => this.handleMouseLeave(e)}
|
onMouseLeave={e => this.handleMouseLeave(e)}
|
||||||
onClick={this.props.onClick}>
|
onClick={this.props.onClick}
|
||||||
<img styleName='icon'
|
>
|
||||||
src={this.state.isActive || this.props.isActive
|
<img
|
||||||
? '../resources/icon/icon-starred.svg'
|
styleName='icon'
|
||||||
: '../resources/icon/icon-star.svg'
|
src={
|
||||||
|
this.state.isActive || this.props.isActive
|
||||||
|
? '../resources/icon/icon-starred.svg'
|
||||||
|
: '../resources/icon/icon-star.svg'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Star')}</span>
|
<span lang={i18n.locale} styleName='tooltip'>
|
||||||
|
{i18n.__('Star')}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import Autosuggest from 'react-autosuggest'
|
|||||||
import { push } from 'connected-react-router'
|
import { push } from 'connected-react-router'
|
||||||
|
|
||||||
class TagSelect extends React.Component {
|
class TagSelect extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -23,12 +23,16 @@ class TagSelect extends React.Component {
|
|||||||
this.onInputBlur = this.onInputBlur.bind(this)
|
this.onInputBlur = this.onInputBlur.bind(this)
|
||||||
this.onInputChange = this.onInputChange.bind(this)
|
this.onInputChange = this.onInputChange.bind(this)
|
||||||
this.onInputKeyDown = this.onInputKeyDown.bind(this)
|
this.onInputKeyDown = this.onInputKeyDown.bind(this)
|
||||||
this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this)
|
this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(
|
||||||
this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this)
|
this
|
||||||
|
)
|
||||||
|
this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(
|
||||||
|
this
|
||||||
|
)
|
||||||
this.onSuggestionSelected = this.onSuggestionSelected.bind(this)
|
this.onSuggestionSelected = this.onSuggestionSelected.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
addNewTag (newTag) {
|
addNewTag(newTag) {
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
|
||||||
|
|
||||||
newTag = newTag.trim().replace(/ +/g, '_')
|
newTag = newTag.trim().replace(/ +/g, '_')
|
||||||
@@ -44,9 +48,7 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let { value } = this.props
|
let { value } = this.props
|
||||||
value = _.isArray(value)
|
value = _.isArray(value) ? value.slice() : []
|
||||||
? value.slice()
|
|
||||||
: []
|
|
||||||
|
|
||||||
if (!_.includes(value, newTag)) {
|
if (!_.includes(value, newTag)) {
|
||||||
value.push(newTag)
|
value.push(newTag)
|
||||||
@@ -56,27 +58,31 @@ class TagSelect extends React.Component {
|
|||||||
value = _.sortBy(value)
|
value = _.sortBy(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
newTag: ''
|
{
|
||||||
}, () => {
|
newTag: ''
|
||||||
this.value = value
|
},
|
||||||
this.props.onChange()
|
() => {
|
||||||
})
|
this.value = value
|
||||||
|
this.props.onChange()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildSuggestions () {
|
buildSuggestions() {
|
||||||
this.suggestions = _.sortBy(this.props.data.tagNoteMap.map(
|
this.suggestions = _.sortBy(
|
||||||
(tag, name) => ({
|
this.props.data.tagNoteMap
|
||||||
name,
|
.map((tag, name) => ({
|
||||||
nameLC: name.toLowerCase(),
|
name,
|
||||||
size: tag.size
|
nameLC: name.toLowerCase(),
|
||||||
})
|
size: tag.size
|
||||||
).filter(
|
}))
|
||||||
tag => tag.size > 0
|
.filter(tag => tag.size > 0),
|
||||||
), ['name'])
|
['name']
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
this.value = this.props.value
|
this.value = this.props.value
|
||||||
|
|
||||||
this.buildSuggestions()
|
this.buildSuggestions()
|
||||||
@@ -84,19 +90,19 @@ class TagSelect extends React.Component {
|
|||||||
ee.on('editor:add-tag', this.handleAddTag)
|
ee.on('editor:add-tag', this.handleAddTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
componentDidUpdate() {
|
||||||
this.value = this.props.value
|
this.value = this.props.value
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount() {
|
||||||
ee.off('editor:add-tag', this.handleAddTag)
|
ee.off('editor:add-tag', this.handleAddTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddTag () {
|
handleAddTag() {
|
||||||
this.refs.newTag.input.focus()
|
this.refs.newTag.input.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTagLabelClick (tag) {
|
handleTagLabelClick(tag) {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
|
|
||||||
// Note: `tag` requires encoding later.
|
// Note: `tag` requires encoding later.
|
||||||
@@ -104,23 +110,23 @@ class TagSelect extends React.Component {
|
|||||||
dispatch(push(`/tags/${tag}`))
|
dispatch(push(`/tags/${tag}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTagRemoveButtonClick (tag) {
|
handleTagRemoveButtonClick(tag) {
|
||||||
this.removeTagByCallback((value, tag) => {
|
this.removeTagByCallback((value, tag) => {
|
||||||
value.splice(value.indexOf(tag), 1)
|
value.splice(value.indexOf(tag), 1)
|
||||||
}, tag)
|
}, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
onInputBlur (e) {
|
onInputBlur(e) {
|
||||||
this.submitNewTag()
|
this.submitNewTag()
|
||||||
}
|
}
|
||||||
|
|
||||||
onInputChange (e, { newValue, method }) {
|
onInputChange(e, { newValue, method }) {
|
||||||
this.setState({
|
this.setState({
|
||||||
newTag: newValue
|
newTag: newValue
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onInputKeyDown (e) {
|
onInputKeyDown(e) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case 9:
|
case 9:
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -136,17 +142,18 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuggestionsClearRequested () {
|
onSuggestionsClearRequested() {
|
||||||
this.setState({
|
this.setState({
|
||||||
suggestions: []
|
suggestions: []
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuggestionsFetchRequested ({ value }) {
|
onSuggestionsFetchRequested({ value }) {
|
||||||
const valueLC = value.toLowerCase()
|
const valueLC = value.toLowerCase()
|
||||||
const suggestions = _.filter(
|
const suggestions = _.filter(
|
||||||
this.suggestions,
|
this.suggestions,
|
||||||
tag => !_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1
|
tag =>
|
||||||
|
!_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1
|
||||||
)
|
)
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -154,22 +161,20 @@ class TagSelect extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuggestionSelected (event, { suggestion, suggestionValue }) {
|
onSuggestionSelected(event, { suggestion, suggestionValue }) {
|
||||||
this.addNewTag(suggestionValue)
|
this.addNewTag(suggestionValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
removeLastTag () {
|
removeLastTag() {
|
||||||
this.removeTagByCallback((value) => {
|
this.removeTagByCallback(value => {
|
||||||
value.pop()
|
value.pop()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
removeTagByCallback (callback, tag = null) {
|
removeTagByCallback(callback, tag = null) {
|
||||||
let { value } = this.props
|
let { value } = this.props
|
||||||
|
|
||||||
value = _.isArray(value)
|
value = _.isArray(value) ? value.slice() : []
|
||||||
? value.slice()
|
|
||||||
: []
|
|
||||||
callback(value, tag)
|
callback(value, tag)
|
||||||
value = _.uniq(value)
|
value = _.uniq(value)
|
||||||
|
|
||||||
@@ -177,7 +182,7 @@ class TagSelect extends React.Component {
|
|||||||
this.props.onChange()
|
this.props.onChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
reset () {
|
reset() {
|
||||||
this.buildSuggestions()
|
this.buildSuggestions()
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -185,51 +190,60 @@ class TagSelect extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
submitNewTag () {
|
submitNewTag() {
|
||||||
this.addNewTag(this.refs.newTag.input.value)
|
this.addNewTag(this.refs.newTag.input.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { value, className, showTagsAlphabetically, coloredTags } = this.props
|
const { value, className, showTagsAlphabetically, coloredTags } = this.props
|
||||||
|
|
||||||
const tagList = _.isArray(value)
|
const tagList = _.isArray(value)
|
||||||
? (showTagsAlphabetically ? _.sortBy(value) : value).map((tag) => {
|
? (showTagsAlphabetically ? _.sortBy(value) : value).map(tag => {
|
||||||
const wrapperStyle = {}
|
const wrapperStyle = {}
|
||||||
const textStyle = {}
|
const textStyle = {}
|
||||||
const BLACK = '#333333'
|
const BLACK = '#333333'
|
||||||
const WHITE = '#f1f1f1'
|
const WHITE = '#f1f1f1'
|
||||||
const color = coloredTags[tag]
|
const color = coloredTags[tag]
|
||||||
const invertedColor = color && invertColor(color, { black: BLACK, white: WHITE })
|
const invertedColor =
|
||||||
let iconRemove = '../resources/icon/icon-x.svg'
|
color && invertColor(color, { black: BLACK, white: WHITE })
|
||||||
if (color) {
|
let iconRemove = '../resources/icon/icon-x.svg'
|
||||||
wrapperStyle.backgroundColor = color
|
if (color) {
|
||||||
textStyle.color = invertedColor
|
wrapperStyle.backgroundColor = color
|
||||||
}
|
textStyle.color = invertedColor
|
||||||
if (invertedColor === WHITE) {
|
}
|
||||||
iconRemove = '../resources/icon/icon-x-light.svg'
|
if (invertedColor === WHITE) {
|
||||||
}
|
iconRemove = '../resources/icon/icon-x-light.svg'
|
||||||
return (
|
}
|
||||||
<span styleName='tag'
|
return (
|
||||||
key={tag}
|
<span styleName='tag' key={tag} style={wrapperStyle}>
|
||||||
style={wrapperStyle}
|
<span
|
||||||
>
|
styleName='tag-label'
|
||||||
<span styleName='tag-label' style={textStyle} onClick={(e) => this.handleTagLabelClick(tag)}>#{tag}</span>
|
style={textStyle}
|
||||||
<button styleName='tag-removeButton'
|
onClick={e => this.handleTagLabelClick(tag)}
|
||||||
onClick={(e) => this.handleTagRemoveButtonClick(tag)}
|
>
|
||||||
>
|
#{tag}
|
||||||
<img className='tag-removeButton-icon' src={iconRemove} width='8px' />
|
</span>
|
||||||
</button>
|
<button
|
||||||
</span>
|
styleName='tag-removeButton'
|
||||||
)
|
onClick={e => this.handleTagRemoveButtonClick(tag)}
|
||||||
})
|
>
|
||||||
|
<img
|
||||||
|
className='tag-removeButton-icon'
|
||||||
|
src={iconRemove}
|
||||||
|
width='8px'
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})
|
||||||
: []
|
: []
|
||||||
|
|
||||||
const { newTag, suggestions } = this.state
|
const { newTag, suggestions } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={_.isString(className)
|
<div
|
||||||
? 'TagSelect ' + className
|
className={
|
||||||
: 'TagSelect'
|
_.isString(className) ? 'TagSelect ' + className : 'TagSelect'
|
||||||
}
|
}
|
||||||
styleName='root'
|
styleName='root'
|
||||||
>
|
>
|
||||||
@@ -241,11 +255,7 @@ class TagSelect extends React.Component {
|
|||||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||||
onSuggestionSelected={this.onSuggestionSelected}
|
onSuggestionSelected={this.onSuggestionSelected}
|
||||||
getSuggestionValue={suggestion => suggestion.name}
|
getSuggestionValue={suggestion => suggestion.name}
|
||||||
renderSuggestion={suggestion => (
|
renderSuggestion={suggestion => <div>{suggestion.name}</div>}
|
||||||
<div>
|
|
||||||
{suggestion.name}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
inputProps={{
|
inputProps={{
|
||||||
placeholder: i18n.__('Add tag...'),
|
placeholder: i18n.__('Add tag...'),
|
||||||
value: newTag,
|
value: newTag,
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './ToggleDirectionButton.styl'
|
import styles from './ToggleDirectionButton.styl'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const ToggleDirectionButton = ({
|
const ToggleDirectionButton = ({ onClick, isRTL }) => (
|
||||||
onClick, isRTL
|
|
||||||
}) => (
|
|
||||||
<div styleName='control-toggleModeButton'>
|
<div styleName='control-toggleModeButton'>
|
||||||
<div styleName={isRTL ? 'active' : undefined} onClick={() => onClick()}>
|
<div styleName={isRTL ? 'active' : undefined} onClick={() => onClick()}>
|
||||||
<img src={!isRTL ? '../resources/icon/icon-left-to-right.svg' : ''} />
|
<img src={!isRTL ? '../resources/icon/icon-left-to-right.svg' : ''} />
|
||||||
@@ -14,7 +12,9 @@ const ToggleDirectionButton = ({
|
|||||||
<div styleName={!isRTL ? 'active' : undefined} onClick={() => onClick()}>
|
<div styleName={!isRTL ? 'active' : undefined} onClick={() => onClick()}>
|
||||||
<img src={!isRTL ? '' : '../resources/icon/icon-right-to-left.svg'} />
|
<img src={!isRTL ? '' : '../resources/icon/icon-right-to-left.svg'} />
|
||||||
</div>
|
</div>
|
||||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Toggle Direction')}</span>
|
<span lang={i18n.locale} styleName='tooltip'>
|
||||||
|
{i18n.__('Toggle Direction')}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,35 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './ToggleModeButton.styl'
|
import styles from './ToggleModeButton.styl'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const ToggleModeButton = ({
|
const ToggleModeButton = ({ onClick, editorType }) => (
|
||||||
onClick, editorType
|
|
||||||
}) => (
|
|
||||||
<div styleName='control-toggleModeButton'>
|
<div styleName='control-toggleModeButton'>
|
||||||
<div styleName={editorType === 'SPLIT' ? 'active' : undefined} onClick={() => onClick('SPLIT')}>
|
<div
|
||||||
<img src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} />
|
styleName={editorType === 'SPLIT' ? 'active' : undefined}
|
||||||
|
onClick={() => onClick('SPLIT')}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
editorType === 'EDITOR_PREVIEW'
|
||||||
|
? '../resources/icon/icon-mode-markdown-off-active.svg'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : undefined} onClick={() => onClick('EDITOR_PREVIEW')}>
|
<div
|
||||||
<img src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
|
styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : undefined}
|
||||||
|
onClick={() => onClick('EDITOR_PREVIEW')}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
editorType === 'EDITOR_PREVIEW'
|
||||||
|
? ''
|
||||||
|
: '../resources/icon/icon-mode-split-on-active.svg'
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
|
<span lang={i18n.locale} styleName='tooltip'>
|
||||||
|
{i18n.__('Toggle Mode')}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,12 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './TrashButton.styl'
|
import styles from './TrashButton.styl'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const TrashButton = ({
|
const TrashButton = ({ onClick }) => (
|
||||||
onClick
|
<button styleName='control-trashButton' onClick={e => onClick(e)}>
|
||||||
}) => (
|
|
||||||
<button styleName='control-trashButton'
|
|
||||||
onClick={(e) => onClick(e)}
|
|
||||||
>
|
|
||||||
<img src='../resources/icon/icon-trash.svg' />
|
<img src='../resources/icon/icon-trash.svg' />
|
||||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Trash')}</span>
|
<span lang={i18n.locale} styleName='tooltip'>
|
||||||
|
{i18n.__('Trash')}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import queryString from 'query-string'
|
|||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
|
||||||
class Detail extends React.Component {
|
class Detail extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.focusHandler = () => {
|
this.focusHandler = () => {
|
||||||
@@ -26,41 +26,55 @@ class Detail extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
ee.on('detail:focus', this.focusHandler)
|
ee.on('detail:focus', this.focusHandler)
|
||||||
ee.on('detail:delete', this.deleteHandler)
|
ee.on('detail:delete', this.deleteHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount() {
|
||||||
ee.off('detail:focus', this.focusHandler)
|
ee.off('detail:focus', this.focusHandler)
|
||||||
ee.off('detail:delete', this.deleteHandler)
|
ee.off('detail:delete', this.deleteHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { location, data, match: { params }, config } = this.props
|
const {
|
||||||
const noteKey = location.search !== '' && queryString.parse(location.search).key
|
location,
|
||||||
|
data,
|
||||||
|
match: { params },
|
||||||
|
config
|
||||||
|
} = this.props
|
||||||
|
const noteKey =
|
||||||
|
location.search !== '' && queryString.parse(location.search).key
|
||||||
let note = null
|
let note = null
|
||||||
|
|
||||||
if (location.search !== '') {
|
if (location.search !== '') {
|
||||||
const allNotes = data.noteMap.map(note => note)
|
const allNotes = data.noteMap.map(note => note)
|
||||||
const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
|
const trashedNotes = data.trashedSet
|
||||||
|
.toJS()
|
||||||
|
.map(uniqueKey => data.noteMap.get(uniqueKey))
|
||||||
let displayedNotes = allNotes
|
let displayedNotes = allNotes
|
||||||
|
|
||||||
if (location.pathname.match(/\/searched/)) {
|
if (location.pathname.match(/\/searched/)) {
|
||||||
const searchStr = params.searchword
|
const searchStr = params.searchword
|
||||||
displayedNotes = searchStr === undefined || searchStr === '' ? allNotes
|
displayedNotes =
|
||||||
: searchFromNotes(allNotes, searchStr)
|
searchStr === undefined || searchStr === ''
|
||||||
|
? allNotes
|
||||||
|
: searchFromNotes(allNotes, searchStr)
|
||||||
} else if (location.pathname.match(/^\/tags/)) {
|
} else if (location.pathname.match(/^\/tags/)) {
|
||||||
const listOfTags = params.tagname.split(' ')
|
const listOfTags = params.tagname.split(' ')
|
||||||
displayedNotes = data.noteMap.map(note => note).filter(note =>
|
displayedNotes = data.noteMap
|
||||||
listOfTags.every(tag => note.tags.includes(tag))
|
.map(note => note)
|
||||||
)
|
.filter(note => listOfTags.every(tag => note.tags.includes(tag)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location.pathname.match(/^\/trashed/)) {
|
if (location.pathname.match(/^\/trashed/)) {
|
||||||
displayedNotes = trashedNotes
|
displayedNotes = trashedNotes
|
||||||
} else {
|
} else {
|
||||||
displayedNotes = _.differenceWith(displayedNotes, trashedNotes, (note, trashed) => note.key === trashed.key)
|
displayedNotes = _.differenceWith(
|
||||||
|
displayedNotes,
|
||||||
|
trashedNotes,
|
||||||
|
(note, trashed) => note.key === trashed.key
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const noteKeys = displayedNotes.map(note => note.key)
|
const noteKeys = displayedNotes.map(note => note.key)
|
||||||
@@ -71,12 +85,12 @@ class Detail extends React.Component {
|
|||||||
|
|
||||||
if (note == null) {
|
if (note == null) {
|
||||||
return (
|
return (
|
||||||
<div styleName='root'
|
<div styleName='root' style={this.props.style} tabIndex='0'>
|
||||||
style={this.props.style}
|
|
||||||
tabIndex='0'
|
|
||||||
>
|
|
||||||
<div styleName='empty'>
|
<div styleName='empty'>
|
||||||
<div styleName='empty-message'>{OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')} + N<br />{i18n.__('to create a new note')}</div>
|
<div styleName='empty-message'>
|
||||||
|
{OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')} + N<br />
|
||||||
|
{i18n.__('to create a new note')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const electron = require('electron')
|
|||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
|
|
||||||
class Main extends React.Component {
|
class Main extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
@@ -46,7 +46,7 @@ class Main extends React.Component {
|
|||||||
this.toggleFullScreen = () => this.handleFullScreenButton()
|
this.toggleFullScreen = () => this.handleFullScreenButton()
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildContext () {
|
getChildContext() {
|
||||||
const { status, config } = this.props
|
const { status, config } = this.props
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -55,7 +55,7 @@ class Main extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init () {
|
init() {
|
||||||
dataApi
|
dataApi
|
||||||
.addStorage({
|
.addStorage({
|
||||||
name: 'My Storage Location',
|
name: 'My Storage Location',
|
||||||
@@ -93,18 +93,21 @@ class Main extends React.Component {
|
|||||||
type: 'SNIPPET_NOTE',
|
type: 'SNIPPET_NOTE',
|
||||||
folder: data.storage.folders[0].key,
|
folder: data.storage.folders[0].key,
|
||||||
title: 'Snippet note example',
|
title: 'Snippet note example',
|
||||||
description: 'Snippet note example\nYou can store a series of snippets as a single note, like Gist.',
|
description:
|
||||||
|
'Snippet note example\nYou can store a series of snippets as a single note, like Gist.',
|
||||||
snippets: [
|
snippets: [
|
||||||
{
|
{
|
||||||
name: 'example.html',
|
name: 'example.html',
|
||||||
mode: 'html',
|
mode: 'html',
|
||||||
content: "<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>",
|
content:
|
||||||
|
"<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>",
|
||||||
linesHighlighted: []
|
linesHighlighted: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'example.js',
|
name: 'example.js',
|
||||||
mode: 'javascript',
|
mode: 'javascript',
|
||||||
content: "var boostnote = document.getElementById('hello').innerHTML\n\nconsole.log(boostnote)",
|
content:
|
||||||
|
"var boostnote = document.getElementById('hello').innerHTML\n\nconsole.log(boostnote)",
|
||||||
linesHighlighted: []
|
linesHighlighted: []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -120,7 +123,8 @@ class Main extends React.Component {
|
|||||||
type: 'MARKDOWN_NOTE',
|
type: 'MARKDOWN_NOTE',
|
||||||
folder: data.storage.folders[0].key,
|
folder: data.storage.folders[0].key,
|
||||||
title: 'Welcome to Boostnote!',
|
title: 'Welcome to Boostnote!',
|
||||||
content: '# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
|
content:
|
||||||
|
'# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
|
||||||
})
|
})
|
||||||
.then(note => {
|
.then(note => {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
@@ -141,7 +145,7 @@ class Main extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
const { dispatch, config } = this.props
|
const { dispatch, config } = this.props
|
||||||
|
|
||||||
if (uiThemes.some(theme => theme.name === config.ui.theme)) {
|
if (uiThemes.some(theme => theme.name === config.ui.theme)) {
|
||||||
@@ -173,38 +177,44 @@ class Main extends React.Component {
|
|||||||
delete CodeMirror.keyMap.emacs['Ctrl-V']
|
delete CodeMirror.keyMap.emacs['Ctrl-V']
|
||||||
|
|
||||||
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
|
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
|
||||||
eventEmitter.on('menubar:togglemenubar', this.toggleMenuBarVisible.bind(this))
|
eventEmitter.on(
|
||||||
|
'menubar:togglemenubar',
|
||||||
|
this.toggleMenuBarVisible.bind(this)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount() {
|
||||||
eventEmitter.off('editor:fullscreen', this.toggleFullScreen)
|
eventEmitter.off('editor:fullscreen', this.toggleFullScreen)
|
||||||
eventEmitter.off('menubar:togglemenubar', this.toggleMenuBarVisible.bind(this))
|
eventEmitter.off(
|
||||||
|
'menubar:togglemenubar',
|
||||||
|
this.toggleMenuBarVisible.bind(this)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleMenuBarVisible () {
|
toggleMenuBarVisible() {
|
||||||
const { config } = this.props
|
const { config } = this.props
|
||||||
const { ui } = config
|
const { ui } = config
|
||||||
|
|
||||||
const newUI = Object.assign(ui, {showMenuBar: !ui.showMenuBar})
|
const newUI = Object.assign(ui, { showMenuBar: !ui.showMenuBar })
|
||||||
const newConfig = Object.assign(config, newUI)
|
const newConfig = Object.assign(config, newUI)
|
||||||
ConfigManager.set(newConfig)
|
ConfigManager.set(newConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLeftSlideMouseDown (e) {
|
handleLeftSlideMouseDown(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.setState({
|
this.setState({
|
||||||
isLeftSliderFocused: true
|
isLeftSliderFocused: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRightSlideMouseDown (e) {
|
handleRightSlideMouseDown(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.setState({
|
this.setState({
|
||||||
isRightSliderFocused: true
|
isRightSliderFocused: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp (e) {
|
handleMouseUp(e) {
|
||||||
// Change width of NoteList component.
|
// Change width of NoteList component.
|
||||||
if (this.state.isRightSliderFocused) {
|
if (this.state.isRightSliderFocused) {
|
||||||
this.setState(
|
this.setState(
|
||||||
@@ -244,7 +254,7 @@ class Main extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseMove (e) {
|
handleMouseMove(e) {
|
||||||
if (this.state.isRightSliderFocused) {
|
if (this.state.isRightSliderFocused) {
|
||||||
const offset = this.refs.body.getBoundingClientRect().left
|
const offset = this.refs.body.getBoundingClientRect().left
|
||||||
let newListWidth = e.pageX - offset
|
let newListWidth = e.pageX - offset
|
||||||
@@ -270,7 +280,7 @@ class Main extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFullScreenButton (e) {
|
handleFullScreenButton(e) {
|
||||||
this.setState({ fullScreen: !this.state.fullScreen }, () => {
|
this.setState({ fullScreen: !this.state.fullScreen }, () => {
|
||||||
const noteDetail = document.querySelector('.NoteDetail')
|
const noteDetail = document.querySelector('.NoteDetail')
|
||||||
const noteList = document.querySelector('.NoteList')
|
const noteList = document.querySelector('.NoteList')
|
||||||
@@ -284,7 +294,7 @@ class Main extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
hideLeftLists (noteDetail, noteList, mainBody) {
|
hideLeftLists(noteDetail, noteList, mainBody) {
|
||||||
this.setState({ noteDetailWidth: noteDetail.style.left })
|
this.setState({ noteDetailWidth: noteDetail.style.left })
|
||||||
this.setState({ mainBodyWidth: mainBody.style.left })
|
this.setState({ mainBodyWidth: mainBody.style.left })
|
||||||
noteDetail.style.left = '0px'
|
noteDetail.style.left = '0px'
|
||||||
@@ -292,13 +302,13 @@ class Main extends React.Component {
|
|||||||
noteList.style.display = 'none'
|
noteList.style.display = 'none'
|
||||||
}
|
}
|
||||||
|
|
||||||
showLeftLists (noteDetail, noteList, mainBody) {
|
showLeftLists(noteDetail, noteList, mainBody) {
|
||||||
noteDetail.style.left = this.state.noteDetailWidth
|
noteDetail.style.left = this.state.noteDetailWidth
|
||||||
mainBody.style.left = this.state.mainBodyWidth
|
mainBody.style.left = this.state.mainBodyWidth
|
||||||
noteList.style.display = 'inline'
|
noteList.style.display = 'inline'
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { config } = this.props
|
const { config } = this.props
|
||||||
|
|
||||||
// the width of the navigation bar when it is folded/collapsed
|
// the width of the navigation bar when it is folded/collapsed
|
||||||
@@ -312,10 +322,16 @@ class Main extends React.Component {
|
|||||||
onMouseUp={e => this.handleMouseUp(e)}
|
onMouseUp={e => this.handleMouseUp(e)}
|
||||||
>
|
>
|
||||||
<SideNav
|
<SideNav
|
||||||
{..._.pick(this.props, ['dispatch', 'data', 'config', 'match', 'location'])}
|
{..._.pick(this.props, [
|
||||||
|
'dispatch',
|
||||||
|
'data',
|
||||||
|
'config',
|
||||||
|
'match',
|
||||||
|
'location'
|
||||||
|
])}
|
||||||
width={this.state.navWidth}
|
width={this.state.navWidth}
|
||||||
/>
|
/>
|
||||||
{!config.isSideNavFolded &&
|
{!config.isSideNavFolded && (
|
||||||
<div
|
<div
|
||||||
styleName={
|
styleName={
|
||||||
this.state.isLeftSliderFocused ? 'slider--active' : 'slider'
|
this.state.isLeftSliderFocused ? 'slider--active' : 'slider'
|
||||||
@@ -325,7 +341,8 @@ class Main extends React.Component {
|
|||||||
draggable='false'
|
draggable='false'
|
||||||
>
|
>
|
||||||
<div styleName='slider-hitbox' />
|
<div styleName='slider-hitbox' />
|
||||||
</div>}
|
</div>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
||||||
id='main-body'
|
id='main-body'
|
||||||
|
|||||||
@@ -15,30 +15,48 @@ const { dialog } = remote
|
|||||||
const OSX = window.process.platform === 'darwin'
|
const OSX = window.process.platform === 'darwin'
|
||||||
|
|
||||||
class NewNoteButton extends React.Component {
|
class NewNoteButton extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {}
|
||||||
}
|
|
||||||
|
|
||||||
this.handleNewNoteButtonClick = this.handleNewNoteButtonClick.bind(this)
|
this.handleNewNoteButtonClick = this.handleNewNoteButtonClick.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
eventEmitter.on('top:new-note', this.handleNewNoteButtonClick)
|
eventEmitter.on('top:new-note', this.handleNewNoteButtonClick)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount() {
|
||||||
eventEmitter.off('top:new-note', this.handleNewNoteButtonClick)
|
eventEmitter.off('top:new-note', this.handleNewNoteButtonClick)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNewNoteButtonClick (e) {
|
handleNewNoteButtonClick(e) {
|
||||||
const { location, dispatch, match: { params }, config } = this.props
|
const {
|
||||||
|
location,
|
||||||
|
dispatch,
|
||||||
|
match: { params },
|
||||||
|
config
|
||||||
|
} = this.props
|
||||||
const { storage, folder } = this.resolveTargetFolder()
|
const { storage, folder } = this.resolveTargetFolder()
|
||||||
if (config.ui.defaultNote === 'MARKDOWN_NOTE') {
|
if (config.ui.defaultNote === 'MARKDOWN_NOTE') {
|
||||||
createMarkdownNote(storage.key, folder.key, dispatch, location, params, config)
|
createMarkdownNote(
|
||||||
|
storage.key,
|
||||||
|
folder.key,
|
||||||
|
dispatch,
|
||||||
|
location,
|
||||||
|
params,
|
||||||
|
config
|
||||||
|
)
|
||||||
} else if (config.ui.defaultNote === 'SNIPPET_NOTE') {
|
} else if (config.ui.defaultNote === 'SNIPPET_NOTE') {
|
||||||
createSnippetNote(storage.key, folder.key, dispatch, location, params, config)
|
createSnippetNote(
|
||||||
|
storage.key,
|
||||||
|
folder.key,
|
||||||
|
dispatch,
|
||||||
|
location,
|
||||||
|
params,
|
||||||
|
config
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
modal.open(NewNoteModal, {
|
modal.open(NewNoteModal, {
|
||||||
storage: storage.key,
|
storage: storage.key,
|
||||||
@@ -51,8 +69,11 @@ class NewNoteButton extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveTargetFolder () {
|
resolveTargetFolder() {
|
||||||
const { data, match: { params } } = this.props
|
const {
|
||||||
|
data,
|
||||||
|
match: { params }
|
||||||
|
} = this.props
|
||||||
let storage = data.storageMap.get(params.storageKey)
|
let storage = data.storageMap.get(params.storageKey)
|
||||||
// Find first storage
|
// Find first storage
|
||||||
if (storage == null) {
|
if (storage == null) {
|
||||||
@@ -62,9 +83,12 @@ class NewNoteButton extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storage == null) this.showMessageBox(i18n.__('No storage to create a note'))
|
if (storage == null)
|
||||||
const folder = _.find(storage.folders, {key: params.folderKey}) || storage.folders[0]
|
this.showMessageBox(i18n.__('No storage to create a note'))
|
||||||
if (folder == null) this.showMessageBox(i18n.__('No folder to create a note'))
|
const folder =
|
||||||
|
_.find(storage.folders, { key: params.folderKey }) || storage.folders[0]
|
||||||
|
if (folder == null)
|
||||||
|
this.showMessageBox(i18n.__('No folder to create a note'))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
storage,
|
storage,
|
||||||
@@ -72,7 +96,7 @@ class NewNoteButton extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showMessageBox (message) {
|
showMessageBox(message) {
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: message,
|
message: message,
|
||||||
@@ -80,16 +104,19 @@ class NewNoteButton extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { config, style } = this.props
|
const { config, style } = this.props
|
||||||
return (
|
return (
|
||||||
<div className='NewNoteButton'
|
<div
|
||||||
|
className='NewNoteButton'
|
||||||
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
|
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
<div styleName='control'>
|
<div styleName='control'>
|
||||||
<button styleName='control-newNoteButton'
|
<button
|
||||||
onClick={this.handleNewNoteButtonClick}>
|
styleName='control-newNoteButton'
|
||||||
|
onClick={this.handleNewNoteButtonClick}
|
||||||
|
>
|
||||||
<img src='../resources/icon/icon-newnote.svg' />
|
<img src='../resources/icon/icon-newnote.svg' />
|
||||||
<span styleName='control-newNoteButton-tooltip'>
|
<span styleName='control-newNoteButton-tooltip'>
|
||||||
{i18n.__('Make a note')} {OSX ? '⌘' : i18n.__('Ctrl')} + N
|
{i18n.__('Make a note')} {OSX ? '⌘' : i18n.__('Ctrl')} + N
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,14 +4,17 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './SwitchButton.styl'
|
import styles from './SwitchButton.styl'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const ListButton = ({
|
const ListButton = ({ onClick, isTagActive }) => (
|
||||||
onClick, isTagActive
|
<button
|
||||||
}) => (
|
styleName={isTagActive ? 'non-active-button' : 'active-button'}
|
||||||
<button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={onClick}>
|
onClick={onClick}
|
||||||
<img src={isTagActive
|
>
|
||||||
? '../resources/icon/icon-list.svg'
|
<img
|
||||||
: '../resources/icon/icon-list-active.svg'
|
src={
|
||||||
}
|
isTagActive
|
||||||
|
? '../resources/icon/icon-list.svg'
|
||||||
|
: '../resources/icon/icon-list-active.svg'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<span styleName='tooltip'>{i18n.__('Notes')}</span>
|
<span styleName='tooltip'>{i18n.__('Notes')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './PreferenceButton.styl'
|
import styles from './PreferenceButton.styl'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const PreferenceButton = ({
|
const PreferenceButton = ({ onClick }) => (
|
||||||
onClick
|
<button styleName='top-menu-preference' onClick={e => onClick(e)}>
|
||||||
}) => (
|
|
||||||
<button styleName='top-menu-preference' onClick={(e) => onClick(e)}>
|
|
||||||
<img src='../resources/icon/icon-setting.svg' />
|
<img src='../resources/icon/icon-setting.svg' />
|
||||||
<span styleName='tooltip'>{i18n.__('Preferences')}</span>
|
<span styleName='tooltip'>{i18n.__('Preferences')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const escapeStringRegexp = require('escape-string-regexp')
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
class StorageItem extends React.Component {
|
class StorageItem extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
const { storage } = this.props
|
const { storage } = this.props
|
||||||
@@ -30,11 +30,11 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHeaderContextMenu (e) {
|
handleHeaderContextMenu(e) {
|
||||||
context.popup([
|
context.popup([
|
||||||
{
|
{
|
||||||
label: i18n.__('Add Folder'),
|
label: i18n.__('Add Folder'),
|
||||||
click: (e) => this.handleAddFolderButtonClick(e)
|
click: e => this.handleAddFolderButtonClick(e)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
@@ -44,11 +44,11 @@ class StorageItem extends React.Component {
|
|||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: i18n.__('Export as txt'),
|
label: i18n.__('Export as txt'),
|
||||||
click: (e) => this.handleExportStorageClick(e, 'txt')
|
click: e => this.handleExportStorageClick(e, 'txt')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18n.__('Export as md'),
|
label: i18n.__('Export as md'),
|
||||||
click: (e) => this.handleExportStorageClick(e, 'md')
|
click: e => this.handleExportStorageClick(e, 'md')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -57,75 +57,74 @@ class StorageItem extends React.Component {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18n.__('Unlink Storage'),
|
label: i18n.__('Unlink Storage'),
|
||||||
click: (e) => this.handleUnlinkStorageClick(e)
|
click: e => this.handleUnlinkStorageClick(e)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUnlinkStorageClick (e) {
|
handleUnlinkStorageClick(e) {
|
||||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: i18n.__('Unlink Storage'),
|
message: i18n.__('Unlink Storage'),
|
||||||
detail: i18n.__('This work will just detatches a storage from Boostnote. (Any data won\'t be deleted.)'),
|
detail: i18n.__(
|
||||||
|
"This work will just detatches a storage from Boostnote. (Any data won't be deleted.)"
|
||||||
|
),
|
||||||
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||||
})
|
})
|
||||||
|
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
const { storage, dispatch } = this.props
|
const { storage, dispatch } = this.props
|
||||||
dataApi.removeStorage(storage.key)
|
dataApi
|
||||||
|
.removeStorage(storage.key)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'REMOVE_STORAGE',
|
type: 'REMOVE_STORAGE',
|
||||||
storageKey: storage.key
|
storageKey: storage.key
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleExportStorageClick (e, fileType) {
|
handleExportStorageClick(e, fileType) {
|
||||||
const options = {
|
const options = {
|
||||||
properties: ['openDirectory', 'createDirectory'],
|
properties: ['openDirectory', 'createDirectory'],
|
||||||
buttonLabel: i18n.__('Select directory'),
|
buttonLabel: i18n.__('Select directory'),
|
||||||
title: i18n.__('Select a folder to export the files to'),
|
title: i18n.__('Select a folder to export the files to'),
|
||||||
multiSelections: false
|
multiSelections: false
|
||||||
}
|
}
|
||||||
dialog.showOpenDialog(remote.getCurrentWindow(), options,
|
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
|
||||||
(paths) => {
|
if (paths && paths.length === 1) {
|
||||||
if (paths && paths.length === 1) {
|
const { storage, dispatch } = this.props
|
||||||
const { storage, dispatch } = this.props
|
dataApi.exportStorage(storage.key, fileType, paths[0]).then(data => {
|
||||||
dataApi
|
dispatch({
|
||||||
.exportStorage(storage.key, fileType, paths[0])
|
type: 'EXPORT_STORAGE',
|
||||||
.then(data => {
|
storage: data.storage,
|
||||||
dispatch({
|
fileType: data.fileType
|
||||||
type: 'EXPORT_STORAGE',
|
})
|
||||||
storage: data.storage,
|
})
|
||||||
fileType: data.fileType
|
}
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleToggleButtonClick (e) {
|
handleToggleButtonClick(e) {
|
||||||
const { storage, dispatch } = this.props
|
const { storage, dispatch } = this.props
|
||||||
const isOpen = !this.state.isOpen
|
const isOpen = !this.state.isOpen
|
||||||
dataApi.toggleStorage(storage.key, isOpen)
|
dataApi.toggleStorage(storage.key, isOpen).then(storage => {
|
||||||
.then((storage) => {
|
dispatch({
|
||||||
dispatch({
|
type: 'EXPAND_STORAGE',
|
||||||
type: 'EXPAND_STORAGE',
|
storage,
|
||||||
storage,
|
isOpen
|
||||||
isOpen
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
this.setState({
|
this.setState({
|
||||||
isOpen: isOpen
|
isOpen: isOpen
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddFolderButtonClick (e) {
|
handleAddFolderButtonClick(e) {
|
||||||
const { storage } = this.props
|
const { storage } = this.props
|
||||||
|
|
||||||
modal.open(CreateFolderModal, {
|
modal.open(CreateFolderModal, {
|
||||||
@@ -133,23 +132,23 @@ class StorageItem extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHeaderInfoClick (e) {
|
handleHeaderInfoClick(e) {
|
||||||
const { storage, dispatch } = this.props
|
const { storage, dispatch } = this.props
|
||||||
dispatch(push('/storages/' + storage.key))
|
dispatch(push('/storages/' + storage.key))
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFolderButtonClick (folderKey) {
|
handleFolderButtonClick(folderKey) {
|
||||||
return (e) => {
|
return e => {
|
||||||
const { storage, dispatch } = this.props
|
const { storage, dispatch } = this.props
|
||||||
dispatch(push('/storages/' + storage.key + '/folders/' + folderKey))
|
dispatch(push('/storages/' + storage.key + '/folders/' + folderKey))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFolderButtonContextMenu (e, folder) {
|
handleFolderButtonContextMenu(e, folder) {
|
||||||
context.popup([
|
context.popup([
|
||||||
{
|
{
|
||||||
label: i18n.__('Rename Folder'),
|
label: i18n.__('Rename Folder'),
|
||||||
click: (e) => this.handleRenameFolderClick(e, folder)
|
click: e => this.handleRenameFolderClick(e, folder)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
@@ -159,11 +158,11 @@ class StorageItem extends React.Component {
|
|||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: i18n.__('Export as txt'),
|
label: i18n.__('Export as txt'),
|
||||||
click: (e) => this.handleExportFolderClick(e, folder, 'txt')
|
click: e => this.handleExportFolderClick(e, folder, 'txt')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18n.__('Export as md'),
|
label: i18n.__('Export as md'),
|
||||||
click: (e) => this.handleExportFolderClick(e, folder, 'md')
|
click: e => this.handleExportFolderClick(e, folder, 'md')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -172,12 +171,12 @@ class StorageItem extends React.Component {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18n.__('Delete Folder'),
|
label: i18n.__('Delete Folder'),
|
||||||
click: (e) => this.handleFolderDeleteClick(e, folder)
|
click: e => this.handleFolderDeleteClick(e, folder)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRenameFolderClick (e, folder) {
|
handleRenameFolderClick(e, folder) {
|
||||||
const { storage } = this.props
|
const { storage } = this.props
|
||||||
modal.open(RenameFolderModal, {
|
modal.open(RenameFolderModal, {
|
||||||
storage,
|
storage,
|
||||||
@@ -185,20 +184,19 @@ class StorageItem extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleExportFolderClick (e, folder, fileType) {
|
handleExportFolderClick(e, folder, fileType) {
|
||||||
const options = {
|
const options = {
|
||||||
properties: ['openDirectory', 'createDirectory'],
|
properties: ['openDirectory', 'createDirectory'],
|
||||||
buttonLabel: i18n.__('Select directory'),
|
buttonLabel: i18n.__('Select directory'),
|
||||||
title: i18n.__('Select a folder to export the files to'),
|
title: i18n.__('Select a folder to export the files to'),
|
||||||
multiSelections: false
|
multiSelections: false
|
||||||
}
|
}
|
||||||
dialog.showOpenDialog(remote.getCurrentWindow(), options,
|
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
|
||||||
(paths) => {
|
|
||||||
if (paths && paths.length === 1) {
|
if (paths && paths.length === 1) {
|
||||||
const { storage, dispatch } = this.props
|
const { storage, dispatch } = this.props
|
||||||
dataApi
|
dataApi
|
||||||
.exportFolder(storage.key, folder.key, fileType, paths[0])
|
.exportFolder(storage.key, folder.key, fileType, paths[0])
|
||||||
.then((data) => {
|
.then(data => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'EXPORT_FOLDER',
|
type: 'EXPORT_FOLDER',
|
||||||
storage: data.storage,
|
storage: data.storage,
|
||||||
@@ -224,66 +222,74 @@ class StorageItem extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFolderDeleteClick (e, folder) {
|
handleFolderDeleteClick(e, folder) {
|
||||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: i18n.__('Delete Folder'),
|
message: i18n.__('Delete Folder'),
|
||||||
detail: i18n.__('This will delete all notes in the folder and can not be undone.'),
|
detail: i18n.__(
|
||||||
|
'This will delete all notes in the folder and can not be undone.'
|
||||||
|
),
|
||||||
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||||
})
|
})
|
||||||
|
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
const { storage, dispatch } = this.props
|
const { storage, dispatch } = this.props
|
||||||
dataApi
|
dataApi.deleteFolder(storage.key, folder.key).then(data => {
|
||||||
.deleteFolder(storage.key, folder.key)
|
dispatch({
|
||||||
.then((data) => {
|
type: 'DELETE_FOLDER',
|
||||||
dispatch({
|
storage: data.storage,
|
||||||
type: 'DELETE_FOLDER',
|
folderKey: data.folderKey
|
||||||
storage: data.storage,
|
|
||||||
folderKey: data.folderKey
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDragEnter (e, key) {
|
handleDragEnter(e, key) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.state.draggedOver === key) { return }
|
if (this.state.draggedOver === key) {
|
||||||
|
return
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
draggedOver: key
|
draggedOver: key
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDragLeave (e) {
|
handleDragLeave(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.state.draggedOver === null) { return }
|
if (this.state.draggedOver === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
draggedOver: null
|
draggedOver: null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
dropNote (storage, folder, dispatch, location, noteData) {
|
dropNote(storage, folder, dispatch, location, noteData) {
|
||||||
noteData = noteData.filter((note) => folder.key !== note.folder)
|
noteData = noteData.filter(note => folder.key !== note.folder)
|
||||||
if (noteData.length === 0) return
|
if (noteData.length === 0) return
|
||||||
|
|
||||||
Promise.all(
|
Promise.all(
|
||||||
noteData.map((note) => dataApi.moveNote(note.storage, note.key, storage.key, folder.key))
|
noteData.map(note =>
|
||||||
|
dataApi.moveNote(note.storage, note.key, storage.key, folder.key)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.then((createdNoteData) => {
|
.then(createdNoteData => {
|
||||||
createdNoteData.forEach((newNote) => {
|
createdNoteData.forEach(newNote => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'MOVE_NOTE',
|
type: 'MOVE_NOTE',
|
||||||
originNote: noteData.find((note) => note.content === newNote.oldContent),
|
originNote: noteData.find(
|
||||||
note: newNote
|
note => note.content === newNote.oldContent
|
||||||
|
),
|
||||||
|
note: newNote
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
.catch(err => {
|
||||||
.catch((err) => {
|
console.error(`error on delete notes: ${err}`)
|
||||||
console.error(`error on delete notes: ${err}`)
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDrop (e, storage, folder, dispatch, location) {
|
handleDrop(e, storage, folder, dispatch, location) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.state.draggedOver !== null) {
|
if (this.state.draggedOver !== null) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -294,21 +300,37 @@ class StorageItem extends React.Component {
|
|||||||
this.dropNote(storage, folder, dispatch, location, noteData)
|
this.dropNote(storage, folder, dispatch, location, noteData)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { storage, location, isFolded, data, dispatch } = this.props
|
const { storage, location, isFolded, data, dispatch } = this.props
|
||||||
const { folderNoteMap, trashedSet } = data
|
const { folderNoteMap, trashedSet } = data
|
||||||
const SortableStorageItemChild = SortableElement(StorageItemChild)
|
const SortableStorageItemChild = SortableElement(StorageItemChild)
|
||||||
const folderList = storage.folders.map((folder, index) => {
|
const folderList = storage.folders.map((folder, index) => {
|
||||||
const folderRegex = new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + escapeStringRegexp(path.sep) + 'folders' + escapeStringRegexp(path.sep) + folder.key)
|
const folderRegex = new RegExp(
|
||||||
const isActive = !!(location.pathname.match(folderRegex))
|
escapeStringRegexp(path.sep) +
|
||||||
|
'storages' +
|
||||||
|
escapeStringRegexp(path.sep) +
|
||||||
|
storage.key +
|
||||||
|
escapeStringRegexp(path.sep) +
|
||||||
|
'folders' +
|
||||||
|
escapeStringRegexp(path.sep) +
|
||||||
|
folder.key
|
||||||
|
)
|
||||||
|
const isActive = !!location.pathname.match(folderRegex)
|
||||||
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
||||||
|
|
||||||
let noteCount = 0
|
let noteCount = 0
|
||||||
if (noteSet) {
|
if (noteSet) {
|
||||||
let trashedNoteCount = 0
|
let trashedNoteCount = 0
|
||||||
const noteKeys = noteSet.map(noteKey => { return noteKey })
|
const noteKeys = noteSet.map(noteKey => {
|
||||||
|
return noteKey
|
||||||
|
})
|
||||||
trashedSet.toJS().forEach(trashedKey => {
|
trashedSet.toJS().forEach(trashedKey => {
|
||||||
if (noteKeys.some(noteKey => { return noteKey === trashedKey })) trashedNoteCount++
|
if (
|
||||||
|
noteKeys.some(noteKey => {
|
||||||
|
return noteKey === trashedKey
|
||||||
|
})
|
||||||
|
)
|
||||||
|
trashedNoteCount++
|
||||||
})
|
})
|
||||||
noteCount = noteSet.size - trashedNoteCount
|
noteCount = noteSet.size - trashedNoteCount
|
||||||
}
|
}
|
||||||
@@ -317,73 +339,80 @@ class StorageItem extends React.Component {
|
|||||||
key={folder.key}
|
key={folder.key}
|
||||||
index={index}
|
index={index}
|
||||||
isActive={isActive || folder.key === this.state.draggedOver}
|
isActive={isActive || folder.key === this.state.draggedOver}
|
||||||
handleButtonClick={(e) => this.handleFolderButtonClick(folder.key)(e)}
|
handleButtonClick={e => this.handleFolderButtonClick(folder.key)(e)}
|
||||||
handleContextMenu={(e) => this.handleFolderButtonContextMenu(e, folder)}
|
handleContextMenu={e => this.handleFolderButtonContextMenu(e, folder)}
|
||||||
folderName={folder.name}
|
folderName={folder.name}
|
||||||
folderColor={folder.color}
|
folderColor={folder.color}
|
||||||
isFolded={isFolded}
|
isFolded={isFolded}
|
||||||
noteCount={noteCount}
|
noteCount={noteCount}
|
||||||
handleDrop={(e) => {
|
handleDrop={e => {
|
||||||
this.handleDrop(e, storage, folder, dispatch, location)
|
this.handleDrop(e, storage, folder, dispatch, location)
|
||||||
}}
|
}}
|
||||||
handleDragEnter={(e) => {
|
handleDragEnter={e => {
|
||||||
this.handleDragEnter(e, folder.key)
|
this.handleDragEnter(e, folder.key)
|
||||||
}}
|
}}
|
||||||
handleDragLeave={(e) => {
|
handleDragLeave={e => {
|
||||||
this.handleDragLeave(e, folder)
|
this.handleDragLeave(e, folder)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const isActive = location.pathname.match(new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + '$'))
|
const isActive = location.pathname.match(
|
||||||
|
new RegExp(
|
||||||
|
escapeStringRegexp(path.sep) +
|
||||||
|
'storages' +
|
||||||
|
escapeStringRegexp(path.sep) +
|
||||||
|
storage.key +
|
||||||
|
'$'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div styleName={isFolded ? 'root--folded' : 'root'}
|
<div styleName={isFolded ? 'root--folded' : 'root'} key={storage.key}>
|
||||||
key={storage.key}
|
<div
|
||||||
>
|
styleName={isActive ? 'header--active' : 'header'}
|
||||||
<div styleName={isActive
|
onContextMenu={e => this.handleHeaderContextMenu(e)}
|
||||||
? 'header--active'
|
|
||||||
: 'header'
|
|
||||||
}
|
|
||||||
onContextMenu={(e) => this.handleHeaderContextMenu(e)}
|
|
||||||
>
|
>
|
||||||
<button styleName='header-toggleButton'
|
<button
|
||||||
onMouseDown={(e) => this.handleToggleButtonClick(e)}
|
styleName='header-toggleButton'
|
||||||
|
onMouseDown={e => this.handleToggleButtonClick(e)}
|
||||||
>
|
>
|
||||||
<img src={this.state.isOpen
|
<img
|
||||||
? '../resources/icon/icon-down.svg'
|
src={
|
||||||
: '../resources/icon/icon-right.svg'
|
this.state.isOpen
|
||||||
}
|
? '../resources/icon/icon-down.svg'
|
||||||
|
: '../resources/icon/icon-right.svg'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{!isFolded &&
|
{!isFolded && (
|
||||||
<button styleName='header-addFolderButton'
|
<button
|
||||||
onClick={(e) => this.handleAddFolderButtonClick(e)}
|
styleName='header-addFolderButton'
|
||||||
|
onClick={e => this.handleAddFolderButtonClick(e)}
|
||||||
>
|
>
|
||||||
<img src='../resources/icon/icon-plus.svg' />
|
<img src='../resources/icon/icon-plus.svg' />
|
||||||
</button>
|
</button>
|
||||||
}
|
)}
|
||||||
|
|
||||||
<button styleName='header-info'
|
<button
|
||||||
onClick={(e) => this.handleHeaderInfoClick(e)}
|
styleName='header-info'
|
||||||
|
onClick={e => this.handleHeaderInfoClick(e)}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
{isFolded ? _.truncate(storage.name, {length: 1, omission: ''}) : storage.name}
|
{isFolded
|
||||||
|
? _.truncate(storage.name, { length: 1, omission: '' })
|
||||||
|
: storage.name}
|
||||||
</span>
|
</span>
|
||||||
{isFolded &&
|
{isFolded && (
|
||||||
<span styleName='header-info--folded-tooltip'>
|
<span styleName='header-info--folded-tooltip'>
|
||||||
{storage.name}
|
{storage.name}
|
||||||
</span>
|
</span>
|
||||||
}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{this.state.isOpen &&
|
{this.state.isOpen && <div>{folderList}</div>}
|
||||||
<div>
|
|
||||||
{folderList}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,17 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './SwitchButton.styl'
|
import styles from './SwitchButton.styl'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const TagButton = ({
|
const TagButton = ({ onClick, isTagActive }) => (
|
||||||
onClick, isTagActive
|
<button
|
||||||
}) => (
|
styleName={isTagActive ? 'active-button' : 'non-active-button'}
|
||||||
<button styleName={isTagActive ? 'active-button' : 'non-active-button'} onClick={onClick}>
|
onClick={onClick}
|
||||||
<img src={isTagActive
|
>
|
||||||
? '../resources/icon/icon-tag-active.svg'
|
<img
|
||||||
: '../resources/icon/icon-tag.svg'
|
src={
|
||||||
}
|
isTagActive
|
||||||
|
? '../resources/icon/icon-tag-active.svg'
|
||||||
|
: '../resources/icon/icon-tag.svg'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<span styleName='tooltip'>{i18n.__('Tags')}</span>
|
<span styleName='tooltip'>{i18n.__('Tags')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import EventEmitter from 'browser/main/lib/eventEmitter'
|
|||||||
import PreferenceButton from './PreferenceButton'
|
import PreferenceButton from './PreferenceButton'
|
||||||
import ListButton from './ListButton'
|
import ListButton from './ListButton'
|
||||||
import TagButton from './TagButton'
|
import TagButton from './TagButton'
|
||||||
import {SortableContainer} from 'react-sortable-hoc'
|
import { SortableContainer } from 'react-sortable-hoc'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import context from 'browser/lib/context'
|
import context from 'browser/lib/context'
|
||||||
import { remote } from 'electron'
|
import { remote } from 'electron'
|
||||||
@@ -24,13 +24,13 @@ import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
|||||||
import ColorPicker from 'browser/components/ColorPicker'
|
import ColorPicker from 'browser/components/ColorPicker'
|
||||||
import { every, sortBy } from 'lodash'
|
import { every, sortBy } from 'lodash'
|
||||||
|
|
||||||
function matchActiveTags (tags, activeTags) {
|
function matchActiveTags(tags, activeTags) {
|
||||||
return every(activeTags, v => tags.indexOf(v) >= 0)
|
return every(activeTags, v => tags.indexOf(v) >= 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
class SideNav extends React.Component {
|
class SideNav extends React.Component {
|
||||||
// TODO: should not use electron stuff v0.7
|
// TODO: should not use electron stuff v0.7
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -47,24 +47,32 @@ class SideNav extends React.Component {
|
|||||||
this.handleColorPickerReset = this.handleColorPickerReset.bind(this)
|
this.handleColorPickerReset = this.handleColorPickerReset.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
EventEmitter.on('side:preferences', this.handleMenuButtonClick)
|
EventEmitter.on('side:preferences', this.handleMenuButtonClick)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount() {
|
||||||
EventEmitter.off('side:preferences', this.handleMenuButtonClick)
|
EventEmitter.off('side:preferences', this.handleMenuButtonClick)
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteTag (tag) {
|
deleteTag(tag) {
|
||||||
const selectedButton = remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
const selectedButton = remote.dialog.showMessageBox(
|
||||||
type: 'warning',
|
remote.getCurrentWindow(),
|
||||||
message: i18n.__('Confirm tag deletion'),
|
{
|
||||||
detail: i18n.__('This will permanently remove this tag.'),
|
type: 'warning',
|
||||||
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
message: i18n.__('Confirm tag deletion'),
|
||||||
})
|
detail: i18n.__('This will permanently remove this tag.'),
|
||||||
|
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (selectedButton === 0) {
|
if (selectedButton === 0) {
|
||||||
const { data, dispatch, location, match: { params } } = this.props
|
const {
|
||||||
|
data,
|
||||||
|
dispatch,
|
||||||
|
location,
|
||||||
|
match: { params }
|
||||||
|
} = this.props
|
||||||
|
|
||||||
const notes = data.noteMap
|
const notes = data.noteMap
|
||||||
.map(note => note)
|
.map(note => note)
|
||||||
@@ -78,44 +86,48 @@ class SideNav extends React.Component {
|
|||||||
return note
|
return note
|
||||||
})
|
})
|
||||||
|
|
||||||
Promise
|
Promise.all(
|
||||||
.all(notes.map(note => dataApi.updateNote(note.storage, note.key, note)))
|
notes.map(note => dataApi.updateNote(note.storage, note.key, note))
|
||||||
.then(updatedNotes => {
|
).then(updatedNotes => {
|
||||||
updatedNotes.forEach(note => {
|
updatedNotes.forEach(note => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note
|
note
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (location.pathname.match('/tags')) {
|
|
||||||
const tags = params.tagname.split(' ')
|
|
||||||
const index = tags.indexOf(tag)
|
|
||||||
if (index !== -1) {
|
|
||||||
tags.splice(index, 1)
|
|
||||||
|
|
||||||
dispatch(push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (location.pathname.match('/tags')) {
|
||||||
|
const tags = params.tagname.split(' ')
|
||||||
|
const index = tags.indexOf(tag)
|
||||||
|
if (index !== -1) {
|
||||||
|
tags.splice(index, 1)
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
push(
|
||||||
|
`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMenuButtonClick (e) {
|
handleMenuButtonClick(e) {
|
||||||
openModal(PreferencesModal)
|
openModal(PreferencesModal)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHomeButtonClick (e) {
|
handleHomeButtonClick(e) {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
dispatch(push('/home'))
|
dispatch(push('/home'))
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStarredButtonClick (e) {
|
handleStarredButtonClick(e) {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
dispatch(push('/starred'))
|
dispatch(push('/starred'))
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTagContextMenu (e, tag) {
|
handleTagContextMenu(e, tag) {
|
||||||
const menu = []
|
const menu = []
|
||||||
|
|
||||||
menu.push({
|
menu.push({
|
||||||
@@ -125,13 +137,17 @@ class SideNav extends React.Component {
|
|||||||
|
|
||||||
menu.push({
|
menu.push({
|
||||||
label: i18n.__('Customize Color'),
|
label: i18n.__('Customize Color'),
|
||||||
click: this.displayColorPicker.bind(this, tag, e.target.getBoundingClientRect())
|
click: this.displayColorPicker.bind(
|
||||||
|
this,
|
||||||
|
tag,
|
||||||
|
e.target.getBoundingClientRect()
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
context.popup(menu)
|
context.popup(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissColorPicker () {
|
dismissColorPicker() {
|
||||||
this.setState({
|
this.setState({
|
||||||
colorPicker: {
|
colorPicker: {
|
||||||
show: false
|
show: false
|
||||||
@@ -139,7 +155,7 @@ class SideNav extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
displayColorPicker (tagName, rect) {
|
displayColorPicker(tagName, rect) {
|
||||||
const { config } = this.props
|
const { config } = this.props
|
||||||
this.setState({
|
this.setState({
|
||||||
colorPicker: {
|
colorPicker: {
|
||||||
@@ -151,10 +167,17 @@ class SideNav extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleColorPickerConfirm (color) {
|
handleColorPickerConfirm(color) {
|
||||||
const { dispatch, config: {coloredTags} } = this.props
|
const {
|
||||||
const { colorPicker: { tagName } } = this.state
|
dispatch,
|
||||||
const newColoredTags = Object.assign({}, coloredTags, {[tagName]: color.hex})
|
config: { coloredTags }
|
||||||
|
} = this.props
|
||||||
|
const {
|
||||||
|
colorPicker: { tagName }
|
||||||
|
} = this.state
|
||||||
|
const newColoredTags = Object.assign({}, coloredTags, {
|
||||||
|
[tagName]: color.hex
|
||||||
|
})
|
||||||
|
|
||||||
const config = { coloredTags: newColoredTags }
|
const config = { coloredTags: newColoredTags }
|
||||||
ConfigManager.set(config)
|
ConfigManager.set(config)
|
||||||
@@ -165,9 +188,14 @@ class SideNav extends React.Component {
|
|||||||
this.dismissColorPicker()
|
this.dismissColorPicker()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleColorPickerReset () {
|
handleColorPickerReset() {
|
||||||
const { dispatch, config: {coloredTags} } = this.props
|
const {
|
||||||
const { colorPicker: { tagName } } = this.state
|
dispatch,
|
||||||
|
config: { coloredTags }
|
||||||
|
} = this.props
|
||||||
|
const {
|
||||||
|
colorPicker: { tagName }
|
||||||
|
} = this.state
|
||||||
const newColoredTags = Object.assign({}, coloredTags)
|
const newColoredTags = Object.assign({}, coloredTags)
|
||||||
|
|
||||||
delete newColoredTags[tagName]
|
delete newColoredTags[tagName]
|
||||||
@@ -181,43 +209,41 @@ class SideNav extends React.Component {
|
|||||||
this.dismissColorPicker()
|
this.dismissColorPicker()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleToggleButtonClick (e) {
|
handleToggleButtonClick(e) {
|
||||||
const { dispatch, config } = this.props
|
const { dispatch, config } = this.props
|
||||||
|
|
||||||
ConfigManager.set({isSideNavFolded: !config.isSideNavFolded})
|
ConfigManager.set({ isSideNavFolded: !config.isSideNavFolded })
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'SET_IS_SIDENAV_FOLDED',
|
type: 'SET_IS_SIDENAV_FOLDED',
|
||||||
isFolded: !config.isSideNavFolded
|
isFolded: !config.isSideNavFolded
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTrashedButtonClick (e) {
|
handleTrashedButtonClick(e) {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
dispatch(push('/trashed'))
|
dispatch(push('/trashed'))
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSwitchFoldersButtonClick () {
|
handleSwitchFoldersButtonClick() {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
dispatch(push('/home'))
|
dispatch(push('/home'))
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSwitchTagsButtonClick () {
|
handleSwitchTagsButtonClick() {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
dispatch(push('/alltags'))
|
dispatch(push('/alltags'))
|
||||||
}
|
}
|
||||||
|
|
||||||
onSortEnd (storage) {
|
onSortEnd(storage) {
|
||||||
return ({oldIndex, newIndex}) => {
|
return ({ oldIndex, newIndex }) => {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
dataApi
|
dataApi.reorderFolder(storage.key, oldIndex, newIndex).then(data => {
|
||||||
.reorderFolder(storage.key, oldIndex, newIndex)
|
dispatch({ type: 'REORDER_FOLDER', storage: data.storage })
|
||||||
.then((data) => {
|
})
|
||||||
dispatch({ type: 'REORDER_FOLDER', storage: data.storage })
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SideNavComponent (isFolded, storageList) {
|
SideNavComponent(isFolded, storageList) {
|
||||||
const { location, data, config } = this.props
|
const { location, data, config } = this.props
|
||||||
|
|
||||||
const isHomeActive = !!location.pathname.match(/^\/home$/)
|
const isHomeActive = !!location.pathname.match(/^\/home$/)
|
||||||
@@ -227,25 +253,35 @@ class SideNav extends React.Component {
|
|||||||
let component
|
let component
|
||||||
|
|
||||||
// TagsMode is not selected
|
// TagsMode is not selected
|
||||||
if (!location.pathname.match('/tags') && !location.pathname.match('/alltags')) {
|
if (
|
||||||
|
!location.pathname.match('/tags') &&
|
||||||
|
!location.pathname.match('/alltags')
|
||||||
|
) {
|
||||||
component = (
|
component = (
|
||||||
<div>
|
<div>
|
||||||
<SideNavFilter
|
<SideNavFilter
|
||||||
isFolded={isFolded}
|
isFolded={isFolded}
|
||||||
isHomeActive={isHomeActive}
|
isHomeActive={isHomeActive}
|
||||||
handleAllNotesButtonClick={(e) => this.handleHomeButtonClick(e)}
|
handleAllNotesButtonClick={e => this.handleHomeButtonClick(e)}
|
||||||
isStarredActive={isStarredActive}
|
isStarredActive={isStarredActive}
|
||||||
isTrashedActive={isTrashedActive}
|
isTrashedActive={isTrashedActive}
|
||||||
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
|
handleStarredButtonClick={e => this.handleStarredButtonClick(e)}
|
||||||
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
|
handleTrashedButtonClick={e => this.handleTrashedButtonClick(e)}
|
||||||
counterTotalNote={data.noteMap._map.size - data.trashedSet._set.size}
|
counterTotalNote={
|
||||||
|
data.noteMap._map.size - data.trashedSet._set.size
|
||||||
|
}
|
||||||
counterStarredNote={data.starredSet._set.size}
|
counterStarredNote={data.starredSet._set.size}
|
||||||
counterDelNote={data.trashedSet._set.size}
|
counterDelNote={data.trashedSet._set.size}
|
||||||
handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(this)}
|
handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(
|
||||||
|
this
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StorageList storageList={storageList} isFolded={isFolded} />
|
<StorageList storageList={storageList} isFolded={isFolded} />
|
||||||
<NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} />
|
<NavToggleButton
|
||||||
|
isFolded={isFolded}
|
||||||
|
handleToggleButtonClick={this.handleToggleButtonClick.bind(this)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -257,22 +293,26 @@ class SideNav extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<div styleName='tag-control-sortTagsBy'>
|
<div styleName='tag-control-sortTagsBy'>
|
||||||
<i className='fa fa-angle-down' />
|
<i className='fa fa-angle-down' />
|
||||||
<select styleName='tag-control-sortTagsBy-select'
|
<select
|
||||||
|
styleName='tag-control-sortTagsBy-select'
|
||||||
title={i18n.__('Select filter mode')}
|
title={i18n.__('Select filter mode')}
|
||||||
value={config.sortTagsBy}
|
value={config.sortTagsBy}
|
||||||
onChange={(e) => this.handleSortTagsByChange(e)}
|
onChange={e => this.handleSortTagsByChange(e)}
|
||||||
>
|
>
|
||||||
<option title='Sort alphabetically'
|
<option title='Sort alphabetically' value='ALPHABETICAL'>
|
||||||
value='ALPHABETICAL'>{i18n.__('Alphabetically')}</option>
|
{i18n.__('Alphabetically')}
|
||||||
<option title='Sort by update time'
|
</option>
|
||||||
value='COUNTER'>{i18n.__('Counter')}</option>
|
<option title='Sort by update time' value='COUNTER'>
|
||||||
|
{i18n.__('Counter')}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='tagList'>
|
<div styleName='tagList'>{this.tagListComponent(data)}</div>
|
||||||
{this.tagListComponent(data)}
|
<NavToggleButton
|
||||||
</div>
|
isFolded={isFolded}
|
||||||
<NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} />
|
handleToggleButtonClick={this.handleToggleButtonClick.bind(this)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -280,82 +320,84 @@ class SideNav extends React.Component {
|
|||||||
return component
|
return component
|
||||||
}
|
}
|
||||||
|
|
||||||
tagListComponent () {
|
tagListComponent() {
|
||||||
const { data, location, config } = this.props
|
const { data, location, config } = this.props
|
||||||
const { colorPicker } = this.state
|
const { colorPicker } = this.state
|
||||||
const activeTags = this.getActiveTags(location.pathname)
|
const activeTags = this.getActiveTags(location.pathname)
|
||||||
const relatedTags = this.getRelatedTags(activeTags, data.noteMap)
|
const relatedTags = this.getRelatedTags(activeTags, data.noteMap)
|
||||||
let tagList = sortBy(data.tagNoteMap.map(
|
let tagList = sortBy(
|
||||||
(tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
|
data.tagNoteMap
|
||||||
).filter(
|
.map((tag, name) => ({
|
||||||
tag => tag.size > 0
|
name,
|
||||||
), ['name'])
|
size: tag.size,
|
||||||
|
related: relatedTags.has(name)
|
||||||
|
}))
|
||||||
|
.filter(tag => tag.size > 0),
|
||||||
|
['name']
|
||||||
|
)
|
||||||
if (config.ui.enableLiveNoteCounts && activeTags.length !== 0) {
|
if (config.ui.enableLiveNoteCounts && activeTags.length !== 0) {
|
||||||
const notesTags = data.noteMap.map(note => note.tags)
|
const notesTags = data.noteMap.map(note => note.tags)
|
||||||
tagList = tagList.map(tag => {
|
tagList = tagList.map(tag => {
|
||||||
tag.size = notesTags.filter(tags => tags.includes(tag.name) && matchActiveTags(tags, activeTags)).length
|
tag.size = notesTags.filter(
|
||||||
|
tags => tags.includes(tag.name) && matchActiveTags(tags, activeTags)
|
||||||
|
).length
|
||||||
return tag
|
return tag
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (config.sortTagsBy === 'COUNTER') {
|
if (config.sortTagsBy === 'COUNTER') {
|
||||||
tagList = sortBy(tagList, item => (0 - item.size))
|
tagList = sortBy(tagList, item => 0 - item.size)
|
||||||
}
|
}
|
||||||
if (config.ui.showOnlyRelatedTags && (relatedTags.size > 0)) {
|
if (config.ui.showOnlyRelatedTags && relatedTags.size > 0) {
|
||||||
tagList = tagList.filter(
|
tagList = tagList.filter(tag => tag.related)
|
||||||
tag => tag.related
|
}
|
||||||
|
return tagList.map(tag => {
|
||||||
|
return (
|
||||||
|
<TagListItem
|
||||||
|
name={tag.name}
|
||||||
|
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
|
||||||
|
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
|
||||||
|
handleContextMenu={this.handleTagContextMenu.bind(this)}
|
||||||
|
isActive={
|
||||||
|
this.getTagActive(location.pathname, tag.name) ||
|
||||||
|
colorPicker.tagName === tag.name
|
||||||
|
}
|
||||||
|
isRelated={tag.related}
|
||||||
|
key={tag.name}
|
||||||
|
count={tag.size}
|
||||||
|
color={config.coloredTags[tag.name]}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
return (
|
|
||||||
tagList.map(tag => {
|
|
||||||
return (
|
|
||||||
<TagListItem
|
|
||||||
name={tag.name}
|
|
||||||
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
|
|
||||||
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
|
|
||||||
handleContextMenu={this.handleTagContextMenu.bind(this)}
|
|
||||||
isActive={this.getTagActive(location.pathname, tag.name) || (colorPicker.tagName === tag.name)}
|
|
||||||
isRelated={tag.related}
|
|
||||||
key={tag.name}
|
|
||||||
count={tag.size}
|
|
||||||
color={config.coloredTags[tag.name]}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getRelatedTags (activeTags, noteMap) {
|
getRelatedTags(activeTags, noteMap) {
|
||||||
if (activeTags.length === 0) {
|
if (activeTags.length === 0) {
|
||||||
return new Set()
|
return new Set()
|
||||||
}
|
}
|
||||||
const relatedNotes = noteMap.map(
|
const relatedNotes = noteMap
|
||||||
note => ({key: note.key, tags: note.tags})
|
.map(note => ({ key: note.key, tags: note.tags }))
|
||||||
).filter(
|
.filter(note => activeTags.every(tag => note.tags.includes(tag)))
|
||||||
note => activeTags.every(tag => note.tags.includes(tag))
|
|
||||||
)
|
|
||||||
const relatedTags = new Set()
|
const relatedTags = new Set()
|
||||||
relatedNotes.forEach(note => note.tags.map(tag => relatedTags.add(tag)))
|
relatedNotes.forEach(note => note.tags.map(tag => relatedTags.add(tag)))
|
||||||
return relatedTags
|
return relatedTags
|
||||||
}
|
}
|
||||||
|
|
||||||
getTagActive (path, tag) {
|
getTagActive(path, tag) {
|
||||||
return this.getActiveTags(path).includes(tag)
|
return this.getActiveTags(path).includes(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveTags (path) {
|
getActiveTags(path) {
|
||||||
const pathSegments = path.split('/')
|
const pathSegments = path.split('/')
|
||||||
const tags = pathSegments[pathSegments.length - 1]
|
const tags = pathSegments[pathSegments.length - 1]
|
||||||
return (tags === 'alltags')
|
return tags === 'alltags' ? [] : decodeURIComponent(tags).split(' ')
|
||||||
? []
|
|
||||||
: decodeURIComponent(tags).split(' ')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClickTagListItem (name) {
|
handleClickTagListItem(name) {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
dispatch(push(`/tags/${encodeURIComponent(name)}`))
|
dispatch(push(`/tags/${encodeURIComponent(name)}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSortTagsByChange (e) {
|
handleSortTagsByChange(e) {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
@@ -369,7 +411,7 @@ class SideNav extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClickNarrowToTag (tag) {
|
handleClickNarrowToTag(tag) {
|
||||||
const { dispatch, location } = this.props
|
const { dispatch, location } = this.props
|
||||||
const listOfTags = this.getActiveTags(location.pathname)
|
const listOfTags = this.getActiveTags(location.pathname)
|
||||||
const indexOfTag = listOfTags.indexOf(tag)
|
const indexOfTag = listOfTags.indexOf(tag)
|
||||||
@@ -381,33 +423,38 @@ class SideNav extends React.Component {
|
|||||||
dispatch(push(`/tags/${encodeURIComponent(listOfTags.join(' '))}`))
|
dispatch(push(`/tags/${encodeURIComponent(listOfTags.join(' '))}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
emptyTrash (entries) {
|
emptyTrash(entries) {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
const deletionPromises = entries.map((note) => {
|
const deletionPromises = entries.map(note => {
|
||||||
return dataApi.deleteNote(note.storage, note.key)
|
return dataApi.deleteNote(note.storage, note.key)
|
||||||
})
|
})
|
||||||
const { confirmDeletion } = this.props.config.ui
|
const { confirmDeletion } = this.props.config.ui
|
||||||
if (!confirmDeleteNote(confirmDeletion, true)) return
|
if (!confirmDeleteNote(confirmDeletion, true)) return
|
||||||
Promise.all(deletionPromises)
|
Promise.all(deletionPromises)
|
||||||
.then((arrayOfStorageAndNoteKeys) => {
|
.then(arrayOfStorageAndNoteKeys => {
|
||||||
arrayOfStorageAndNoteKeys.forEach(({ storageKey, noteKey }) => {
|
arrayOfStorageAndNoteKeys.forEach(({ storageKey, noteKey }) => {
|
||||||
dispatch({ type: 'DELETE_NOTE', storageKey, noteKey })
|
dispatch({ type: 'DELETE_NOTE', storageKey, noteKey })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Cannot Delete note: ' + err)
|
||||||
})
|
})
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('Cannot Delete note: ' + err)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFilterButtonContextMenu (event) {
|
handleFilterButtonContextMenu(event) {
|
||||||
const { data } = this.props
|
const { data } = this.props
|
||||||
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
const trashedNotes = data.trashedSet
|
||||||
|
.toJS()
|
||||||
|
.map(uniqueKey => data.noteMap.get(uniqueKey))
|
||||||
context.popup([
|
context.popup([
|
||||||
{ label: i18n.__('Empty Trash'), click: () => this.emptyTrash(trashedNotes) }
|
{
|
||||||
|
label: i18n.__('Empty Trash'),
|
||||||
|
click: () => this.emptyTrash(trashedNotes)
|
||||||
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { data, location, config, dispatch } = this.props
|
const { data, location, config, dispatch } = this.props
|
||||||
const { colorPicker: colorPickerState } = this.state
|
const { colorPicker: colorPickerState } = this.state
|
||||||
|
|
||||||
@@ -415,16 +462,18 @@ class SideNav extends React.Component {
|
|||||||
|
|
||||||
const storageList = data.storageMap.map((storage, key) => {
|
const storageList = data.storageMap.map((storage, key) => {
|
||||||
const SortableStorageItem = SortableContainer(StorageItem)
|
const SortableStorageItem = SortableContainer(StorageItem)
|
||||||
return <SortableStorageItem
|
return (
|
||||||
key={storage.key}
|
<SortableStorageItem
|
||||||
storage={storage}
|
key={storage.key}
|
||||||
data={data}
|
storage={storage}
|
||||||
location={location}
|
data={data}
|
||||||
isFolded={isFolded}
|
location={location}
|
||||||
dispatch={dispatch}
|
isFolded={isFolded}
|
||||||
onSortEnd={this.onSortEnd.bind(this)(storage)}
|
dispatch={dispatch}
|
||||||
useDragHandle
|
onSortEnd={this.onSortEnd.bind(this)(storage)}
|
||||||
/>
|
useDragHandle
|
||||||
|
/>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
let colorPicker
|
let colorPicker
|
||||||
@@ -444,15 +493,22 @@ class SideNav extends React.Component {
|
|||||||
if (!isFolded) style.width = this.props.width
|
if (!isFolded) style.width = this.props.width
|
||||||
const isTagActive = /tag/.test(location.pathname)
|
const isTagActive = /tag/.test(location.pathname)
|
||||||
return (
|
return (
|
||||||
<div className='SideNav'
|
<div
|
||||||
|
className='SideNav'
|
||||||
styleName={isFolded ? 'root--folded' : 'root'}
|
styleName={isFolded ? 'root--folded' : 'root'}
|
||||||
tabIndex='1'
|
tabIndex='1'
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
<div styleName='top'>
|
<div styleName='top'>
|
||||||
<div styleName='switch-buttons'>
|
<div styleName='switch-buttons'>
|
||||||
<ListButton onClick={this.handleSwitchFoldersButtonClick.bind(this)} isTagActive={isTagActive} />
|
<ListButton
|
||||||
<TagButton onClick={this.handleSwitchTagsButtonClick.bind(this)} isTagActive={isTagActive} />
|
onClick={this.handleSwitchFoldersButtonClick.bind(this)}
|
||||||
|
isTagActive={isTagActive}
|
||||||
|
/>
|
||||||
|
<TagButton
|
||||||
|
onClick={this.handleSwitchTagsButtonClick.bind(this)}
|
||||||
|
isTagActive={isTagActive}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<PreferenceButton onClick={this.handleMenuButtonClick} />
|
<PreferenceButton onClick={this.handleMenuButtonClick} />
|
||||||
|
|||||||
@@ -11,30 +11,43 @@ const electron = require('electron')
|
|||||||
const { remote, ipcRenderer } = electron
|
const { remote, ipcRenderer } = electron
|
||||||
const { 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]
|
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
|
||||||
|
]
|
||||||
|
|
||||||
class StatusBar extends React.Component {
|
class StatusBar extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
super(props)
|
||||||
this.handleZoomInMenuItem = this.handleZoomInMenuItem.bind(this)
|
this.handleZoomInMenuItem = this.handleZoomInMenuItem.bind(this)
|
||||||
this.handleZoomOutMenuItem = this.handleZoomOutMenuItem.bind(this)
|
this.handleZoomOutMenuItem = this.handleZoomOutMenuItem.bind(this)
|
||||||
this.handleZoomResetMenuItem = this.handleZoomResetMenuItem.bind(this)
|
this.handleZoomResetMenuItem = this.handleZoomResetMenuItem.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
EventEmitter.on('status:zoomin', this.handleZoomInMenuItem)
|
EventEmitter.on('status:zoomin', this.handleZoomInMenuItem)
|
||||||
EventEmitter.on('status:zoomout', this.handleZoomOutMenuItem)
|
EventEmitter.on('status:zoomout', this.handleZoomOutMenuItem)
|
||||||
EventEmitter.on('status:zoomreset', this.handleZoomResetMenuItem)
|
EventEmitter.on('status:zoomreset', this.handleZoomResetMenuItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount() {
|
||||||
EventEmitter.off('status:zoomin', this.handleZoomInMenuItem)
|
EventEmitter.off('status:zoomin', this.handleZoomInMenuItem)
|
||||||
EventEmitter.off('status:zoomout', this.handleZoomOutMenuItem)
|
EventEmitter.off('status:zoomout', this.handleZoomOutMenuItem)
|
||||||
EventEmitter.off('status:zoomreset', this.handleZoomResetMenuItem)
|
EventEmitter.off('status:zoomreset', this.handleZoomResetMenuItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateApp () {
|
updateApp() {
|
||||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: i18n.__('Update Boostnote'),
|
message: i18n.__('Update Boostnote'),
|
||||||
@@ -47,10 +60,10 @@ class StatusBar extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleZoomButtonClick (e) {
|
handleZoomButtonClick(e) {
|
||||||
const templates = []
|
const templates = []
|
||||||
|
|
||||||
zoomOptions.forEach((zoom) => {
|
zoomOptions.forEach(zoom => {
|
||||||
templates.push({
|
templates.push({
|
||||||
label: Math.floor(zoom * 100) + '%',
|
label: Math.floor(zoom * 100) + '%',
|
||||||
click: () => this.handleZoomMenuItemClick(zoom)
|
click: () => this.handleZoomMenuItemClick(zoom)
|
||||||
@@ -60,7 +73,7 @@ class StatusBar extends React.Component {
|
|||||||
context.popup(templates)
|
context.popup(templates)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleZoomMenuItemClick (zoomFactor) {
|
handleZoomMenuItemClick(zoomFactor) {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
ZoomManager.setZoom(zoomFactor)
|
ZoomManager.setZoom(zoomFactor)
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -69,40 +82,36 @@ class StatusBar extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleZoomInMenuItem () {
|
handleZoomInMenuItem() {
|
||||||
const zoomFactor = ZoomManager.getZoom() + 0.1
|
const zoomFactor = ZoomManager.getZoom() + 0.1
|
||||||
this.handleZoomMenuItemClick(zoomFactor)
|
this.handleZoomMenuItemClick(zoomFactor)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleZoomOutMenuItem () {
|
handleZoomOutMenuItem() {
|
||||||
const zoomFactor = ZoomManager.getZoom() - 0.1
|
const zoomFactor = ZoomManager.getZoom() - 0.1
|
||||||
this.handleZoomMenuItemClick(zoomFactor)
|
this.handleZoomMenuItemClick(zoomFactor)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleZoomResetMenuItem () {
|
handleZoomResetMenuItem() {
|
||||||
this.handleZoomMenuItemClick(1.0)
|
this.handleZoomMenuItemClick(1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { config, status } = this.context
|
const { config, status } = this.context
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='StatusBar'
|
<div className='StatusBar' styleName='root'>
|
||||||
styleName='root'
|
<button styleName='zoom' onClick={e => this.handleZoomButtonClick(e)}>
|
||||||
>
|
|
||||||
<button styleName='zoom'
|
|
||||||
onClick={(e) => this.handleZoomButtonClick(e)}
|
|
||||||
>
|
|
||||||
<img src='../resources/icon/icon-zoom.svg' />
|
<img src='../resources/icon/icon-zoom.svg' />
|
||||||
<span>{Math.floor(config.zoom * 100)}%</span>
|
<span>{Math.floor(config.zoom * 100)}%</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{status.updateReady
|
{status.updateReady ? (
|
||||||
? <button onClick={this.updateApp} styleName='update'>
|
<button onClick={this.updateApp} styleName='update'>
|
||||||
<i styleName='update-icon' className='fa fa-cloud-download' /> {i18n.__('Ready to Update!')}
|
<i styleName='update-icon' className='fa fa-cloud-download' />{' '}
|
||||||
|
{i18n.__('Ready to Update!')}
|
||||||
</button>
|
</button>
|
||||||
: null
|
) : null}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import CInput from 'react-composition-input'
|
|||||||
import { push } from 'connected-react-router'
|
import { push } from 'connected-react-router'
|
||||||
|
|
||||||
class TopBar extends React.Component {
|
class TopBar extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -33,19 +33,25 @@ class TopBar extends React.Component {
|
|||||||
this.handleSearchChange = this.handleSearchChange.bind(this)
|
this.handleSearchChange = this.handleSearchChange.bind(this)
|
||||||
this.handleSearchClearButton = this.handleSearchClearButton.bind(this)
|
this.handleSearchClearButton = this.handleSearchClearButton.bind(this)
|
||||||
|
|
||||||
this.debouncedUpdateKeyword = debounce((keyword) => {
|
this.debouncedUpdateKeyword = debounce(
|
||||||
dispatch(push(`/searched/${encodeURIComponent(keyword)}`))
|
keyword => {
|
||||||
this.setState({
|
dispatch(push(`/searched/${encodeURIComponent(keyword)}`))
|
||||||
search: keyword
|
this.setState({
|
||||||
})
|
search: keyword
|
||||||
ee.emit('top:search', keyword)
|
})
|
||||||
}, 1000 / 60, {
|
ee.emit('top:search', keyword)
|
||||||
maxWait: 1000 / 8
|
},
|
||||||
})
|
1000 / 60,
|
||||||
|
{
|
||||||
|
maxWait: 1000 / 8
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
const { match: { params } } = this.props
|
const {
|
||||||
|
match: { params }
|
||||||
|
} = this.props
|
||||||
const searchWord = params && params.searchword
|
const searchWord = params && params.searchword
|
||||||
if (searchWord !== undefined) {
|
if (searchWord !== undefined) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -57,12 +63,12 @@ class TopBar extends React.Component {
|
|||||||
ee.on('code:init', this.codeInitHandler)
|
ee.on('code:init', this.codeInitHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount() {
|
||||||
ee.off('top:focus-search', this.focusSearchHandler)
|
ee.off('top:focus-search', this.focusSearchHandler)
|
||||||
ee.off('code:init', this.codeInitHandler)
|
ee.off('code:init', this.codeInitHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchClearButton (e) {
|
handleSearchClearButton(e) {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
this.setState({
|
this.setState({
|
||||||
search: '',
|
search: '',
|
||||||
@@ -74,7 +80,7 @@ class TopBar extends React.Component {
|
|||||||
this.debouncedUpdateKeyword('')
|
this.debouncedUpdateKeyword('')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown (e) {
|
handleKeyDown(e) {
|
||||||
// Re-apply search field on ENTER key
|
// Re-apply search field on ENTER key
|
||||||
if (e.keyCode === 13) {
|
if (e.keyCode === 13) {
|
||||||
this.debouncedUpdateKeyword(e.target.value)
|
this.debouncedUpdateKeyword(e.target.value)
|
||||||
@@ -98,18 +104,18 @@ class TopBar extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchChange (e) {
|
handleSearchChange(e) {
|
||||||
const keyword = e.target.value
|
const keyword = e.target.value
|
||||||
this.debouncedUpdateKeyword(keyword)
|
this.debouncedUpdateKeyword(keyword)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchFocus (e) {
|
handleSearchFocus(e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isSearching: true
|
isSearching: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchBlur (e) {
|
handleSearchBlur(e) {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
let el = e.relatedTarget
|
let el = e.relatedTarget
|
||||||
@@ -128,7 +134,7 @@ class TopBar extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOnSearchFocus () {
|
handleOnSearchFocus() {
|
||||||
const el = this.refs.search.childNodes[0]
|
const el = this.refs.search.childNodes[0]
|
||||||
if (this.state.isSearching) {
|
if (this.state.isSearching) {
|
||||||
el.blur()
|
el.blur()
|
||||||
@@ -137,20 +143,22 @@ class TopBar extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCodeInit () {
|
handleCodeInit() {
|
||||||
ee.emit('top:search', this.refs.searchInput.value || '')
|
ee.emit('top:search', this.refs.searchInput.value || '')
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { config, style, location } = this.props
|
const { config, style, location } = this.props
|
||||||
return (
|
return (
|
||||||
<div className='TopBar'
|
<div
|
||||||
|
className='TopBar'
|
||||||
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
|
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
<div styleName='control'>
|
<div styleName='control'>
|
||||||
<div styleName='control-search'>
|
<div styleName='control-search'>
|
||||||
<div styleName='control-search-input'
|
<div
|
||||||
|
styleName='control-search-input'
|
||||||
onFocus={this.handleSearchFocus}
|
onFocus={this.handleSearchFocus}
|
||||||
onBlur={this.handleSearchBlur}
|
onBlur={this.handleSearchBlur}
|
||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
@@ -165,27 +173,33 @@ class TopBar extends React.Component {
|
|||||||
type='text'
|
type='text'
|
||||||
className='searchInput'
|
className='searchInput'
|
||||||
/>
|
/>
|
||||||
{this.state.search !== '' &&
|
{this.state.search !== '' && (
|
||||||
<button styleName='control-search-input-clear'
|
<button
|
||||||
|
styleName='control-search-input-clear'
|
||||||
onClick={this.handleSearchClearButton}
|
onClick={this.handleSearchClearButton}
|
||||||
>
|
>
|
||||||
<i className='fa fa-fw fa-times' />
|
<i className='fa fa-fw fa-times' />
|
||||||
<span styleName='control-search-input-clear-tooltip'>{i18n.__('Clear Search')}</span>
|
<span styleName='control-search-input-clear-tooltip'>
|
||||||
|
{i18n.__('Clear Search')}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{location.pathname === '/trashed' ? ''
|
{location.pathname === '/trashed' ? (
|
||||||
: <NewNoteButton
|
''
|
||||||
{..._.pick(this.props, [
|
) : (
|
||||||
'dispatch',
|
<NewNoteButton
|
||||||
'data',
|
{..._.pick(this.props, [
|
||||||
'config',
|
'dispatch',
|
||||||
'location',
|
'data',
|
||||||
'match'
|
'config',
|
||||||
])}
|
'location',
|
||||||
/>}
|
'match'
|
||||||
|
])}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ const electron = require('electron')
|
|||||||
const { remote, ipcRenderer } = electron
|
const { remote, ipcRenderer } = electron
|
||||||
const { dialog } = remote
|
const { dialog } = remote
|
||||||
|
|
||||||
document.addEventListener('drop', function (e) {
|
document.addEventListener('drop', function(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
})
|
})
|
||||||
document.addEventListener('dragover', function (e) {
|
document.addEventListener('dragover', function(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
})
|
})
|
||||||
@@ -33,7 +33,7 @@ let isAltWithMouse = false
|
|||||||
let isAltWithOtherKey = false
|
let isAltWithOtherKey = false
|
||||||
let isOtherKey = false
|
let isOtherKey = false
|
||||||
|
|
||||||
document.addEventListener('keydown', function (e) {
|
document.addEventListener('keydown', function(e) {
|
||||||
if (e.key === 'Alt') {
|
if (e.key === 'Alt') {
|
||||||
isAltPressing = true
|
isAltPressing = true
|
||||||
if (isOtherKey) {
|
if (isOtherKey) {
|
||||||
@@ -47,13 +47,13 @@ document.addEventListener('keydown', function (e) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
document.addEventListener('mousedown', function (e) {
|
document.addEventListener('mousedown', function(e) {
|
||||||
if (isAltPressing) {
|
if (isAltPressing) {
|
||||||
isAltWithMouse = true
|
isAltWithMouse = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
document.addEventListener('keyup', function (e) {
|
document.addEventListener('keyup', function(e) {
|
||||||
if (e.key === 'Alt') {
|
if (e.key === 'Alt') {
|
||||||
if (isAltWithMouse || isAltWithOtherKey) {
|
if (isAltWithMouse || isAltWithOtherKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -65,14 +65,13 @@ document.addEventListener('keyup', function (e) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
document.addEventListener('click', function (e) {
|
document.addEventListener('click', function(e) {
|
||||||
const className = e.target.className
|
const className = e.target.className
|
||||||
if (!className && typeof (className) !== 'string') return
|
if (!className && typeof className !== 'string') return
|
||||||
const isInfoButton = className.includes('infoButton')
|
const isInfoButton = className.includes('infoButton')
|
||||||
const offsetParent = e.target.offsetParent
|
const offsetParent = e.target.offsetParent
|
||||||
const isInfoPanel = offsetParent !== null
|
const isInfoPanel =
|
||||||
? offsetParent.className.includes('infoPanel')
|
offsetParent !== null ? offsetParent.className.includes('infoPanel') : false
|
||||||
: false
|
|
||||||
if (isInfoButton || isInfoPanel) return
|
if (isInfoButton || isInfoPanel) return
|
||||||
const infoPanel = document.querySelector('.infoPanel')
|
const infoPanel = document.querySelector('.infoPanel')
|
||||||
if (infoPanel) infoPanel.style.display = 'none'
|
if (infoPanel) infoPanel.style.display = 'none'
|
||||||
@@ -80,11 +79,11 @@ document.addEventListener('click', function (e) {
|
|||||||
|
|
||||||
const el = document.getElementById('content')
|
const el = document.getElementById('content')
|
||||||
|
|
||||||
function notify (...args) {
|
function notify(...args) {
|
||||||
return new window.Notification(...args)
|
return new window.Notification(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateApp () {
|
function updateApp() {
|
||||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: i18n.__('Update Boostnote'),
|
message: i18n.__('Update Boostnote'),
|
||||||
@@ -97,7 +96,7 @@ function updateApp () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render((
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ConnectedRouter history={history}>
|
<ConnectedRouter history={history}>
|
||||||
<Fragment>
|
<Fragment>
|
||||||
@@ -112,36 +111,41 @@ ReactDOM.render((
|
|||||||
{/* storages */}
|
{/* storages */}
|
||||||
<Redirect path='/storages' to='/home' exact />
|
<Redirect path='/storages' to='/home' exact />
|
||||||
<Route path='/storages/:storageKey' component={Main} exact />
|
<Route path='/storages/:storageKey' component={Main} exact />
|
||||||
<Route path='/storages/:storageKey/folders/:folderKey' component={Main} />
|
<Route
|
||||||
|
path='/storages/:storageKey/folders/:folderKey'
|
||||||
|
component={Main}
|
||||||
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
<DevTools />
|
<DevTools />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
</ConnectedRouter>
|
</ConnectedRouter>
|
||||||
</Provider>
|
</Provider>,
|
||||||
), el, function () {
|
el,
|
||||||
const loadingCover = document.getElementById('loadingCover')
|
function() {
|
||||||
loadingCover.parentNode.removeChild(loadingCover)
|
const loadingCover = document.getElementById('loadingCover')
|
||||||
|
loadingCover.parentNode.removeChild(loadingCover)
|
||||||
|
|
||||||
ipcRenderer.on('update-ready', function () {
|
ipcRenderer.on('update-ready', function() {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'UPDATE_AVAILABLE'
|
type: 'UPDATE_AVAILABLE'
|
||||||
|
})
|
||||||
|
notify('Update ready!', {
|
||||||
|
body: 'New Boostnote is ready to be installed.'
|
||||||
|
})
|
||||||
|
updateApp()
|
||||||
})
|
})
|
||||||
notify('Update ready!', {
|
|
||||||
body: 'New Boostnote is ready to be installed.'
|
|
||||||
})
|
|
||||||
updateApp()
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcRenderer.on('update-found', function () {
|
ipcRenderer.on('update-found', function() {
|
||||||
notify('Update found!', {
|
notify('Update found!', {
|
||||||
body: 'Preparing to update...'
|
body: 'Preparing to update...'
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
ipcRenderer.send('update-check', 'check-update')
|
ipcRenderer.send('update-check', 'check-update')
|
||||||
window.addEventListener('online', function () {
|
window.addEventListener('online', function() {
|
||||||
if (!store.getState().status.updateReady) {
|
if (!store.getState().status.updateReady) {
|
||||||
ipcRenderer.send('update-check', 'check-update')
|
ipcRenderer.send('update-check', 'check-update')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ if (!getSendEventCond()) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertPlatformName (platformName) {
|
function convertPlatformName(platformName) {
|
||||||
if (platformName === 'darwin') {
|
if (platformName === 'darwin') {
|
||||||
return 'MacOS'
|
return 'MacOS'
|
||||||
} else if (platformName === 'win32') {
|
} else if (platformName === 'win32') {
|
||||||
@@ -34,16 +34,16 @@ function convertPlatformName (platformName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSendEventCond () {
|
function getSendEventCond() {
|
||||||
const isDev = process.env.NODE_ENV !== 'production'
|
const isDev = process.env.NODE_ENV !== 'production'
|
||||||
const isDisable = !ConfigManager.default.get().amaEnabled
|
const isDisable = !ConfigManager.default.get().amaEnabled
|
||||||
const isOffline = !window.navigator.onLine
|
const isOffline = !window.navigator.onLine
|
||||||
return isDev || isDisable || isOffline
|
return isDev || isDisable || isOffline
|
||||||
}
|
}
|
||||||
|
|
||||||
function initAwsMobileAnalytics () {
|
function initAwsMobileAnalytics() {
|
||||||
if (getSendEventCond()) return
|
if (getSendEventCond()) return
|
||||||
AWS.config.credentials.get((err) => {
|
AWS.config.credentials.get(err => {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
recordDynamicCustomEvent('APP_STARTED')
|
recordDynamicCustomEvent('APP_STARTED')
|
||||||
recordStaticCustomEvent()
|
recordStaticCustomEvent()
|
||||||
@@ -51,7 +51,7 @@ function initAwsMobileAnalytics () {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function recordDynamicCustomEvent (type, options = {}) {
|
function recordDynamicCustomEvent(type, options = {}) {
|
||||||
if (getSendEventCond()) return
|
if (getSendEventCond()) return
|
||||||
try {
|
try {
|
||||||
mobileAnalyticsClient.recordEvent(type, options)
|
mobileAnalyticsClient.recordEvent(type, options)
|
||||||
@@ -62,7 +62,7 @@ function recordDynamicCustomEvent (type, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function recordStaticCustomEvent () {
|
function recordStaticCustomEvent() {
|
||||||
if (getSendEventCond()) return
|
if (getSendEventCond()) return
|
||||||
try {
|
try {
|
||||||
mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', {
|
mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', {
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
let callees = []
|
let callees = []
|
||||||
|
|
||||||
function bind (name, el) {
|
function bind(name, el) {
|
||||||
callees.push({
|
callees.push({
|
||||||
name: name,
|
name: name,
|
||||||
element: el
|
element: el
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function release (el) {
|
function release(el) {
|
||||||
callees = callees.filter((callee) => callee.element !== el)
|
callees = callees.filter(callee => callee.element !== el)
|
||||||
}
|
}
|
||||||
|
|
||||||
function fire (command) {
|
function fire(command) {
|
||||||
console.info('COMMAND >>', command)
|
console.info('COMMAND >>', command)
|
||||||
const splitted = command.split(':')
|
const splitted = command.split(':')
|
||||||
const target = splitted[0]
|
const target = splitted[0]
|
||||||
const targetCommand = splitted[1]
|
const targetCommand = splitted[1]
|
||||||
const targetCallees = callees
|
const targetCallees = callees.filter(callee => callee.name === target)
|
||||||
.filter((callee) => callee.name === target)
|
|
||||||
|
|
||||||
targetCallees.forEach((callee) => {
|
targetCallees.forEach(callee => {
|
||||||
callee.element.fire(targetCommand)
|
callee.element.fire(targetCommand)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ export const DEFAULT_CONFIG = {
|
|||||||
toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
|
toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
|
||||||
toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M',
|
toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M',
|
||||||
toggleDirection: OSX ? 'Command + Alt + Right' : 'Ctrl + Right',
|
toggleDirection: OSX ? 'Command + Alt + Right' : 'Ctrl + Right',
|
||||||
deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace',
|
deleteNote: OSX
|
||||||
|
? 'Command + Shift + Backspace'
|
||||||
|
: 'Ctrl + Shift + Backspace',
|
||||||
pasteSmartly: OSX ? 'Command + Shift + V' : 'Ctrl + Shift + V',
|
pasteSmartly: OSX ? 'Command + Shift + V' : 'Ctrl + Shift + V',
|
||||||
prettifyMarkdown: OSX ? 'Command + Shift + F' : 'Ctrl + Shift + F',
|
prettifyMarkdown: OSX ? 'Command + Shift + F' : 'Ctrl + Shift + F',
|
||||||
sortLines: OSX ? 'Command + Shift + S' : 'Ctrl + Shift + S',
|
sortLines: OSX ? 'Command + Shift + S' : 'Ctrl + Shift + S',
|
||||||
@@ -116,7 +118,7 @@ export const DEFAULT_CONFIG = {
|
|||||||
coloredTags: {}
|
coloredTags: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validate (config) {
|
function validate(config) {
|
||||||
if (!_.isObject(config)) return false
|
if (!_.isObject(config)) return false
|
||||||
if (!_.isNumber(config.zoom) || config.zoom < 0) return false
|
if (!_.isNumber(config.zoom) || config.zoom < 0) return false
|
||||||
if (!_.isBoolean(config.isSideNavFolded)) return false
|
if (!_.isBoolean(config.isSideNavFolded)) return false
|
||||||
@@ -125,13 +127,17 @@ function validate (config) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
function _save (config) {
|
function _save(config) {
|
||||||
window.localStorage.setItem('config', JSON.stringify(config))
|
window.localStorage.setItem('config', JSON.stringify(config))
|
||||||
}
|
}
|
||||||
|
|
||||||
function get () {
|
function get() {
|
||||||
const rawStoredConfig = window.localStorage.getItem('config')
|
const rawStoredConfig = window.localStorage.getItem('config')
|
||||||
const storedConfig = Object.assign({}, DEFAULT_CONFIG, JSON.parse(rawStoredConfig))
|
const storedConfig = Object.assign(
|
||||||
|
{},
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
JSON.parse(rawStoredConfig)
|
||||||
|
)
|
||||||
let config = storedConfig
|
let config = storedConfig
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -145,7 +151,10 @@ function get () {
|
|||||||
_save(config)
|
_save(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
config.autoUpdateEnabled = electronConfig.get('autoUpdateEnabled', config.autoUpdateEnabled)
|
config.autoUpdateEnabled = electronConfig.get(
|
||||||
|
'autoUpdateEnabled',
|
||||||
|
config.autoUpdateEnabled
|
||||||
|
)
|
||||||
|
|
||||||
if (!isInitialized) {
|
if (!isInitialized) {
|
||||||
isInitialized = true
|
isInitialized = true
|
||||||
@@ -157,7 +166,9 @@ function get () {
|
|||||||
document.head.appendChild(editorTheme)
|
document.head.appendChild(editorTheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
const theme = consts.THEMES.find(theme => theme.name === config.editor.theme)
|
const theme = consts.THEMES.find(
|
||||||
|
theme => theme.name === config.editor.theme
|
||||||
|
)
|
||||||
|
|
||||||
if (theme) {
|
if (theme) {
|
||||||
editorTheme.setAttribute('href', theme.path)
|
editorTheme.setAttribute('href', theme.path)
|
||||||
@@ -169,7 +180,7 @@ function get () {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
function set (updates) {
|
function set(updates) {
|
||||||
const currentConfig = get()
|
const currentConfig = get()
|
||||||
|
|
||||||
const arrangedUpdates = updates
|
const arrangedUpdates = updates
|
||||||
@@ -177,7 +188,12 @@ function set (updates) {
|
|||||||
arrangedUpdates.preview.customCSS = DEFAULT_CONFIG.preview.customCSS
|
arrangedUpdates.preview.customCSS = DEFAULT_CONFIG.preview.customCSS
|
||||||
}
|
}
|
||||||
|
|
||||||
const newConfig = Object.assign({}, DEFAULT_CONFIG, currentConfig, arrangedUpdates)
|
const newConfig = Object.assign(
|
||||||
|
{},
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
currentConfig,
|
||||||
|
arrangedUpdates
|
||||||
|
)
|
||||||
if (!validate(newConfig)) throw new Error('INVALID CONFIG')
|
if (!validate(newConfig)) throw new Error('INVALID CONFIG')
|
||||||
_save(newConfig)
|
_save(newConfig)
|
||||||
|
|
||||||
@@ -197,7 +213,9 @@ function set (updates) {
|
|||||||
document.head.appendChild(editorTheme)
|
document.head.appendChild(editorTheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTheme = consts.THEMES.find(theme => theme.name === newConfig.editor.theme)
|
const newTheme = consts.THEMES.find(
|
||||||
|
theme => theme.name === newConfig.editor.theme
|
||||||
|
)
|
||||||
|
|
||||||
if (newTheme) {
|
if (newTheme) {
|
||||||
editorTheme.setAttribute('href', newTheme.path)
|
editorTheme.setAttribute('href', newTheme.path)
|
||||||
@@ -211,20 +229,45 @@ function set (updates) {
|
|||||||
ee.emit('config-renew')
|
ee.emit('config-renew')
|
||||||
}
|
}
|
||||||
|
|
||||||
function assignConfigValues (originalConfig, rcConfig) {
|
function assignConfigValues(originalConfig, rcConfig) {
|
||||||
const config = Object.assign({}, DEFAULT_CONFIG, originalConfig, rcConfig)
|
const config = Object.assign({}, DEFAULT_CONFIG, originalConfig, rcConfig)
|
||||||
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, originalConfig.hotkey, rcConfig.hotkey)
|
config.hotkey = Object.assign(
|
||||||
config.blog = Object.assign({}, DEFAULT_CONFIG.blog, originalConfig.blog, rcConfig.blog)
|
{},
|
||||||
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
|
DEFAULT_CONFIG.hotkey,
|
||||||
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
|
originalConfig.hotkey,
|
||||||
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview)
|
rcConfig.hotkey
|
||||||
|
)
|
||||||
|
config.blog = Object.assign(
|
||||||
|
{},
|
||||||
|
DEFAULT_CONFIG.blog,
|
||||||
|
originalConfig.blog,
|
||||||
|
rcConfig.blog
|
||||||
|
)
|
||||||
|
config.ui = Object.assign(
|
||||||
|
{},
|
||||||
|
DEFAULT_CONFIG.ui,
|
||||||
|
originalConfig.ui,
|
||||||
|
rcConfig.ui
|
||||||
|
)
|
||||||
|
config.editor = Object.assign(
|
||||||
|
{},
|
||||||
|
DEFAULT_CONFIG.editor,
|
||||||
|
originalConfig.editor,
|
||||||
|
rcConfig.editor
|
||||||
|
)
|
||||||
|
config.preview = Object.assign(
|
||||||
|
{},
|
||||||
|
DEFAULT_CONFIG.preview,
|
||||||
|
originalConfig.preview,
|
||||||
|
rcConfig.preview
|
||||||
|
)
|
||||||
|
|
||||||
rewriteHotkey(config)
|
rewriteHotkey(config)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
function rewriteHotkey (config) {
|
function rewriteHotkey(config) {
|
||||||
const keys = [...Object.keys(config.hotkey)]
|
const keys = [...Object.keys(config.hotkey)]
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
config.hotkey[key] = config.hotkey[key].replace(/Cmd\s/g, 'Command ')
|
config.hotkey[key] = config.hotkey[key].replace(/Cmd\s/g, 'Command ')
|
||||||
|
|||||||
@@ -5,20 +5,20 @@ const { remote } = electron
|
|||||||
|
|
||||||
_init()
|
_init()
|
||||||
|
|
||||||
function _init () {
|
function _init() {
|
||||||
setZoom(getZoom(), true)
|
setZoom(getZoom(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function _saveZoom (zoomFactor) {
|
function _saveZoom(zoomFactor) {
|
||||||
ConfigManager.set({zoom: zoomFactor})
|
ConfigManager.set({ zoom: zoomFactor })
|
||||||
}
|
}
|
||||||
|
|
||||||
function setZoom (zoomFactor, noSave = false) {
|
function setZoom(zoomFactor, noSave = false) {
|
||||||
if (!noSave) _saveZoom(zoomFactor)
|
if (!noSave) _saveZoom(zoomFactor)
|
||||||
remote.getCurrentWebContents().setZoomFactor(zoomFactor)
|
remote.getCurrentWebContents().setZoomFactor(zoomFactor)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getZoom () {
|
function getZoom() {
|
||||||
const config = ConfigManager.get()
|
const config = ConfigManager.get()
|
||||||
|
|
||||||
return config.zoom
|
return config.zoom
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const CSON = require('@rokt33r/season')
|
|||||||
* 3. fetch notes & folders
|
* 3. fetch notes & folders
|
||||||
* 4. return `{storage: {...} folders: [folder]}`
|
* 4. return `{storage: {...} folders: [folder]}`
|
||||||
*/
|
*/
|
||||||
function addStorage (input) {
|
function addStorage(input) {
|
||||||
if (!_.isString(input.path)) {
|
if (!_.isString(input.path)) {
|
||||||
return Promise.reject(new Error('Path must be a string.'))
|
return Promise.reject(new Error('Path must be a string.'))
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ function addStorage (input) {
|
|||||||
rawStorages = []
|
rawStorages = []
|
||||||
}
|
}
|
||||||
let key = keygen()
|
let key = keygen()
|
||||||
while (rawStorages.some((storage) => storage.key === key)) {
|
while (rawStorages.some(storage => storage.key === key)) {
|
||||||
key = keygen()
|
key = keygen()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ function addStorage (input) {
|
|||||||
|
|
||||||
return Promise.resolve(newStorage)
|
return Promise.resolve(newStorage)
|
||||||
.then(resolveStorageData)
|
.then(resolveStorageData)
|
||||||
.then(function saveMetadataToLocalStorage (resolvedStorage) {
|
.then(function saveMetadataToLocalStorage(resolvedStorage) {
|
||||||
newStorage = resolvedStorage
|
newStorage = resolvedStorage
|
||||||
rawStorages.push({
|
rawStorages.push({
|
||||||
key: newStorage.key,
|
key: newStorage.key,
|
||||||
@@ -56,27 +56,29 @@ function addStorage (input) {
|
|||||||
localStorage.setItem('storages', JSON.stringify(rawStorages))
|
localStorage.setItem('storages', JSON.stringify(rawStorages))
|
||||||
return newStorage
|
return newStorage
|
||||||
})
|
})
|
||||||
.then(function (storage) {
|
.then(function(storage) {
|
||||||
return resolveStorageNotes(storage)
|
return resolveStorageNotes(storage).then(notes => {
|
||||||
.then((notes) => {
|
let unknownCount = 0
|
||||||
let unknownCount = 0
|
notes.forEach(note => {
|
||||||
notes.forEach((note) => {
|
if (!storage.folders.some(folder => note.folder === folder.key)) {
|
||||||
if (!storage.folders.some((folder) => note.folder === folder.key)) {
|
unknownCount++
|
||||||
unknownCount++
|
storage.folders.push({
|
||||||
storage.folders.push({
|
key: note.folder,
|
||||||
key: note.folder,
|
color: consts.FOLDER_COLORS[(unknownCount - 1) % 7],
|
||||||
color: consts.FOLDER_COLORS[(unknownCount - 1) % 7],
|
name: 'Unknown ' + unknownCount
|
||||||
name: 'Unknown ' + unknownCount
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (unknownCount > 0) {
|
|
||||||
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version']))
|
|
||||||
}
|
}
|
||||||
return notes
|
|
||||||
})
|
})
|
||||||
|
if (unknownCount > 0) {
|
||||||
|
CSON.writeFileSync(
|
||||||
|
path.join(storage.path, 'boostnote.json'),
|
||||||
|
_.pick(storage, ['folders', 'version'])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return notes
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.then(function returnValue (notes) {
|
.then(function returnValue(notes) {
|
||||||
return {
|
return {
|
||||||
storage: newStorage,
|
storage: newStorage,
|
||||||
notes
|
notes
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ const path = require('path')
|
|||||||
* @param {String} dstPath
|
* @param {String} dstPath
|
||||||
* @return {Promise} an image path
|
* @return {Promise} an image path
|
||||||
*/
|
*/
|
||||||
function copyFile (srcPath, dstPath) {
|
function copyFile(srcPath, dstPath) {
|
||||||
if (!path.extname(dstPath)) {
|
if (!path.extname(dstPath)) {
|
||||||
dstPath = path.join(dstPath, path.basename(srcPath))
|
dstPath = path.join(dstPath, path.basename(srcPath))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const { findStorage } = require('browser/lib/findStorage')
|
|||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
function createFolder (storageKey, input) {
|
function createFolder(storageKey, input) {
|
||||||
let targetStorage
|
let targetStorage
|
||||||
try {
|
try {
|
||||||
if (input == null) throw new Error('No input found.')
|
if (input == null) throw new Error('No input found.')
|
||||||
@@ -34,26 +34,28 @@ function createFolder (storageKey, input) {
|
|||||||
return Promise.reject(e)
|
return Promise.reject(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolveStorageData(targetStorage)
|
return resolveStorageData(targetStorage).then(function createFolder(storage) {
|
||||||
.then(function createFolder (storage) {
|
let key = keygen()
|
||||||
let key = keygen()
|
while (storage.folders.some(folder => folder.key === key)) {
|
||||||
while (storage.folders.some((folder) => folder.key === key)) {
|
key = keygen()
|
||||||
key = keygen()
|
}
|
||||||
}
|
const newFolder = {
|
||||||
const newFolder = {
|
key,
|
||||||
key,
|
color: input.color,
|
||||||
color: input.color,
|
name: input.name
|
||||||
name: input.name
|
}
|
||||||
}
|
|
||||||
|
|
||||||
storage.folders.push(newFolder)
|
storage.folders.push(newFolder)
|
||||||
|
|
||||||
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version']))
|
CSON.writeFileSync(
|
||||||
|
path.join(storage.path, 'boostnote.json'),
|
||||||
|
_.pick(storage, ['folders', 'version'])
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
storage
|
storage
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = createFolder
|
module.exports = createFolder
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ const path = require('path')
|
|||||||
const CSON = require('@rokt33r/season')
|
const CSON = require('@rokt33r/season')
|
||||||
const { findStorage } = require('browser/lib/findStorage')
|
const { findStorage } = require('browser/lib/findStorage')
|
||||||
|
|
||||||
function validateInput (input) {
|
function validateInput(input) {
|
||||||
if (!_.isArray(input.tags)) input.tags = []
|
if (!_.isArray(input.tags)) input.tags = []
|
||||||
input.tags = input.tags.filter((tag) => _.isString(tag) && tag.trim().length > 0)
|
input.tags = input.tags.filter(
|
||||||
|
tag => _.isString(tag) && tag.trim().length > 0
|
||||||
|
)
|
||||||
if (!_.isString(input.title)) input.title = ''
|
if (!_.isString(input.title)) input.title = ''
|
||||||
input.isStarred = !!input.isStarred
|
input.isStarred = !!input.isStarred
|
||||||
input.isTrashed = !!input.isTrashed
|
input.isTrashed = !!input.isTrashed
|
||||||
@@ -21,20 +23,24 @@ function validateInput (input) {
|
|||||||
case 'SNIPPET_NOTE':
|
case 'SNIPPET_NOTE':
|
||||||
if (!_.isString(input.description)) input.description = ''
|
if (!_.isString(input.description)) input.description = ''
|
||||||
if (!_.isArray(input.snippets)) {
|
if (!_.isArray(input.snippets)) {
|
||||||
input.snippets = [{
|
input.snippets = [
|
||||||
name: '',
|
{
|
||||||
mode: 'text',
|
name: '',
|
||||||
content: '',
|
mode: 'text',
|
||||||
linesHighlighted: []
|
content: '',
|
||||||
}]
|
linesHighlighted: []
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error('Invalid type: only MARKDOWN_NOTE and SNIPPET_NOTE are available.')
|
throw new Error(
|
||||||
|
'Invalid type: only MARKDOWN_NOTE and SNIPPET_NOTE are available.'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNote (storageKey, input) {
|
function createNote(storageKey, input) {
|
||||||
let targetStorage
|
let targetStorage
|
||||||
try {
|
try {
|
||||||
if (input == null) throw new Error('No input found.')
|
if (input == null) throw new Error('No input found.')
|
||||||
@@ -47,13 +53,13 @@ function createNote (storageKey, input) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return resolveStorageData(targetStorage)
|
return resolveStorageData(targetStorage)
|
||||||
.then(function checkFolderExists (storage) {
|
.then(function checkFolderExists(storage) {
|
||||||
if (_.find(storage.folders, {key: input.folder}) == null) {
|
if (_.find(storage.folders, { key: input.folder }) == null) {
|
||||||
throw new Error('Target folder doesn\'t exist.')
|
throw new Error("Target folder doesn't exist.")
|
||||||
}
|
}
|
||||||
return storage
|
return storage
|
||||||
})
|
})
|
||||||
.then(function saveNote (storage) {
|
.then(function saveNote(storage) {
|
||||||
let key = keygen(true)
|
let key = keygen(true)
|
||||||
let isUnique = false
|
let isUnique = false
|
||||||
while (!isUnique) {
|
while (!isUnique) {
|
||||||
@@ -68,7 +74,8 @@ function createNote (storageKey, input) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const noteData = Object.assign({},
|
const noteData = Object.assign(
|
||||||
|
{},
|
||||||
{
|
{
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date()
|
updatedAt: new Date()
|
||||||
@@ -77,9 +84,13 @@ function createNote (storageKey, input) {
|
|||||||
{
|
{
|
||||||
key,
|
key,
|
||||||
storage: storageKey
|
storage: storageKey
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
CSON.writeFileSync(path.join(storage.path, 'notes', key + '.cson'), _.omit(noteData, ['key', 'storage']))
|
CSON.writeFileSync(
|
||||||
|
path.join(storage.path, 'notes', key + '.cson'),
|
||||||
|
_.omit(noteData, ['key', 'storage'])
|
||||||
|
)
|
||||||
|
|
||||||
return noteData
|
return noteData
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ const createNote = require('./createNote')
|
|||||||
import { push } from 'connected-react-router'
|
import { push } from 'connected-react-router'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
|
||||||
function validateUrl (str) {
|
function validateUrl(str) {
|
||||||
if (/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(str)) {
|
if (
|
||||||
|
/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
|
||||||
|
str
|
||||||
|
)
|
||||||
|
) {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@@ -15,25 +19,33 @@ function validateUrl (str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ERROR_MESSAGES = {
|
const ERROR_MESSAGES = {
|
||||||
ENOTFOUND: 'URL not found. Please check the URL, or your internet connection and try again.',
|
ENOTFOUND:
|
||||||
VALIDATION_ERROR: 'Please check if the URL follows this format: https://www.google.com',
|
'URL not found. Please check the URL, or your internet connection and try again.',
|
||||||
|
VALIDATION_ERROR:
|
||||||
|
'Please check if the URL follows this format: https://www.google.com',
|
||||||
UNEXPECTED: 'Unexpected error! Please check console for details!'
|
UNEXPECTED: 'Unexpected error! Please check console for details!'
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNoteFromUrl (url, storage, folder, dispatch = null, location = null) {
|
function createNoteFromUrl(
|
||||||
|
url,
|
||||||
|
storage,
|
||||||
|
folder,
|
||||||
|
dispatch = null,
|
||||||
|
location = null
|
||||||
|
) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const td = createTurndownService()
|
const td = createTurndownService()
|
||||||
|
|
||||||
if (!validateUrl(url)) {
|
if (!validateUrl(url)) {
|
||||||
reject({result: false, error: ERROR_MESSAGES.VALIDATION_ERROR})
|
reject({ result: false, error: ERROR_MESSAGES.VALIDATION_ERROR })
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = url.startsWith('https') ? https : http
|
const request = url.startsWith('https') ? https : http
|
||||||
|
|
||||||
const req = request.request(url, (res) => {
|
const req = request.request(url, res => {
|
||||||
let data = ''
|
let data = ''
|
||||||
|
|
||||||
res.on('data', (chunk) => {
|
res.on('data', chunk => {
|
||||||
data += chunk
|
data += chunk
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -46,20 +58,21 @@ function createNoteFromUrl (url, storage, folder, dispatch = null, location = nu
|
|||||||
folder: folder,
|
folder: folder,
|
||||||
title: '',
|
title: '',
|
||||||
content: markdownHTML
|
content: markdownHTML
|
||||||
})
|
}).then(note => {
|
||||||
.then((note) => {
|
|
||||||
const noteHash = note.key
|
const noteHash = note.key
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
})
|
})
|
||||||
dispatch(push({
|
dispatch(
|
||||||
pathname: location.pathname,
|
push({
|
||||||
query: {key: noteHash}
|
pathname: location.pathname,
|
||||||
}))
|
query: { key: noteHash }
|
||||||
|
})
|
||||||
|
)
|
||||||
ee.emit('list:jump', noteHash)
|
ee.emit('list:jump', noteHash)
|
||||||
ee.emit('detail:focus')
|
ee.emit('detail:focus')
|
||||||
resolve({result: true, error: null})
|
resolve({ result: true, error: null })
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
createNote(storage, {
|
createNote(storage, {
|
||||||
@@ -67,16 +80,19 @@ function createNoteFromUrl (url, storage, folder, dispatch = null, location = nu
|
|||||||
folder: folder,
|
folder: folder,
|
||||||
title: '',
|
title: '',
|
||||||
content: markdownHTML
|
content: markdownHTML
|
||||||
}).then((note) => {
|
}).then(note => {
|
||||||
resolve({result: true, note, error: null})
|
resolve({ result: true, note, error: null })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
req.on('error', (e) => {
|
req.on('error', e => {
|
||||||
console.error('error in parsing URL', e)
|
console.error('error in parsing URL', e)
|
||||||
reject({result: false, error: ERROR_MESSAGES[e.code] || ERROR_MESSAGES.UNEXPECTED})
|
reject({
|
||||||
|
result: false,
|
||||||
|
error: ERROR_MESSAGES[e.code] || ERROR_MESSAGES.UNEXPECTED
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
req.end()
|
req.end()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import crypto from 'crypto'
|
|||||||
import consts from 'browser/lib/consts'
|
import consts from 'browser/lib/consts'
|
||||||
import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet'
|
import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet'
|
||||||
|
|
||||||
function createSnippet (snippetFile) {
|
function createSnippet(snippetFile) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const newSnippet = {
|
const newSnippet = {
|
||||||
id: crypto.randomBytes(16).toString('hex'),
|
id: crypto.randomBytes(16).toString('hex'),
|
||||||
@@ -12,15 +12,21 @@ function createSnippet (snippetFile) {
|
|||||||
content: '',
|
content: '',
|
||||||
linesHighlighted: []
|
linesHighlighted: []
|
||||||
}
|
}
|
||||||
fetchSnippet(null, snippetFile).then((snippets) => {
|
fetchSnippet(null, snippetFile)
|
||||||
snippets.push(newSnippet)
|
.then(snippets => {
|
||||||
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
|
snippets.push(newSnippet)
|
||||||
if (err) reject(err)
|
fs.writeFile(
|
||||||
resolve(newSnippet)
|
snippetFile || consts.SNIPPET_FILE,
|
||||||
|
JSON.stringify(snippets, null, 4),
|
||||||
|
err => {
|
||||||
|
if (err) reject(err)
|
||||||
|
resolve(newSnippet)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
reject(err)
|
||||||
})
|
})
|
||||||
}).catch((err) => {
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const deleteSingleNote = require('./deleteNote')
|
|||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
function deleteFolder (storageKey, folderKey) {
|
function deleteFolder(storageKey, folderKey) {
|
||||||
let targetStorage
|
let targetStorage
|
||||||
try {
|
try {
|
||||||
targetStorage = findStorage(storageKey)
|
targetStorage = findStorage(storageKey)
|
||||||
@@ -27,35 +27,36 @@ function deleteFolder (storageKey, folderKey) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return resolveStorageData(targetStorage)
|
return resolveStorageData(targetStorage)
|
||||||
.then(function assignNotes (storage) {
|
.then(function assignNotes(storage) {
|
||||||
return resolveStorageNotes(storage)
|
return resolveStorageNotes(storage).then(notes => {
|
||||||
.then((notes) => {
|
return {
|
||||||
return {
|
storage,
|
||||||
storage,
|
notes
|
||||||
notes
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.then(function deleteFolderAndNotes (data) {
|
.then(function deleteFolderAndNotes(data) {
|
||||||
const { storage, notes } = data
|
const { storage, notes } = data
|
||||||
storage.folders = storage.folders
|
storage.folders = storage.folders.filter(function excludeTargetFolder(
|
||||||
.filter(function excludeTargetFolder (folder) {
|
folder
|
||||||
return folder.key !== folderKey
|
) {
|
||||||
})
|
return folder.key !== folderKey
|
||||||
|
})
|
||||||
|
|
||||||
const targetNotes = notes.filter(function filterTargetNotes (note) {
|
const targetNotes = notes.filter(function filterTargetNotes(note) {
|
||||||
return note.folder === folderKey
|
return note.folder === folderKey
|
||||||
})
|
})
|
||||||
|
|
||||||
const deleteAllNotes = targetNotes
|
const deleteAllNotes = targetNotes.map(function deleteNote(note) {
|
||||||
.map(function deleteNote (note) {
|
return deleteSingleNote(storageKey, note.key)
|
||||||
return deleteSingleNote(storageKey, note.key)
|
})
|
||||||
})
|
return Promise.all(deleteAllNotes).then(() => storage)
|
||||||
return Promise.all(deleteAllNotes)
|
|
||||||
.then(() => storage)
|
|
||||||
})
|
})
|
||||||
.then(function (storage) {
|
.then(function(storage) {
|
||||||
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version']))
|
CSON.writeFileSync(
|
||||||
|
path.join(storage.path, 'boostnote.json'),
|
||||||
|
_.pick(storage, ['folders', 'version'])
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
storage,
|
storage,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const sander = require('sander')
|
|||||||
const attachmentManagement = require('./attachmentManagement')
|
const attachmentManagement = require('./attachmentManagement')
|
||||||
const { findStorage } = require('browser/lib/findStorage')
|
const { findStorage } = require('browser/lib/findStorage')
|
||||||
|
|
||||||
function deleteNote (storageKey, noteKey) {
|
function deleteNote(storageKey, noteKey) {
|
||||||
let targetStorage
|
let targetStorage
|
||||||
try {
|
try {
|
||||||
targetStorage = findStorage(storageKey)
|
targetStorage = findStorage(storageKey)
|
||||||
@@ -13,7 +13,7 @@ function deleteNote (storageKey, noteKey) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return resolveStorageData(targetStorage)
|
return resolveStorageData(targetStorage)
|
||||||
.then(function deleteNoteFile (storage) {
|
.then(function deleteNoteFile(storage) {
|
||||||
const notePath = path.join(storage.path, 'notes', noteKey + '.cson')
|
const notePath = path.join(storage.path, 'notes', noteKey + '.cson')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -26,8 +26,11 @@ function deleteNote (storageKey, noteKey) {
|
|||||||
storageKey
|
storageKey
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(function deleteAttachments (storageInfo) {
|
.then(function deleteAttachments(storageInfo) {
|
||||||
attachmentManagement.deleteAttachmentFolder(storageInfo.storageKey, storageInfo.noteKey)
|
attachmentManagement.deleteAttachmentFolder(
|
||||||
|
storageInfo.storageKey,
|
||||||
|
storageInfo.noteKey
|
||||||
|
)
|
||||||
return storageInfo
|
return storageInfo
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,20 @@ import fs from 'fs'
|
|||||||
import consts from 'browser/lib/consts'
|
import consts from 'browser/lib/consts'
|
||||||
import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet'
|
import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet'
|
||||||
|
|
||||||
function deleteSnippet (snippet, snippetFile) {
|
function deleteSnippet(snippet, snippetFile) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fetchSnippet(null, snippetFile).then((snippets) => {
|
fetchSnippet(null, snippetFile).then(snippets => {
|
||||||
snippets = snippets.filter(currentSnippet => currentSnippet.id !== snippet.id)
|
snippets = snippets.filter(
|
||||||
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
|
currentSnippet => currentSnippet.id !== snippet.id
|
||||||
if (err) reject(err)
|
)
|
||||||
resolve(snippet)
|
fs.writeFile(
|
||||||
})
|
snippetFile || consts.SNIPPET_FILE,
|
||||||
|
JSON.stringify(snippets, null, 4),
|
||||||
|
err => {
|
||||||
|
if (err) reject(err)
|
||||||
|
resolve(snippet)
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import * as path from 'path'
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function exportFolder (storageKey, folderKey, fileType, exportDir) {
|
function exportFolder(storageKey, folderKey, fileType, exportDir) {
|
||||||
let targetStorage
|
let targetStorage
|
||||||
try {
|
try {
|
||||||
targetStorage = findStorage(storageKey)
|
targetStorage = findStorage(storageKey)
|
||||||
@@ -31,24 +31,38 @@ function exportFolder (storageKey, folderKey, fileType, exportDir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return resolveStorageData(targetStorage)
|
return resolveStorageData(targetStorage)
|
||||||
.then(function assignNotes (storage) {
|
.then(function assignNotes(storage) {
|
||||||
return resolveStorageNotes(storage)
|
return resolveStorageNotes(storage).then(notes => {
|
||||||
.then((notes) => {
|
return {
|
||||||
return {
|
storage,
|
||||||
storage,
|
notes
|
||||||
notes
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.then(function exportNotes (data) {
|
.then(function exportNotes(data) {
|
||||||
const { storage, notes } = data
|
const { storage, notes } = data
|
||||||
|
|
||||||
return Promise.all(notes
|
return Promise.all(
|
||||||
.filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE')
|
notes
|
||||||
.map(note => {
|
.filter(
|
||||||
const notePath = path.join(exportDir, `${filenamify(note.title, {replacement: '_'})}.${fileType}`)
|
note =>
|
||||||
return exportNote(note.key, storage.path, note.content, notePath, null)
|
note.folder === folderKey &&
|
||||||
})
|
note.isTrashed === false &&
|
||||||
|
note.type === 'MARKDOWN_NOTE'
|
||||||
|
)
|
||||||
|
.map(note => {
|
||||||
|
const notePath = path.join(
|
||||||
|
exportDir,
|
||||||
|
`${filenamify(note.title, { replacement: '_' })}.${fileType}`
|
||||||
|
)
|
||||||
|
return exportNote(
|
||||||
|
note.key,
|
||||||
|
storage.path,
|
||||||
|
note.content,
|
||||||
|
notePath,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
})
|
||||||
).then(() => ({
|
).then(() => ({
|
||||||
storage,
|
storage,
|
||||||
folderKey,
|
folderKey,
|
||||||
|
|||||||
@@ -19,8 +19,16 @@ const attachmentManagement = require('./attachmentManagement')
|
|||||||
* @param {function} outputFormatter
|
* @param {function} outputFormatter
|
||||||
* @return {Promise.<*[]>}
|
* @return {Promise.<*[]>}
|
||||||
*/
|
*/
|
||||||
function exportNote (nodeKey, storageKey, noteContent, targetPath, outputFormatter) {
|
function exportNote(
|
||||||
const storagePath = path.isAbsolute(storageKey) ? storageKey : findStorage(storageKey).path
|
nodeKey,
|
||||||
|
storageKey,
|
||||||
|
noteContent,
|
||||||
|
targetPath,
|
||||||
|
outputFormatter
|
||||||
|
) {
|
||||||
|
const storagePath = path.isAbsolute(storageKey)
|
||||||
|
? storageKey
|
||||||
|
: findStorage(storageKey).path
|
||||||
const exportTasks = []
|
const exportTasks = []
|
||||||
|
|
||||||
if (!storagePath) {
|
if (!storagePath) {
|
||||||
@@ -50,18 +58,19 @@ function exportNote (nodeKey, storageKey, noteContent, targetPath, outputFormatt
|
|||||||
|
|
||||||
const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath))
|
const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath))
|
||||||
|
|
||||||
return Promise.all(tasks.map((task) => copyFile(task.src, task.dst)))
|
return Promise.all(tasks.map(task => copyFile(task.src, task.dst)))
|
||||||
.then(() => exportedData)
|
.then(() => exportedData)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
return saveToFile(data, targetPath)
|
return saveToFile(data, targetPath)
|
||||||
}).catch((err) => {
|
})
|
||||||
rollbackExport(tasks)
|
.catch(err => {
|
||||||
throw err
|
rollbackExport(tasks)
|
||||||
})
|
throw err
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareTasks (tasks, storagePath, targetPath) {
|
function prepareTasks(tasks, storagePath, targetPath) {
|
||||||
return tasks.map((task) => {
|
return tasks.map(task => {
|
||||||
if (!path.isAbsolute(task.src)) {
|
if (!path.isAbsolute(task.src)) {
|
||||||
task.src = path.join(storagePath, task.src)
|
task.src = path.join(storagePath, task.src)
|
||||||
}
|
}
|
||||||
@@ -74,9 +83,9 @@ function prepareTasks (tasks, storagePath, targetPath) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveToFile (data, filename) {
|
function saveToFile(data, filename) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fs.writeFile(filename, data, (err) => {
|
fs.writeFile(filename, data, err => {
|
||||||
if (err) return reject(err)
|
if (err) return reject(err)
|
||||||
|
|
||||||
resolve(filename)
|
resolve(filename)
|
||||||
@@ -88,9 +97,9 @@ function saveToFile (data, filename) {
|
|||||||
* Remove exported files
|
* Remove exported files
|
||||||
* @param tasks Array of copy task objects. Object consists of two mandatory fields – `src` and `dst`
|
* @param tasks Array of copy task objects. Object consists of two mandatory fields – `src` and `dst`
|
||||||
*/
|
*/
|
||||||
function rollbackExport (tasks) {
|
function rollbackExport(tasks) {
|
||||||
const folders = new Set()
|
const folders = new Set()
|
||||||
tasks.forEach((task) => {
|
tasks.forEach(task => {
|
||||||
let fullpath = task.dst
|
let fullpath = task.dst
|
||||||
|
|
||||||
if (!path.extname(task.dst)) {
|
if (!path.extname(task.dst)) {
|
||||||
@@ -103,7 +112,7 @@ function rollbackExport (tasks) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
folders.forEach((folder) => {
|
folders.forEach(folder => {
|
||||||
if (fs.readdirSync(folder).length === 0) {
|
if (fs.readdirSync(folder).length === 0) {
|
||||||
fs.rmdir(folder)
|
fs.rmdir(folder)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import * as fs from 'fs'
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function exportStorage (storageKey, fileType, exportDir) {
|
function exportStorage(storageKey, fileType, exportDir) {
|
||||||
let targetStorage
|
let targetStorage
|
||||||
try {
|
try {
|
||||||
targetStorage = findStorage(storageKey)
|
targetStorage = findStorage(storageKey)
|
||||||
@@ -29,14 +29,17 @@ function exportStorage (storageKey, fileType, exportDir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return resolveStorageData(targetStorage)
|
return resolveStorageData(targetStorage)
|
||||||
.then(storage => (
|
.then(storage =>
|
||||||
resolveStorageNotes(storage).then(notes => ({storage, notes}))
|
resolveStorageNotes(storage).then(notes => ({ storage, notes }))
|
||||||
))
|
)
|
||||||
.then(function exportNotes (data) {
|
.then(function exportNotes(data) {
|
||||||
const { storage, notes } = data
|
const { storage, notes } = data
|
||||||
const folderNamesMapping = {}
|
const folderNamesMapping = {}
|
||||||
storage.folders.forEach(folder => {
|
storage.folders.forEach(folder => {
|
||||||
const folderExportedDir = path.join(exportDir, filenamify(folder.name, {replacement: '_'}))
|
const folderExportedDir = path.join(
|
||||||
|
exportDir,
|
||||||
|
filenamify(folder.name, { replacement: '_' })
|
||||||
|
)
|
||||||
folderNamesMapping[folder.key] = folderExportedDir
|
folderNamesMapping[folder.key] = folderExportedDir
|
||||||
// make sure directory exists
|
// make sure directory exists
|
||||||
try {
|
try {
|
||||||
@@ -47,7 +50,9 @@ function exportStorage (storageKey, fileType, exportDir) {
|
|||||||
.filter(note => !note.isTrashed && note.type === 'MARKDOWN_NOTE')
|
.filter(note => !note.isTrashed && note.type === 'MARKDOWN_NOTE')
|
||||||
.forEach(markdownNote => {
|
.forEach(markdownNote => {
|
||||||
const folderExportedDir = folderNamesMapping[markdownNote.folder]
|
const folderExportedDir = folderNamesMapping[markdownNote.folder]
|
||||||
const snippetName = `${filenamify(markdownNote.title, {replacement: '_'})}.${fileType}`
|
const snippetName = `${filenamify(markdownNote.title, {
|
||||||
|
replacement: '_'
|
||||||
|
})}.${fileType}`
|
||||||
const notePath = path.join(folderExportedDir, snippetName)
|
const notePath = path.join(folderExportedDir, snippetName)
|
||||||
fs.writeFileSync(notePath, markdownNote.content)
|
fs.writeFileSync(notePath, markdownNote.content)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import consts from 'browser/lib/consts'
|
import consts from 'browser/lib/consts'
|
||||||
|
|
||||||
function fetchSnippet (id, snippetFile) {
|
function fetchSnippet(id, snippetFile) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fs.readFile(snippetFile || consts.SNIPPET_FILE, 'utf8', (err, data) => {
|
fs.readFile(snippetFile || consts.SNIPPET_FILE, 'utf8', (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -9,7 +9,9 @@ function fetchSnippet (id, snippetFile) {
|
|||||||
}
|
}
|
||||||
const snippets = JSON.parse(data)
|
const snippets = JSON.parse(data)
|
||||||
if (id) {
|
if (id) {
|
||||||
const snippet = snippets.find(snippet => { return snippet.id === id })
|
const snippet = snippets.find(snippet => {
|
||||||
|
return snippet.id === id
|
||||||
|
})
|
||||||
resolve(snippet)
|
resolve(snippet)
|
||||||
}
|
}
|
||||||
resolve(snippets)
|
resolve(snippets)
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ const CSON = require('@rokt33r/season')
|
|||||||
* 3. empty directory
|
* 3. empty directory
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function init () {
|
function init() {
|
||||||
const fetchStorages = function () {
|
const fetchStorages = function() {
|
||||||
let rawStorages
|
let rawStorages
|
||||||
try {
|
try {
|
||||||
rawStorages = JSON.parse(window.localStorage.getItem('storages'))
|
rawStorages = JSON.parse(window.localStorage.getItem('storages'))
|
||||||
@@ -34,44 +34,50 @@ function init () {
|
|||||||
rawStorages = []
|
rawStorages = []
|
||||||
window.localStorage.setItem('storages', JSON.stringify(rawStorages))
|
window.localStorage.setItem('storages', JSON.stringify(rawStorages))
|
||||||
}
|
}
|
||||||
return Promise.all(rawStorages
|
return Promise.all(rawStorages.map(resolveStorageData))
|
||||||
.map(resolveStorageData))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchNotes = function (storages) {
|
const fetchNotes = function(storages) {
|
||||||
const findNotesFromEachStorage = storages
|
const findNotesFromEachStorage = storages
|
||||||
.filter(storage => fs.existsSync(storage.path))
|
.filter(storage => fs.existsSync(storage.path))
|
||||||
.map((storage) => {
|
.map(storage => {
|
||||||
return resolveStorageNotes(storage)
|
return resolveStorageNotes(storage).then(notes => {
|
||||||
.then((notes) => {
|
let unknownCount = 0
|
||||||
let unknownCount = 0
|
notes.forEach(note => {
|
||||||
notes.forEach((note) => {
|
if (
|
||||||
if (note && !storage.folders.some((folder) => note.folder === folder.key)) {
|
note &&
|
||||||
unknownCount++
|
!storage.folders.some(folder => note.folder === folder.key)
|
||||||
storage.folders.push({
|
) {
|
||||||
key: note.folder,
|
unknownCount++
|
||||||
color: consts.FOLDER_COLORS[(unknownCount - 1) % 7],
|
storage.folders.push({
|
||||||
name: 'Unknown ' + unknownCount
|
key: note.folder,
|
||||||
})
|
color: consts.FOLDER_COLORS[(unknownCount - 1) % 7],
|
||||||
}
|
name: 'Unknown ' + unknownCount
|
||||||
})
|
})
|
||||||
if (unknownCount > 0) {
|
|
||||||
try {
|
|
||||||
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version']))
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Error writting boostnote.json: ' + e + ' from init.js')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return notes
|
|
||||||
})
|
})
|
||||||
|
if (unknownCount > 0) {
|
||||||
|
try {
|
||||||
|
CSON.writeFileSync(
|
||||||
|
path.join(storage.path, 'boostnote.json'),
|
||||||
|
_.pick(storage, ['folders', 'version'])
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(
|
||||||
|
'Error writting boostnote.json: ' + e + ' from init.js'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return notes
|
||||||
|
})
|
||||||
})
|
})
|
||||||
return Promise.all(findNotesFromEachStorage)
|
return Promise.all(findNotesFromEachStorage)
|
||||||
.then(function concatNoteGroup (noteGroups) {
|
.then(function concatNoteGroup(noteGroups) {
|
||||||
return noteGroups.reduce(function (sum, group) {
|
return noteGroups.reduce(function(sum, group) {
|
||||||
return sum.concat(group)
|
return sum.concat(group)
|
||||||
}, [])
|
}, [])
|
||||||
})
|
})
|
||||||
.then(function returnData (notes) {
|
.then(function returnData(notes) {
|
||||||
return {
|
return {
|
||||||
storages,
|
storages,
|
||||||
notes
|
notes
|
||||||
@@ -80,12 +86,11 @@ function init () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(fetchStorages())
|
return Promise.resolve(fetchStorages())
|
||||||
.then((storages) => {
|
.then(storages => {
|
||||||
return storages
|
return storages.filter(storage => {
|
||||||
.filter((storage) => {
|
if (!_.isObject(storage)) return false
|
||||||
if (!_.isObject(storage)) return false
|
return true
|
||||||
return true
|
})
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.then(fetchNotes)
|
.then(fetchNotes)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,102 +6,111 @@ const CSON = require('@rokt33r/season')
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const sander = require('sander')
|
const sander = require('sander')
|
||||||
|
|
||||||
function migrateFromV5Storage (storageKey, data) {
|
function migrateFromV5Storage(storageKey, data) {
|
||||||
let targetStorage
|
let targetStorage
|
||||||
try {
|
try {
|
||||||
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
if (!_.isArray(cachedStorageList))
|
||||||
|
throw new Error("Target storage doesn't exist.")
|
||||||
|
|
||||||
targetStorage = _.find(cachedStorageList, {key: storageKey})
|
targetStorage = _.find(cachedStorageList, { key: storageKey })
|
||||||
if (targetStorage == null) throw new Error('Target storage doesn\'t exist.')
|
if (targetStorage == null) throw new Error("Target storage doesn't exist.")
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Promise.reject(e)
|
return Promise.reject(e)
|
||||||
}
|
}
|
||||||
return resolveStorageData(targetStorage)
|
return resolveStorageData(targetStorage).then(function(storage) {
|
||||||
.then(function (storage) {
|
return importAll(storage, data)
|
||||||
return importAll(storage, data)
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function importAll (storage, data) {
|
function importAll(storage, data) {
|
||||||
const oldArticles = data.articles
|
const oldArticles = data.articles
|
||||||
const notes = []
|
const notes = []
|
||||||
data.folders
|
data.folders.forEach(function(oldFolder) {
|
||||||
.forEach(function (oldFolder) {
|
let folderKey = keygen()
|
||||||
let folderKey = keygen()
|
while (storage.folders.some(folder => folder.key === folderKey)) {
|
||||||
while (storage.folders.some((folder) => folder.key === folderKey)) {
|
folderKey = keygen()
|
||||||
folderKey = keygen()
|
}
|
||||||
}
|
const newFolder = {
|
||||||
const newFolder = {
|
key: folderKey,
|
||||||
key: folderKey,
|
name: oldFolder.name,
|
||||||
name: oldFolder.name,
|
color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7]
|
||||||
color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7]
|
}
|
||||||
}
|
|
||||||
|
|
||||||
storage.folders.push(newFolder)
|
storage.folders.push(newFolder)
|
||||||
|
|
||||||
const articles = oldArticles.filter((article) => article.FolderKey === oldFolder.key)
|
const articles = oldArticles.filter(
|
||||||
articles.forEach((article) => {
|
article => article.FolderKey === oldFolder.key
|
||||||
let noteKey = keygen()
|
)
|
||||||
let isUnique = false
|
articles.forEach(article => {
|
||||||
while (!isUnique) {
|
let noteKey = keygen()
|
||||||
try {
|
let isUnique = false
|
||||||
sander.statSync(path.join(storage.path, 'notes', noteKey + '.cson'))
|
while (!isUnique) {
|
||||||
noteKey = keygen()
|
try {
|
||||||
} catch (err) {
|
sander.statSync(path.join(storage.path, 'notes', noteKey + '.cson'))
|
||||||
if (err.code === 'ENOENT') {
|
noteKey = keygen()
|
||||||
isUnique = true
|
} catch (err) {
|
||||||
} else {
|
if (err.code === 'ENOENT') {
|
||||||
console.error('Failed to read `notes` directory.')
|
isUnique = true
|
||||||
throw err
|
} else {
|
||||||
}
|
console.error('Failed to read `notes` directory.')
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (article.mode === 'markdown') {
|
if (article.mode === 'markdown') {
|
||||||
const newNote = {
|
const newNote = {
|
||||||
tags: article.tags,
|
tags: article.tags,
|
||||||
createdAt: article.createdAt,
|
createdAt: article.createdAt,
|
||||||
updatedAt: article.updatedAt,
|
updatedAt: article.updatedAt,
|
||||||
folder: folderKey,
|
folder: folderKey,
|
||||||
storage: storage.key,
|
storage: storage.key,
|
||||||
type: 'MARKDOWN_NOTE',
|
type: 'MARKDOWN_NOTE',
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
title: article.title,
|
title: article.title,
|
||||||
content: '# ' + article.title + '\n\n' + article.content,
|
content: '# ' + article.title + '\n\n' + article.content,
|
||||||
key: noteKey,
|
key: noteKey,
|
||||||
linesHighlighted: article.linesHighlighted
|
linesHighlighted: article.linesHighlighted
|
||||||
}
|
}
|
||||||
notes.push(newNote)
|
notes.push(newNote)
|
||||||
} else {
|
} else {
|
||||||
const newNote = {
|
const newNote = {
|
||||||
tags: article.tags,
|
tags: article.tags,
|
||||||
createdAt: article.createdAt,
|
createdAt: article.createdAt,
|
||||||
updatedAt: article.updatedAt,
|
updatedAt: article.updatedAt,
|
||||||
folder: folderKey,
|
folder: folderKey,
|
||||||
storage: storage.key,
|
storage: storage.key,
|
||||||
type: 'SNIPPET_NOTE',
|
type: 'SNIPPET_NOTE',
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
title: article.title,
|
title: article.title,
|
||||||
description: article.title,
|
description: article.title,
|
||||||
key: noteKey,
|
key: noteKey,
|
||||||
snippets: [{
|
snippets: [
|
||||||
|
{
|
||||||
name: article.mode,
|
name: article.mode,
|
||||||
mode: article.mode,
|
mode: article.mode,
|
||||||
content: article.content,
|
content: article.content,
|
||||||
linesHighlighted: article.linesHighlighted
|
linesHighlighted: article.linesHighlighted
|
||||||
}]
|
}
|
||||||
}
|
]
|
||||||
notes.push(newNote)
|
|
||||||
}
|
}
|
||||||
})
|
notes.push(newNote)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
notes.forEach(function (note) {
|
|
||||||
CSON.writeFileSync(path.join(storage.path, 'notes', note.key + '.cson'), _.omit(note, ['storage', 'key']))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['version', 'folders']))
|
notes.forEach(function(note) {
|
||||||
|
CSON.writeFileSync(
|
||||||
|
path.join(storage.path, 'notes', note.key + '.cson'),
|
||||||
|
_.omit(note, ['storage', 'key'])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
CSON.writeFileSync(
|
||||||
|
path.join(storage.path, 'boostnote.json'),
|
||||||
|
_.pick(storage, ['version', 'folders'])
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
storage,
|
storage,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user