mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-12 17:26:17 +00:00
Merge branch 'master' into fix-autocomplete-codeblock
This commit is contained in:
2
.babelrc
2
.babelrc
@@ -7,7 +7,7 @@
|
|||||||
"test": {
|
"test": {
|
||||||
"presets": ["env" ,"react", "es2015"],
|
"presets": ["env" ,"react", "es2015"],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
[ "babel-plugin-webpack-alias", { "config": "${PWD}/webpack.config.js" } ]
|
[ "babel-plugin-webpack-alias", { "config": "<rootDir>/webpack.config.js" } ]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
.eslintrc
11
.eslintrc
@@ -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,12 +13,15 @@
|
|||||||
"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,
|
||||||
"localStorage": true,
|
"localStorage": true,
|
||||||
"fetch": true
|
"fetch": true,
|
||||||
|
"Image": true,
|
||||||
|
"MutationObserver": true
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"jest": true
|
"jest": true
|
||||||
|
|||||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
issuehunt: BoostIo/Boostnote
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,3 +10,5 @@ node_modules/*
|
|||||||
*.log
|
*.log
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
package-lock.json
|
||||||
|
config.json
|
||||||
|
|||||||
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"semi": false,
|
||||||
|
"jsxSingleQuote": true
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 7
|
- 8
|
||||||
script:
|
script:
|
||||||
- npm run lint && npm run test
|
- npm run lint && npm run test
|
||||||
- yarn jest
|
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@6.4 && grunt pre-build; fi'
|
||||||
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
|
|
||||||
after_success:
|
after_success:
|
||||||
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
|
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
|
||||||
-in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d
|
-in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ Let us know what is currently happening.
|
|||||||
|
|
||||||
Please include some **screenshots** with the **developer tools** open (console tab) when you report a bug.
|
Please include some **screenshots** with the **developer tools** open (console tab) when you report a bug.
|
||||||
|
|
||||||
If your issue is regarding Boostnote mobile, please open an issue in the Boostnote Mobile repo 👉 https://github.com/BoostIO/boostnote-mobile.
|
If your issue is regarding the new Boost Note.next, please open an issue in the new repo 👉 https://github.com/BoostIO/BoostNote.next/issues.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# Expected behavior
|
# Expected behavior
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Let us know what you think should happen!
|
Let us know what you think should happen.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# Steps to reproduce
|
# Steps to reproduce
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Please be thorough, issues we can reproduce are easier to fix!
|
Please be thorough, issues we can reproduce are easier to fix.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
1.
|
1.
|
||||||
@@ -26,8 +26,8 @@ Please be thorough, issues we can reproduce are easier to fix!
|
|||||||
|
|
||||||
# Environment
|
# Environment
|
||||||
|
|
||||||
- Version :
|
- Boostnote version: <!-- 0.x.x -->
|
||||||
- OS Version and name :
|
- OS version and name: <!-- Windows 10 / Ubuntu 18.04 / etc -->
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Love Boostnote? Please consider supporting us on IssueHunt:
|
Love Boostnote? Please consider supporting us on IssueHunt:
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -2,7 +2,7 @@ GPL-3.0
|
|||||||
|
|
||||||
Boostnote - an open source note-taking app made for programmers just like you.
|
Boostnote - an open source note-taking app made for programmers just like you.
|
||||||
|
|
||||||
Copyright (C) 2017 - 2018 BoostIO
|
Copyright (C) 2017 - 2019 BoostIO
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ Before submitting this PR, please make sure that:
|
|||||||
- You have read and understand the contributing.md
|
- You have read and understand the contributing.md
|
||||||
- You have checked docs/code_style.md for information on code style
|
- You have checked docs/code_style.md for information on code style
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Tell us what your PR does.
|
Tell us what your PR does.
|
||||||
Please attach a screenshot/ video/gif image describing your PR if possible.
|
Please attach a screenshot/ video/gif image describing your PR if possible.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Issue fixed
|
## Issue fixed
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Please list out all issue fixed with this PR here.
|
Please list out all issue fixed with this PR here.
|
||||||
-->
|
-->
|
||||||
@@ -20,6 +23,7 @@ your PR will be reviewed faster if we know exactly what it does.
|
|||||||
|
|
||||||
Change :white_circle: to :radio_button: in all the options that apply
|
Change :white_circle: to :radio_button: in all the options that apply
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Type of changes
|
## Type of changes
|
||||||
|
|
||||||
- :white_circle: Bug fix (Change that fixed an issue)
|
- :white_circle: Bug fix (Change that fixed an issue)
|
||||||
@@ -34,3 +38,5 @@ Change :white_circle: to :radio_button: in all the options that apply
|
|||||||
- :white_circle: I have written test for my code and it has been tested
|
- :white_circle: I have written test for my code and it has been tested
|
||||||
- :white_circle: All existing tests have been passed
|
- :white_circle: All existing tests have been passed
|
||||||
- :white_circle: I have attached a screenshot/video to visualize my change if possible
|
- :white_circle: I have attached a screenshot/video to visualize my change if possible
|
||||||
|
- :white_circle: This PR will modify the UI or affects the UX
|
||||||
|
- :white_circle: This PR will add/update/delete a keybinding
|
||||||
|
|||||||
@@ -2,39 +2,48 @@ import PropTypes from 'prop-types'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
|
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'
|
||||||
import crypto from 'crypto'
|
|
||||||
import consts from 'browser/lib/consts'
|
import { isMarkdownTitleURL } from 'browser/lib/utils'
|
||||||
import styles from '../components/CodeEditor.styl'
|
import styles from '../components/CodeEditor.styl'
|
||||||
import fs from 'fs'
|
|
||||||
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')
|
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
|
||||||
import TurndownService from 'turndown'
|
.buildEditorContextMenu
|
||||||
|
import { createTurndownService } from '../lib/turndown'
|
||||||
|
import { languageMaps } from '../lib/CMLanguageList'
|
||||||
|
import snippetManager from '../lib/SnippetManager'
|
||||||
import {
|
import {
|
||||||
gfm
|
generateInEditor,
|
||||||
} from 'turndown-plugin-gfm'
|
tocExistsInEditor
|
||||||
|
} from 'browser/lib/markdown-toc-generator'
|
||||||
|
import markdownlint from 'markdownlint'
|
||||||
|
import Jsonlint from 'jsonlint-mod'
|
||||||
|
import { DEFAULT_CONFIG } from '../main/lib/ConfigManager'
|
||||||
|
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
|
||||||
|
? rulers.map(ruler => ({
|
||||||
column: 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 {
|
||||||
@@ -45,11 +54,17 @@ export default class CodeEditor extends React.Component {
|
|||||||
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
|
||||||
|
)
|
||||||
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
|
||||||
@@ -61,17 +76,15 @@ 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 { storageKey, noteKey } = this.props
|
||||||
const {
|
if (this.props.deleteUnusedAttachments === true) {
|
||||||
storageKey,
|
debouncedDeletionOfAttachments(
|
||||||
noteKey
|
|
||||||
} = this.props
|
|
||||||
attachmentManagement.deleteAttachmentsNotPresentInNote(
|
|
||||||
this.editor.getValue(),
|
this.editor.getValue(),
|
||||||
storageKey,
|
storageKey,
|
||||||
noteKey
|
noteKey
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
this.pasteHandler = (editor, e) => {
|
this.pasteHandler = (editor, e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
@@ -83,6 +96,8 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.searchHandler = (e, msg) => this.handleSearch(msg)
|
this.searchHandler = (e, msg) => this.handleSearch(msg)
|
||||||
this.searchState = null
|
this.searchState = null
|
||||||
this.scrollToLineHandeler = this.scrollToLine.bind(this)
|
this.scrollToLineHandeler = this.scrollToLine.bind(this)
|
||||||
|
this.getCodeEditorLintConfig = this.getCodeEditorLintConfig.bind(this)
|
||||||
|
this.validatorOfMarkdown = this.validatorOfMarkdown.bind(this)
|
||||||
|
|
||||||
this.formatTable = () => this.handleFormatTable()
|
this.formatTable = () => this.handleFormatTable()
|
||||||
|
|
||||||
@@ -97,7 +112,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
this.editorActivityHandler = () => this.handleEditorActivity()
|
this.editorActivityHandler = () => this.handleEditorActivity()
|
||||||
|
|
||||||
this.turndownService = new TurndownService()
|
this.turndownService = createTurndownService()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearch(msg) {
|
handleSearch(msg) {
|
||||||
@@ -105,7 +120,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
const component = this
|
const component = this
|
||||||
|
|
||||||
if (component.searchState) cm.removeOverlay(component.searchState)
|
if (component.searchState) cm.removeOverlay(component.searchState)
|
||||||
if (msg.length < 3) return
|
if (msg.length < 1) return
|
||||||
|
|
||||||
cm.operation(function() {
|
cm.operation(function() {
|
||||||
component.searchState = makeOverlay(msg, 'searching')
|
component.searchState = makeOverlay(msg, 'searching')
|
||||||
@@ -135,9 +150,11 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleFormatTable() {
|
handleFormatTable() {
|
||||||
this.tableEditor.formatAll(options({
|
this.tableEditor.formatAll(
|
||||||
|
options({
|
||||||
textWidthOptions: {}
|
textWidthOptions: {}
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEditorActivity() {
|
handleEditorActivity() {
|
||||||
@@ -148,7 +165,8 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
updateDefaultKeyMap() {
|
updateDefaultKeyMap() {
|
||||||
const { hotkey } = this.props
|
const { hotkey } = this.props
|
||||||
const expandSnippet = this.expandSnippet.bind(this)
|
const self = this
|
||||||
|
const expandSnippet = snippetManager.expandSnippet
|
||||||
|
|
||||||
this.defaultKeyMap = CodeMirror.normalizeKeyMap({
|
this.defaultKeyMap = CodeMirror.normalizeKeyMap({
|
||||||
Tab: function(cm) {
|
Tab: function(cm) {
|
||||||
@@ -168,14 +186,16 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
cm.execCommand('goLineEnd')
|
cm.execCommand('goLineEnd')
|
||||||
} else if (
|
} else if (
|
||||||
!charBeforeCursor.match(/\t|\s|\r|\n/) &&
|
!charBeforeCursor.match(/\t|\s|\r|\n|\$/) &&
|
||||||
cursor.ch > 1
|
cursor.ch > 1
|
||||||
) {
|
) {
|
||||||
// text expansion on tab key if the char before is alphabet
|
// text expansion on tab key if the char before is alphabet
|
||||||
const snippets = JSON.parse(
|
const wordBeforeCursor = self.getWordBeforeCursor(
|
||||||
fs.readFileSync(consts.SNIPPET_FILE, 'utf8')
|
line,
|
||||||
|
cursor.line,
|
||||||
|
cursor.ch
|
||||||
)
|
)
|
||||||
if (expandSnippet(line, cursor, cm, snippets) === false) {
|
if (expandSnippet(wordBeforeCursor, cursor, cm) === false) {
|
||||||
if (tabs) {
|
if (tabs) {
|
||||||
cm.execCommand('insertTab')
|
cm.execCommand('insertTab')
|
||||||
} else {
|
} else {
|
||||||
@@ -197,6 +217,14 @@ export default class CodeEditor extends React.Component {
|
|||||||
'Cmd-T': function(cm) {
|
'Cmd-T': function(cm) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
},
|
},
|
||||||
|
[translateHotkey(hotkey.insertDate)]: function(cm) {
|
||||||
|
const dateNow = new Date()
|
||||||
|
cm.replaceSelection(dateNow.toLocaleDateString())
|
||||||
|
},
|
||||||
|
[translateHotkey(hotkey.insertDateTime)]: function(cm) {
|
||||||
|
const dateNow = new Date()
|
||||||
|
cm.replaceSelection(dateNow.toLocaleString())
|
||||||
|
},
|
||||||
Enter: 'boostNewLineAndIndentContinueMarkdownList',
|
Enter: 'boostNewLineAndIndentContinueMarkdownList',
|
||||||
'Ctrl-C': cm => {
|
'Ctrl-C': cm => {
|
||||||
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
|
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
|
||||||
@@ -204,6 +232,41 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
return CodeMirror.Pass
|
return CodeMirror.Pass
|
||||||
},
|
},
|
||||||
|
[translateHotkey(hotkey.prettifyMarkdown)]: cm => {
|
||||||
|
// Default / User configured prettier options
|
||||||
|
const currentConfig = JSON.parse(self.props.prettierConfig)
|
||||||
|
|
||||||
|
// Parser type will always need to be markdown so we override the option before use
|
||||||
|
currentConfig.parser = 'markdown'
|
||||||
|
|
||||||
|
// Get current cursor position
|
||||||
|
const cursorPos = cm.getCursor()
|
||||||
|
currentConfig.cursorOffset = cm.doc.indexFromPos(cursorPos)
|
||||||
|
|
||||||
|
// Prettify contents of editor
|
||||||
|
const formattedTextDetails = prettier.formatWithCursor(
|
||||||
|
cm.doc.getValue(),
|
||||||
|
currentConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
const formattedText = formattedTextDetails.formatted
|
||||||
|
const formattedCursorPos = formattedTextDetails.cursorOffset
|
||||||
|
cm.doc.setValue(formattedText)
|
||||||
|
|
||||||
|
// Reset Cursor position to be at the same markdown as was before prettifying
|
||||||
|
const newCursorPos = cm.doc.posFromIndex(formattedCursorPos)
|
||||||
|
cm.doc.setCursor(newCursorPos)
|
||||||
|
},
|
||||||
|
[translateHotkey(hotkey.sortLines)]: cm => {
|
||||||
|
const selection = cm.doc.getSelection()
|
||||||
|
const appendLineBreak = /\n$/.test(selection)
|
||||||
|
|
||||||
|
const sorted = _.split(selection.trim(), '\n').sort()
|
||||||
|
const sortedString =
|
||||||
|
_.join(sorted, '\n') + (appendLineBreak ? '\n' : '')
|
||||||
|
|
||||||
|
cm.doc.replaceSelection(sortedString)
|
||||||
|
},
|
||||||
[translateHotkey(hotkey.pasteSmartly)]: cm => {
|
[translateHotkey(hotkey.pasteSmartly)]: cm => {
|
||||||
this.handlePaste(cm, true)
|
this.handlePaste(cm, true)
|
||||||
}
|
}
|
||||||
@@ -227,25 +290,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { rulers, enableRulers } = this.props
|
const { rulers, enableRulers, enableMarkdownLint, RTL } = this.props
|
||||||
eventEmitter.on('line:jump', this.scrollToLineHandeler)
|
eventEmitter.on('line:jump', this.scrollToLineHandeler)
|
||||||
|
|
||||||
const defaultSnippet = [
|
snippetManager.init()
|
||||||
{
|
|
||||||
id: crypto.randomBytes(16).toString('hex'),
|
|
||||||
name: 'Dummy text',
|
|
||||||
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.'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
if (!fs.existsSync(consts.SNIPPET_FILE)) {
|
|
||||||
fs.writeFileSync(
|
|
||||||
consts.SNIPPET_FILE,
|
|
||||||
JSON.stringify(defaultSnippet, null, 4),
|
|
||||||
'utf8'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateDefaultKeyMap()
|
this.updateDefaultKeyMap()
|
||||||
|
|
||||||
this.value = this.props.value
|
this.value = this.props.value
|
||||||
@@ -254,7 +302,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
value: this.props.value,
|
value: this.props.value,
|
||||||
linesHighlighted: this.props.linesHighlighted,
|
linesHighlighted: this.props.linesHighlighted,
|
||||||
lineNumbers: this.props.displayLineNumbers,
|
lineNumbers: this.props.displayLineNumbers,
|
||||||
lineWrapping: true,
|
lineWrapping: this.props.lineWrapping,
|
||||||
theme: this.props.theme,
|
theme: this.props.theme,
|
||||||
indentUnit: this.props.indentSize,
|
indentUnit: this.props.indentSize,
|
||||||
tabSize: this.props.indentSize,
|
tabSize: this.props.indentSize,
|
||||||
@@ -263,24 +311,42 @@ export default class CodeEditor extends React.Component {
|
|||||||
scrollPastEnd: this.props.scrollPastEnd,
|
scrollPastEnd: this.props.scrollPastEnd,
|
||||||
inputStyle: 'textarea',
|
inputStyle: 'textarea',
|
||||||
dragDrop: false,
|
dragDrop: false,
|
||||||
|
direction: RTL ? 'rtl' : 'ltr',
|
||||||
|
rtlMoveVisually: RTL,
|
||||||
foldGutter: true,
|
foldGutter: true,
|
||||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
lint: enableMarkdownLint ? this.getCodeEditorLintConfig() : false,
|
||||||
|
gutters: [
|
||||||
|
'CodeMirror-linenumbers',
|
||||||
|
'CodeMirror-foldgutter',
|
||||||
|
'CodeMirror-lint-markers'
|
||||||
|
],
|
||||||
autoCloseBrackets: {
|
autoCloseBrackets: {
|
||||||
codeBlock: {
|
codeBlock: {
|
||||||
pairs: this.props.codeBlockMatchingPairs,
|
pairs: this.props.codeBlockMatchingPairs,
|
||||||
|
closeBefore: this.props.codeBlockMatchingCloseBefore,
|
||||||
triples: this.props.codeBlockMatchingTriples,
|
triples: this.props.codeBlockMatchingTriples,
|
||||||
explode: this.props.codeBlockExplodingPairs
|
explode: this.props.codeBlockExplodingPairs
|
||||||
},
|
},
|
||||||
markdown: {
|
markdown: {
|
||||||
pairs: this.props.matchingPairs,
|
pairs: this.props.matchingPairs,
|
||||||
|
closeBefore: this.props.matchingCloseBefore,
|
||||||
triples: this.props.matchingTriples,
|
triples: this.props.matchingTriples,
|
||||||
explode: this.props.explodingPairs
|
explode: this.props.explodingPairs
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
extraKeys: this.defaultKeyMap
|
extraKeys: this.defaultKeyMap,
|
||||||
|
prettierConfig: this.props.prettierConfig
|
||||||
})
|
})
|
||||||
|
|
||||||
|
document.querySelector(
|
||||||
|
'.CodeMirror-lint-markers'
|
||||||
|
).style.display = enableMarkdownLint ? 'inline-block' : 'none'
|
||||||
|
|
||||||
|
if (!this.props.mode && this.props.value && this.props.autoDetect) {
|
||||||
|
this.autoDetectLanguage(this.props.value)
|
||||||
|
} else {
|
||||||
this.setMode(this.props.mode)
|
this.setMode(this.props.mode)
|
||||||
|
}
|
||||||
|
|
||||||
this.editor.on('focus', this.focusHandler)
|
this.editor.on('focus', this.focusHandler)
|
||||||
this.editor.on('blur', this.blurHandler)
|
this.editor.on('blur', this.blurHandler)
|
||||||
@@ -317,13 +383,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': () => {
|
||||||
@@ -442,61 +508,12 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.initialHighlighting()
|
this.initialHighlighting()
|
||||||
}
|
}
|
||||||
|
|
||||||
expandSnippet (line, cursor, cm, snippets) {
|
|
||||||
const wordBeforeCursor = this.getWordBeforeCursor(
|
|
||||||
line,
|
|
||||||
cursor.line,
|
|
||||||
cursor.ch
|
|
||||||
)
|
|
||||||
const templateCursorString = ':{}'
|
|
||||||
for (let i = 0; i < snippets.length; i++) {
|
|
||||||
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
|
|
||||||
if (snippets[i].content.indexOf(templateCursorString) !== -1) {
|
|
||||||
const snippetLines = snippets[i].content.split('\n')
|
|
||||||
let cursorLineNumber = 0
|
|
||||||
let cursorLinePosition = 0
|
|
||||||
|
|
||||||
let cursorIndex
|
|
||||||
for (let j = 0; j < snippetLines.length; j++) {
|
|
||||||
cursorIndex = snippetLines[j].indexOf(templateCursorString)
|
|
||||||
|
|
||||||
if (cursorIndex !== -1) {
|
|
||||||
cursorLineNumber = j
|
|
||||||
cursorLinePosition = cursorIndex
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cm.replaceRange(
|
|
||||||
snippets[i].content.replace(templateCursorString, ''),
|
|
||||||
wordBeforeCursor.range.from,
|
|
||||||
wordBeforeCursor.range.to
|
|
||||||
)
|
|
||||||
cm.setCursor({
|
|
||||||
line: cursor.line + cursorLineNumber,
|
|
||||||
ch: cursorLinePosition + cursor.ch - wordBeforeCursor.text.length
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
cm.replaceRange(
|
|
||||||
snippets[i].content,
|
|
||||||
wordBeforeCursor.range.from,
|
|
||||||
wordBeforeCursor.range.to
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
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|\$/
|
||||||
|
|
||||||
// to prevent the word to expand is long that will crash the whole app
|
// to prevent the word is long that will crash the whole app
|
||||||
// the safeStop is there to stop user to expand words that longer than 20 chars
|
// the safeStop is there to stop user to expand words that longer than 20 chars
|
||||||
const safeStop = 20
|
const safeStop = 20
|
||||||
|
|
||||||
@@ -506,7 +523,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
if (!emptyChars.test(currentChar)) {
|
if (!emptyChars.test(currentChar)) {
|
||||||
wordBeforeCursor = currentChar + wordBeforeCursor
|
wordBeforeCursor = currentChar + wordBeforeCursor
|
||||||
} else if (wordBeforeCursor.length >= safeStop) {
|
} else if (wordBeforeCursor.length >= safeStop) {
|
||||||
throw new Error('Your snippet trigger is too long !')
|
throw new Error('Stopped after 20 loops for safety reason !')
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -551,7 +568,9 @@ export default class CodeEditor extends React.Component {
|
|||||||
let needRefresh = false
|
let needRefresh = false
|
||||||
const {
|
const {
|
||||||
rulers,
|
rulers,
|
||||||
enableRulers
|
enableRulers,
|
||||||
|
enableMarkdownLint,
|
||||||
|
customMarkdownLintConfig
|
||||||
} = this.props
|
} = this.props
|
||||||
if (prevProps.mode !== this.props.mode) {
|
if (prevProps.mode !== this.props.mode) {
|
||||||
this.setMode(this.props.mode)
|
this.setMode(this.props.mode)
|
||||||
@@ -569,6 +588,25 @@ export default class CodeEditor extends React.Component {
|
|||||||
if (prevProps.keyMap !== this.props.keyMap) {
|
if (prevProps.keyMap !== this.props.keyMap) {
|
||||||
needRefresh = true
|
needRefresh = true
|
||||||
}
|
}
|
||||||
|
if (prevProps.RTL !== this.props.RTL) {
|
||||||
|
this.editor.setOption('direction', this.props.RTL ? 'rtl' : 'ltr')
|
||||||
|
this.editor.setOption('rtlMoveVisually', this.props.RTL)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
prevProps.enableMarkdownLint !== enableMarkdownLint ||
|
||||||
|
prevProps.customMarkdownLintConfig !== customMarkdownLintConfig
|
||||||
|
) {
|
||||||
|
if (!enableMarkdownLint) {
|
||||||
|
this.editor.setOption('lint', { default: false })
|
||||||
|
document.querySelector('.CodeMirror-lint-markers').style.display =
|
||||||
|
'none'
|
||||||
|
} else {
|
||||||
|
this.editor.setOption('lint', this.getCodeEditorLintConfig())
|
||||||
|
document.querySelector('.CodeMirror-lint-markers').style.display =
|
||||||
|
'inline-block'
|
||||||
|
}
|
||||||
|
needRefresh = true
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
prevProps.enableRulers !== enableRulers ||
|
prevProps.enableRulers !== enableRulers ||
|
||||||
@@ -589,24 +627,36 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.setOption('lineNumbers', this.props.displayLineNumbers)
|
this.editor.setOption('lineNumbers', this.props.displayLineNumbers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prevProps.lineWrapping !== this.props.lineWrapping) {
|
||||||
|
this.editor.setOption('lineWrapping', this.props.lineWrapping)
|
||||||
|
}
|
||||||
|
|
||||||
if (prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
|
if (prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
|
||||||
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.matchingCloseBefore !== this.props.matchingCloseBefore ||
|
||||||
prevProps.matchingTriples !== this.props.matchingTriples ||
|
prevProps.matchingTriples !== this.props.matchingTriples ||
|
||||||
prevProps.explodingPairs !== this.props.explodingPairs ||
|
prevProps.explodingPairs !== this.props.explodingPairs ||
|
||||||
prevProps.codeBlockMatchingPairs !== this.props.codeBlockMatchingPairs ||
|
prevProps.codeBlockMatchingPairs !== this.props.codeBlockMatchingPairs ||
|
||||||
prevProps.codeBlockMatchingTriples !== this.props.codeBlockMatchingTriples ||
|
prevProps.codeBlockMatchingCloseBefore !==
|
||||||
prevProps.codeBlockExplodingPairs !== this.props.codeBlockExplodingPairs) {
|
this.props.codeBlockMatchingCloseBefore ||
|
||||||
|
prevProps.codeBlockMatchingTriples !==
|
||||||
|
this.props.codeBlockMatchingTriples ||
|
||||||
|
prevProps.codeBlockExplodingPairs !== this.props.codeBlockExplodingPairs
|
||||||
|
) {
|
||||||
const autoCloseBrackets = {
|
const autoCloseBrackets = {
|
||||||
codeBlock: {
|
codeBlock: {
|
||||||
pairs: this.props.codeBlockMatchingPairs,
|
pairs: this.props.codeBlockMatchingPairs,
|
||||||
|
closeBefore: this.props.codeBlockMatchingCloseBefore,
|
||||||
triples: this.props.codeBlockMatchingTriples,
|
triples: this.props.codeBlockMatchingTriples,
|
||||||
explode: this.props.codeBlockExplodingPairs
|
explode: this.props.codeBlockExplodingPairs
|
||||||
},
|
},
|
||||||
markdown: {
|
markdown: {
|
||||||
pairs: this.props.matchingPairs,
|
pairs: this.props.matchingPairs,
|
||||||
|
closeBefore: this.props.matchingCloseBefore,
|
||||||
triples: this.props.matchingTriples,
|
triples: this.props.matchingTriples,
|
||||||
explode: this.props.explodingPairs
|
explode: this.props.explodingPairs
|
||||||
}
|
}
|
||||||
@@ -650,17 +700,79 @@ 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
|
||||||
|
) {
|
||||||
|
this.editor.setOption(
|
||||||
|
'deleteUnusedAttachments',
|
||||||
|
this.props.deleteUnusedAttachments
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (needRefresh) {
|
if (needRefresh) {
|
||||||
this.editor.refresh()
|
this.editor.refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCodeEditorLintConfig() {
|
||||||
|
const { mode } = this.props
|
||||||
|
const checkMarkdownNoteIsOpen = mode === 'Boost Flavored Markdown'
|
||||||
|
|
||||||
|
return checkMarkdownNoteIsOpen
|
||||||
|
? {
|
||||||
|
getAnnotations: this.validatorOfMarkdown,
|
||||||
|
async: true
|
||||||
|
}
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
|
||||||
|
validatorOfMarkdown(text, updateLinting) {
|
||||||
|
const { customMarkdownLintConfig } = this.props
|
||||||
|
let lintConfigJson
|
||||||
|
try {
|
||||||
|
Jsonlint.parse(customMarkdownLintConfig)
|
||||||
|
lintConfigJson = JSON.parse(customMarkdownLintConfig)
|
||||||
|
} catch (err) {
|
||||||
|
eventEmitter.emit('APP_SETTING_ERROR')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const lintOptions = {
|
||||||
|
strings: {
|
||||||
|
content: text
|
||||||
|
},
|
||||||
|
config: lintConfigJson
|
||||||
|
}
|
||||||
|
|
||||||
|
return markdownlint(lintOptions, (err, result) => {
|
||||||
|
if (!err) {
|
||||||
|
const foundIssues = []
|
||||||
|
const splitText = text.split('\n')
|
||||||
|
result.content.map(item => {
|
||||||
|
let ruleNames = ''
|
||||||
|
item.ruleNames.map((ruleName, index) => {
|
||||||
|
ruleNames += ruleName
|
||||||
|
ruleNames += index === item.ruleNames.length - 1 ? ': ' : '/'
|
||||||
|
})
|
||||||
|
const lineNumber = item.lineNumber - 1
|
||||||
|
foundIssues.push({
|
||||||
|
from: CodeMirror.Pos(lineNumber, 0),
|
||||||
|
to: CodeMirror.Pos(lineNumber, splitText[lineNumber].length),
|
||||||
|
message: ruleNames + item.ruleDescription,
|
||||||
|
severity: 'warning'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
updateLinting(foundIssues)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
setMode(mode) {
|
setMode(mode) {
|
||||||
let syntax = CodeMirror.findModeByName(convertModeName(mode))
|
let syntax = CodeMirror.findModeByName(convertModeName(mode || 'text'))
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
|
|
||||||
this.editor.setOption('mode', syntax.mime)
|
this.editor.setOption('mode', syntax.mime)
|
||||||
@@ -670,6 +782,38 @@ export default class CodeEditor extends React.Component {
|
|||||||
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.
|
||||||
|
// origin is undefined when markdownTocGenerator replace the old tod
|
||||||
|
if (tocExistsInEditor(editor) && changeObject.origin !== undefined) {
|
||||||
|
let requireTocUpdate
|
||||||
|
|
||||||
|
// Check if one of the changed lines contains a headline
|
||||||
|
for (let line = 0; line < changeObject.text.length; line++) {
|
||||||
|
if (
|
||||||
|
this.linePossibleContainsHeadline(
|
||||||
|
editor.getLine(changeObject.from.line + line)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
requireTocUpdate = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requireTocUpdate) {
|
||||||
|
// Check if one of the removed lines contains a headline
|
||||||
|
for (let line = 0; line < changeObject.removed.length; line++) {
|
||||||
|
if (this.linePossibleContainsHeadline(changeObject.removed[line])) {
|
||||||
|
requireTocUpdate = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requireTocUpdate) {
|
||||||
|
generateInEditor(editor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.updateHighlight(editor, changeObject)
|
this.updateHighlight(editor, changeObject)
|
||||||
|
|
||||||
this.value = editor.getValue()
|
this.value = editor.getValue()
|
||||||
@@ -678,15 +822,21 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
linePossibleContainsHeadline(currentLine) {
|
||||||
|
// We can't check if the line start with # because when some write text before
|
||||||
|
// the # we also need to update the toc
|
||||||
|
return currentLine.includes('# ')
|
||||||
|
}
|
||||||
|
|
||||||
incrementLines(start, linesAdded, linesRemoved, editor) {
|
incrementLines(start, linesAdded, linesRemoved, editor) {
|
||||||
let highlightedLines = editor.options.linesHighlighted
|
const highlightedLines = editor.options.linesHighlighted
|
||||||
|
|
||||||
const totalHighlightedLines = highlightedLines.length
|
const totalHighlightedLines = highlightedLines.length
|
||||||
|
|
||||||
let offset = linesAdded - linesRemoved
|
const offset = linesAdded - linesRemoved
|
||||||
|
|
||||||
// Store new items to be added as we're changing the lines
|
// Store new items to be added as we're changing the lines
|
||||||
let newLines = []
|
const newLines = []
|
||||||
|
|
||||||
let i = totalHighlightedLines
|
let i = totalHighlightedLines
|
||||||
|
|
||||||
@@ -699,7 +849,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -718,10 +868,18 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
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)
|
||||||
@@ -767,6 +925,9 @@ export default class CodeEditor extends React.Component {
|
|||||||
ch: 1
|
ch: 1
|
||||||
}
|
}
|
||||||
this.editor.setCursor(cursor)
|
this.editor.setCursor(cursor)
|
||||||
|
const top = this.editor.charCoords({ line: num, ch: 0 }, 'local').top
|
||||||
|
const middleHeight = this.editor.getScrollerElement().offsetHeight / 2
|
||||||
|
this.editor.scrollTo(null, top - middleHeight - 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
@@ -794,12 +955,24 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.setCursor(cursor)
|
this.editor.setCursor(cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update content of one line
|
||||||
|
* @param {Number} lineNumber
|
||||||
|
* @param {String} content
|
||||||
|
*/
|
||||||
|
setLineContent(lineNumber, content) {
|
||||||
|
const prevContent = this.editor.getLine(lineNumber)
|
||||||
|
const prevContentLength = prevContent ? prevContent.length : 0
|
||||||
|
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,
|
||||||
@@ -812,31 +985,40 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.replaceSelection(imageMd)
|
this.editor.replaceSelection(imageMd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
autoDetectLanguage(content) {
|
||||||
|
const res = hljs.highlightAuto(content, Object.keys(languageMaps))
|
||||||
|
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 => {
|
const isURL = str =>
|
||||||
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
|
/(?:^\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
|
||||||
return matcher.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,
|
line: startCursor.line,
|
||||||
ch: startCursor.ch - 2
|
ch: startCursor.ch - 2
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
line: startCursor.line,
|
line: startCursor.line,
|
||||||
ch: startCursor.ch
|
ch: startCursor.ch
|
||||||
})
|
}
|
||||||
|
)
|
||||||
const endCursor = editor.getCursor('end')
|
const endCursor = editor.getCursor('end')
|
||||||
const nextChar = editor.getRange({
|
const nextChar = editor.getRange(
|
||||||
|
{
|
||||||
line: endCursor.line,
|
line: endCursor.line,
|
||||||
ch: endCursor.ch
|
ch: endCursor.ch
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
line: endCursor.line,
|
line: endCursor.line,
|
||||||
ch: endCursor.ch + 1
|
ch: endCursor.ch + 1
|
||||||
})
|
}
|
||||||
|
)
|
||||||
return prevChar === '](' && nextChar === ')'
|
return prevChar === '](' && nextChar === ')'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -848,7 +1030,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,
|
||||||
@@ -880,6 +1062,12 @@ 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)
|
||||||
|
) {
|
||||||
|
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)
|
||||||
} else if (attachmentManagement.isAttachmentLink(pastedTxt)) {
|
} else if (attachmentManagement.isAttachmentLink(pastedTxt)) {
|
||||||
@@ -891,7 +1079,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
const image = clipboard.readImage()
|
const image = clipboard.readImage()
|
||||||
if (!image.isEmpty()) {
|
if (!image.isEmpty()) {
|
||||||
attachmentManagement.handlePastNativeImage(
|
attachmentManagement.handlePasteNativeImage(
|
||||||
this,
|
this,
|
||||||
storageKey,
|
storageKey,
|
||||||
noteKey,
|
noteKey,
|
||||||
@@ -908,6 +1096,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.handlePasteText(editor, pastedTxt)
|
this.handlePasteText(editor, pastedTxt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.props.mode && this.props.autoDetect) {
|
||||||
|
this.autoDetectLanguage(editor.doc.getValue())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScroll(e) {
|
handleScroll(e) {
|
||||||
@@ -917,7 +1109,17 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlePasteUrl(editor, pastedTxt) {
|
handlePasteUrl(editor, pastedTxt) {
|
||||||
const taggedUrl = `<${pastedTxt}>`
|
let taggedUrl = `<${pastedTxt}>`
|
||||||
|
let urlToFetch = pastedTxt
|
||||||
|
let titleMark = ''
|
||||||
|
|
||||||
|
if (isMarkdownTitleURL(pastedTxt)) {
|
||||||
|
const pastedTxtSplitted = pastedTxt.split(' ')
|
||||||
|
titleMark = `${pastedTxtSplitted[0]} `
|
||||||
|
urlToFetch = pastedTxtSplitted[1]
|
||||||
|
taggedUrl = `<${urlToFetch}>`
|
||||||
|
}
|
||||||
|
|
||||||
editor.replaceSelection(taggedUrl)
|
editor.replaceSelection(taggedUrl)
|
||||||
|
|
||||||
const isImageReponse = response => {
|
const isImageReponse = response => {
|
||||||
@@ -929,22 +1131,23 @@ export default class CodeEditor extends React.Component {
|
|||||||
const replaceTaggedUrl = replacement => {
|
const replaceTaggedUrl = replacement => {
|
||||||
const value = editor.getValue()
|
const value = editor.getValue()
|
||||||
const cursor = editor.getCursor()
|
const cursor = editor.getCursor()
|
||||||
const newValue = value.replace(taggedUrl, replacement)
|
const newValue = value.replace(taggedUrl, titleMark + replacement)
|
||||||
const newCursor = Object.assign({}, cursor, {
|
const newCursor = Object.assign({}, cursor, {
|
||||||
ch: cursor.ch + newValue.length - value.length
|
ch: cursor.ch + newValue.length - (value.length - titleMark.length)
|
||||||
})
|
})
|
||||||
|
|
||||||
editor.setValue(newValue)
|
editor.setValue(newValue)
|
||||||
editor.setCursor(newCursor)
|
editor.setCursor(newCursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(pastedTxt, {
|
fetch(urlToFetch, {
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (isImageReponse(response)) {
|
if (isImageReponse(response)) {
|
||||||
return this.mapImageResponse(response, pastedTxt)
|
return this.mapImageResponse(response, urlToFetch)
|
||||||
} else {
|
} else {
|
||||||
return this.mapNormalResponse(response, pastedTxt)
|
return this.mapNormalResponse(response, urlToFetch)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(replacement => {
|
.then(replacement => {
|
||||||
@@ -972,10 +1175,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)
|
||||||
@@ -998,7 +1203,11 @@ 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'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1028,11 +1237,11 @@ 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(new Buffer(buff), charset).toString())
|
resolve(iconv.decode(Buffer.from(buff), charset).toString())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e)
|
reject(e)
|
||||||
}
|
}
|
||||||
@@ -1044,7 +1253,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
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]
|
||||||
@@ -1052,28 +1264,21 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { className, fontSize, fontFamily, width, height } = this.props
|
||||||
className,
|
const normalisedFontFamily = normalizeEditorFontFamily(fontFamily)
|
||||||
fontSize
|
|
||||||
} = this.props
|
return (
|
||||||
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
|
<div
|
||||||
const width = this.props.width
|
className={className == null ? 'CodeEditor' : `CodeEditor ${className}`}
|
||||||
return (<
|
|
||||||
div className={
|
|
||||||
className == null ? 'CodeEditor' : `CodeEditor ${className}`
|
|
||||||
}
|
|
||||||
ref='root'
|
ref='root'
|
||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
style={
|
style={{
|
||||||
{
|
fontFamily: normalisedFontFamily,
|
||||||
fontFamily,
|
fontSize,
|
||||||
fontSize: fontSize,
|
width,
|
||||||
width: width
|
height
|
||||||
}
|
}}
|
||||||
}
|
onDrop={e => this.handleDropImage(e)}
|
||||||
onDrop={
|
|
||||||
e => this.handleDropImage(e)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1085,7 +1290,9 @@ export default class CodeEditor extends React.Component {
|
|||||||
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')
|
||||||
@@ -1107,7 +1314,12 @@ CodeEditor.propTypes = {
|
|||||||
onBlur: PropTypes.func,
|
onBlur: PropTypes.func,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
readOnly: PropTypes.bool,
|
readOnly: PropTypes.bool,
|
||||||
spellCheck: PropTypes.bool
|
autoDetect: PropTypes.bool,
|
||||||
|
spellCheck: PropTypes.bool,
|
||||||
|
enableMarkdownLint: PropTypes.bool,
|
||||||
|
customMarkdownLintConfig: PropTypes.string,
|
||||||
|
deleteUnusedAttachments: PropTypes.bool,
|
||||||
|
RTL: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeEditor.defaultProps = {
|
CodeEditor.defaultProps = {
|
||||||
@@ -1118,5 +1330,11 @@ CodeEditor.defaultProps = {
|
|||||||
fontFamily: 'Monaco, Consolas',
|
fontFamily: 'Monaco, Consolas',
|
||||||
indentSize: 4,
|
indentSize: 4,
|
||||||
indentType: 'space',
|
indentType: 'space',
|
||||||
spellCheck: false
|
autoDetect: false,
|
||||||
|
spellCheck: false,
|
||||||
|
enableMarkdownLint: DEFAULT_CONFIG.editor.enableMarkdownLint,
|
||||||
|
customMarkdownLintConfig: DEFAULT_CONFIG.editor.customMarkdownLintConfig,
|
||||||
|
prettierConfig: DEFAULT_CONFIG.editor.prettierConfig,
|
||||||
|
deleteUnusedAttachments: DEFAULT_CONFIG.editor.deleteUnusedAttachments,
|
||||||
|
RTL: false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,3 @@
|
|||||||
|
|
||||||
.spellcheck-select
|
.spellcheck-select
|
||||||
border: none
|
border: none
|
||||||
text-decoration underline wavy red
|
|
||||||
|
|||||||
77
browser/components/ColorPicker.js
Normal file
77
browser/components/ColorPicker.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { SketchPicker } from 'react-color'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './ColorPicker.styl'
|
||||||
|
|
||||||
|
const componentHeight = 330
|
||||||
|
|
||||||
|
class ColorPicker extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
color: this.props.color || '#939395'
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onColorChange = this.onColorChange.bind(this)
|
||||||
|
this.handleConfirm = this.handleConfirm.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
this.onColorChange(nextProps.color)
|
||||||
|
}
|
||||||
|
|
||||||
|
onColorChange(color) {
|
||||||
|
this.setState({
|
||||||
|
color
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleConfirm() {
|
||||||
|
this.props.onConfirm(this.state.color)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { onReset, onCancel, targetRect } = this.props
|
||||||
|
const { color } = this.state
|
||||||
|
|
||||||
|
const clientHeight = document.body.clientHeight
|
||||||
|
const alignX = targetRect.right + 4
|
||||||
|
let alignY = targetRect.top
|
||||||
|
if (targetRect.top + componentHeight > clientHeight) {
|
||||||
|
alignY = targetRect.bottom - componentHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
styleName='colorPicker'
|
||||||
|
style={{ top: `${alignY}px`, left: `${alignX}px` }}
|
||||||
|
>
|
||||||
|
<div styleName='cover' onClick={onCancel} />
|
||||||
|
<SketchPicker color={color} onChange={this.onColorChange} />
|
||||||
|
<div styleName='footer'>
|
||||||
|
<button styleName='btn-reset' onClick={onReset}>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
<button styleName='btn-cancel' onClick={onCancel}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button styleName='btn-confirm' onClick={this.handleConfirm}>
|
||||||
|
Confirm
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorPicker.propTypes = {
|
||||||
|
color: PropTypes.string,
|
||||||
|
targetRect: PropTypes.object,
|
||||||
|
onConfirm: PropTypes.func.isRequired,
|
||||||
|
onCancel: PropTypes.func.isRequired,
|
||||||
|
onReset: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(ColorPicker, styles)
|
||||||
39
browser/components/ColorPicker.styl
Normal file
39
browser/components/ColorPicker.styl
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
.colorPicker
|
||||||
|
position fixed
|
||||||
|
z-index 2
|
||||||
|
display flex
|
||||||
|
flex-direction column
|
||||||
|
|
||||||
|
.cover
|
||||||
|
position fixed
|
||||||
|
top 0
|
||||||
|
right 0
|
||||||
|
bottom 0
|
||||||
|
left 0
|
||||||
|
|
||||||
|
.footer
|
||||||
|
display flex
|
||||||
|
justify-content center
|
||||||
|
z-index 2
|
||||||
|
align-items center
|
||||||
|
& > button + button
|
||||||
|
margin-left 10px
|
||||||
|
|
||||||
|
.btn-cancel,
|
||||||
|
.btn-confirm,
|
||||||
|
.btn-reset
|
||||||
|
vertical-align middle
|
||||||
|
height 25px
|
||||||
|
margin-top 2.5px
|
||||||
|
border-radius 2px
|
||||||
|
border none
|
||||||
|
padding 0 5px
|
||||||
|
background-color $default-button-background
|
||||||
|
&:hover
|
||||||
|
background-color $default-button-background--hover
|
||||||
|
.btn-confirm
|
||||||
|
background-color #1EC38B
|
||||||
|
&:hover
|
||||||
|
background-color darken(#1EC38B, 25%)
|
||||||
|
|
||||||
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable camelcase */
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
@@ -20,25 +21,32 @@ 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 : 'PREVIEW',
|
status:
|
||||||
|
props.config.editor.switchPreview === 'RIGHTCLICK'
|
||||||
|
? props.config.editor.delfaultStatus
|
||||||
|
: 'CODE',
|
||||||
renderValue: props.value,
|
renderValue: props.value,
|
||||||
keyPressed: new Set(),
|
keyPressed: new Set(),
|
||||||
isLocked: false
|
isLocked: props.isLocked
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lockEditorCode = () => this.handleLockEditor()
|
this.lockEditorCode = this.handleLockEditor.bind(this)
|
||||||
|
this.focusEditor = this.focusEditor.bind(this)
|
||||||
|
|
||||||
|
this.previewRef = React.createRef()
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
this.value = this.refs.code.value
|
this.value = this.refs.code.value
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (props) {
|
UNSAFE_componentWillReceiveProps(props) {
|
||||||
if (props.value !== this.props.value) {
|
if (props.value !== this.props.value) {
|
||||||
this.queueRendering(props.value)
|
this.queueRendering(props.value)
|
||||||
}
|
}
|
||||||
@@ -47,6 +55,21 @@ class MarkdownEditor extends React.Component {
|
|||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.cancelQueue()
|
this.cancelQueue()
|
||||||
eventEmitter.off('editor:lock', this.lockEditorCode)
|
eventEmitter.off('editor:lock', this.lockEditorCode)
|
||||||
|
eventEmitter.off('editor:focus', this.focusEditor)
|
||||||
|
}
|
||||||
|
|
||||||
|
focusEditor() {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
status: 'CODE'
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (this.refs.code == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.refs.code.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
queueRendering(value) {
|
queueRendering(value) {
|
||||||
@@ -76,23 +99,27 @@ class MarkdownEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleContextMenu(e) {
|
handleContextMenu(e) {
|
||||||
|
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') {
|
if (newStatus === 'CODE') {
|
||||||
this.refs.code.focus()
|
this.refs.code.focus()
|
||||||
} else {
|
} else {
|
||||||
this.refs.preview.focus()
|
this.previewRef.current.focus()
|
||||||
}
|
}
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
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)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,16 +127,21 @@ class MarkdownEditor extends React.Component {
|
|||||||
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.scrollTo(cursorPosition.line)
|
this.previewRef.current.focus()
|
||||||
})
|
this.previewRef.current.scrollToRow(cursorPosition.line)
|
||||||
|
}
|
||||||
|
)
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,12 +151,15 @@ class MarkdownEditor extends React.Component {
|
|||||||
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()
|
this.refs.code.focus()
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,12 +169,18 @@ class MarkdownEditor extends React.Component {
|
|||||||
|
|
||||||
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' &&
|
||||||
|
new Date() - this.previewMouseDownedAt < 200
|
||||||
|
) {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
status: 'CODE'
|
status: 'CODE'
|
||||||
}, () => {
|
},
|
||||||
|
() => {
|
||||||
this.refs.code.focus()
|
this.refs.code.focus()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,34 +189,38 @@ class MarkdownEditor extends React.Component {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
const idMatch = /checkbox-([0-9]+)/
|
const idMatch = /checkbox-([0-9]+)/
|
||||||
const checkedMatch = /^\s*[\+\-\*] \[x\]/i
|
const checkedMatch = /^(\s*>?)*\s*[+\-*] \[x]/i
|
||||||
const uncheckedMatch = /^\s*[\+\-\*] \[ \]/
|
const uncheckedMatch = /^(\s*>?)*\s*[+\-*] \[ ]/
|
||||||
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
|
||||||
|
|
||||||
if (targetLine.match(checkedMatch)) {
|
if (targetLine.match(checkedMatch)) {
|
||||||
lines[lineIndex] = targetLine.replace(checkReplace, '[ ]')
|
newLine = targetLine.replace(checkReplace, '[ ]')
|
||||||
}
|
}
|
||||||
if (targetLine.match(uncheckedMatch)) {
|
if (targetLine.match(uncheckedMatch)) {
|
||||||
lines[lineIndex] = targetLine.replace(uncheckReplace, '[x]')
|
newLine = targetLine.replace(uncheckReplace, '[x]')
|
||||||
}
|
}
|
||||||
this.refs.code.setValue(lines.join('\n'))
|
this.refs.code.setLineContent(lineIndex, newLine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
@@ -194,15 +239,23 @@ class MarkdownEditor extends React.Component {
|
|||||||
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('**')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,20 +268,27 @@ 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) {
|
addMdAroundSelection(mdElement) {
|
||||||
this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`)
|
this.refs.code.editor.replaceSelection(
|
||||||
|
`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDropImage(dropEvent) {
|
handleDropImage(dropEvent) {
|
||||||
dropEvent.preventDefault()
|
dropEvent.preventDefault()
|
||||||
const { storageKey, noteKey } = this.props
|
const { storageKey, noteKey } = this.props
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
|
{
|
||||||
status: 'CODE'
|
status: 'CODE'
|
||||||
}, () => {
|
},
|
||||||
|
() => {
|
||||||
this.refs.code.focus()
|
this.refs.code.focus()
|
||||||
|
|
||||||
this.refs.code.editor.execCommand('goDocEnd')
|
this.refs.code.editor.execCommand('goDocEnd')
|
||||||
@@ -241,7 +301,8 @@ class MarkdownEditor extends React.Component {
|
|||||||
noteKey,
|
noteKey,
|
||||||
dropEvent
|
dropEvent
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyUp(e) {
|
handleKeyUp(e) {
|
||||||
@@ -255,7 +316,15 @@ class MarkdownEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {className, value, config, storageKey, noteKey, linesHighlighted} = 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
|
||||||
@@ -263,23 +332,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'
|
||||||
@@ -293,10 +363,15 @@ class MarkdownEditor extends React.Component {
|
|||||||
enableRulers={config.editor.enableRulers}
|
enableRulers={config.editor.enableRulers}
|
||||||
rulers={config.editor.rulers}
|
rulers={config.editor.rulers}
|
||||||
displayLineNumbers={config.editor.displayLineNumbers}
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
|
lineWrapping
|
||||||
matchingPairs={config.editor.matchingPairs}
|
matchingPairs={config.editor.matchingPairs}
|
||||||
|
matchingCloseBefore={config.editor.matchingCloseBefore}
|
||||||
matchingTriples={config.editor.matchingTriples}
|
matchingTriples={config.editor.matchingTriples}
|
||||||
explodingPairs={config.editor.explodingPairs}
|
explodingPairs={config.editor.explodingPairs}
|
||||||
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
||||||
|
codeBlockMatchingCloseBefore={
|
||||||
|
config.editor.codeBlockMatchingCloseBefore
|
||||||
|
}
|
||||||
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
||||||
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
@@ -305,16 +380,22 @@ 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}
|
||||||
switchPreview={config.editor.switchPreview}
|
switchPreview={config.editor.switchPreview}
|
||||||
|
enableMarkdownLint={config.editor.enableMarkdownLint}
|
||||||
|
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
||||||
|
prettierConfig={config.editor.prettierConfig}
|
||||||
|
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
||||||
|
RTL={RTL}
|
||||||
/>
|
/>
|
||||||
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
|
<MarkdownPreview
|
||||||
? 'preview'
|
ref={this.previewRef}
|
||||||
: 'preview--hide'
|
styleName={
|
||||||
|
this.state.status === 'PREVIEW' ? 'preview' : 'preview--hide'
|
||||||
}
|
}
|
||||||
style={previewStyle}
|
style={previewStyle}
|
||||||
theme={config.ui.theme}
|
theme={config.ui.theme}
|
||||||
@@ -330,21 +411,22 @@ class MarkdownEditor extends React.Component {
|
|||||||
smartArrows={config.preview.smartArrows}
|
smartArrows={config.preview.smartArrows}
|
||||||
breaks={config.preview.breaks}
|
breaks={config.preview.breaks}
|
||||||
sanitize={config.preview.sanitize}
|
sanitize={config.preview.sanitize}
|
||||||
ref='preview'
|
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
|
||||||
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}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
import Markdown from 'browser/lib/markdown'
|
import Markdown from 'browser/lib/markdown'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
@@ -8,9 +9,10 @@ import consts from 'browser/lib/consts'
|
|||||||
import Raphael from 'raphael'
|
import Raphael from 'raphael'
|
||||||
import flowchart from 'flowchart'
|
import flowchart from 'flowchart'
|
||||||
import mermaidRender from './render/MermaidRender'
|
import mermaidRender from './render/MermaidRender'
|
||||||
import SequenceDiagram from 'js-sequence-diagrams'
|
import SequenceDiagram from '@rokt33r/js-sequence-diagrams'
|
||||||
import Chart from 'chart.js'
|
import Chart from 'chart.js'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
import config from 'browser/main/lib/ConfigManager'
|
||||||
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
||||||
import convertModeName from 'browser/lib/convertModeName'
|
import convertModeName from 'browser/lib/convertModeName'
|
||||||
import copy from 'copy-to-clipboard'
|
import copy from 'copy-to-clipboard'
|
||||||
@@ -18,15 +20,17 @@ import mdurl from 'mdurl'
|
|||||||
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
||||||
import { escapeHtmlCharacters } from 'browser/lib/utils'
|
import { escapeHtmlCharacters } from 'browser/lib/utils'
|
||||||
import yaml from 'js-yaml'
|
import yaml from 'js-yaml'
|
||||||
import context from 'browser/lib/context'
|
|
||||||
import i18n from 'browser/lib/i18n'
|
|
||||||
import fs from 'fs'
|
|
||||||
import { render } from 'react-dom'
|
import { render } from 'react-dom'
|
||||||
import Carousel from 'react-image-carousel'
|
import Carousel from 'react-image-carousel'
|
||||||
|
import { push } from 'connected-react-router'
|
||||||
import ConfigManager from '../main/lib/ConfigManager'
|
import ConfigManager from '../main/lib/ConfigManager'
|
||||||
|
import uiThemes from 'browser/lib/ui-themes'
|
||||||
|
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 { app } = remote
|
const { app } = remote
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
@@ -34,8 +38,6 @@ const fileUrl = require('file-url')
|
|||||||
|
|
||||||
const dialog = remote.dialog
|
const dialog = remote.dialog
|
||||||
|
|
||||||
const uri2path = require('file-uri-to-path')
|
|
||||||
|
|
||||||
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
||||||
const appPath = fileUrl(
|
const appPath = fileUrl(
|
||||||
process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve()
|
process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve()
|
||||||
@@ -46,7 +48,20 @@ const CSS_FILES = [
|
|||||||
`${appPath}/node_modules/react-image-carousel/lib/css/main.min.css`
|
`${appPath}/node_modules/react-image-carousel/lib/css/main.min.css`
|
||||||
]
|
]
|
||||||
|
|
||||||
function buildStyle (
|
/**
|
||||||
|
* @param {Object} opts
|
||||||
|
* @param {String} opts.fontFamily
|
||||||
|
* @param {Numberl} opts.fontSize
|
||||||
|
* @param {String} opts.codeBlockFontFamily
|
||||||
|
* @param {String} opts.theme
|
||||||
|
* @param {Boolean} [opts.lineNumber] Should show line number
|
||||||
|
* @param {Boolean} [opts.scrollPastEnd]
|
||||||
|
* @param {Boolean} [opts.allowCustomCSS] Should add custom css
|
||||||
|
* @param {String} [opts.customCSS] Will be added to bottom, only if `opts.allowCustomCSS` is truthy
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
function buildStyle(opts) {
|
||||||
|
const {
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize,
|
fontSize,
|
||||||
codeBlockFontFamily,
|
codeBlockFontFamily,
|
||||||
@@ -54,8 +69,9 @@ function buildStyle (
|
|||||||
scrollPastEnd,
|
scrollPastEnd,
|
||||||
theme,
|
theme,
|
||||||
allowCustomCSS,
|
allowCustomCSS,
|
||||||
customCSS
|
customCSS,
|
||||||
) {
|
RTL
|
||||||
|
} = opts
|
||||||
return `
|
return `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Lato';
|
font-family: 'Lato';
|
||||||
@@ -85,12 +101,23 @@ function buildStyle (
|
|||||||
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
|
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
|
||||||
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
|
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
${markdownStyle}
|
${markdownStyle}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: '${fontFamily.join("','")}';
|
font-family: '${fontFamily.join("','")}';
|
||||||
font-size: ${fontSize}px;
|
font-size: ${fontSize}px;
|
||||||
${scrollPastEnd && 'padding-bottom: 90vh;'}
|
|
||||||
|
${
|
||||||
|
scrollPastEnd
|
||||||
|
? `
|
||||||
|
padding-bottom: 90vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
${RTL ? 'direction: rtl;' : ''}
|
||||||
|
${RTL ? 'text-align: right;' : ''}
|
||||||
}
|
}
|
||||||
@media print {
|
@media print {
|
||||||
body {
|
body {
|
||||||
@@ -100,7 +127,84 @@ body {
|
|||||||
code {
|
code {
|
||||||
font-family: '${codeBlockFontFamily.join("','")}';
|
font-family: '${codeBlockFontFamily.join("','")}';
|
||||||
background-color: rgba(0,0,0,0.04);
|
background-color: rgba(0,0,0,0.04);
|
||||||
|
text-align: left;
|
||||||
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p code,
|
||||||
|
li code,
|
||||||
|
td code
|
||||||
|
{
|
||||||
|
padding: 2px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
[data-theme="default"] p code,
|
||||||
|
[data-theme="default"] li code,
|
||||||
|
[data-theme="default"] td code
|
||||||
|
{
|
||||||
|
background-color: #F4F4F4;
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
[data-theme="white"] p code,
|
||||||
|
[data-theme="white"] li code,
|
||||||
|
[data-theme="white"] td code
|
||||||
|
{
|
||||||
|
background-color: #F4F4F4;
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] p code,
|
||||||
|
[data-theme="dark"] li code,
|
||||||
|
[data-theme="dark"] td code
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
[data-theme="dracula"] p code,
|
||||||
|
[data-theme="dracula"] li code,
|
||||||
|
[data-theme="dracula"] td code
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
[data-theme="monokai"] p code,
|
||||||
|
[data-theme="monokai"] li code,
|
||||||
|
[data-theme="monokai"] td code
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
[data-theme="nord"] p code,
|
||||||
|
[data-theme="nord"] li code,
|
||||||
|
[data-theme="nord"] td code
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
[data-theme="solarized-dark"] p code,
|
||||||
|
[data-theme="solarized-dark"] li code,
|
||||||
|
[data-theme="solarized-dark"] td code
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
[data-theme="vulcan"] p code,
|
||||||
|
[data-theme="vulcan"] li code,
|
||||||
|
[data-theme="vulcan"] td code
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
.lineNumber {
|
.lineNumber {
|
||||||
${lineNumber && 'display: block !important;'}
|
${lineNumber && 'display: block !important;'}
|
||||||
font-family: '${codeBlockFontFamily.join("','")}';
|
font-family: '${codeBlockFontFamily.join("','")}';
|
||||||
@@ -130,14 +234,22 @@ h1, h2 {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 1em 0 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4, h5, h6 {
|
||||||
|
margin: 1.1em 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
padding-bottom: 4px;
|
padding: 0.2em 0 0.2em;
|
||||||
margin: 1em 0 8px;
|
margin: 1em 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
padding-bottom: 0.2em;
|
padding: 0.2em 0 0.2em;
|
||||||
margin: 1em 0 0.37em;
|
margin: 1em 0 0.7em;
|
||||||
}
|
}
|
||||||
|
|
||||||
body p {
|
body p {
|
||||||
@@ -160,21 +272,33 @@ ${allowCustomCSS ? customCSS : ''}
|
|||||||
|
|
||||||
const scrollBarStyle = `
|
const scrollBarStyle = `
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
|
${config.get().ui.showScrollBar ? '' : 'display: none;'}
|
||||||
width: 12px;
|
width: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
|
${config.get().ui.showScrollBar ? '' : 'display: none;'}
|
||||||
background-color: rgba(0, 0, 0, 0.15);
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track-piece {
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
const scrollBarDarkStyle = `
|
const scrollBarDarkStyle = `
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
|
${config.get().ui.showScrollBar ? '' : 'display: none;'}
|
||||||
width: 12px;
|
width: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
|
${config.get().ui.showScrollBar ? '' : 'display: none;'}
|
||||||
background-color: rgba(0, 0, 0, 0.3);
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track-piece {
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
@@ -192,7 +316,20 @@ const defaultCodeBlockFontFamily = [
|
|||||||
'source-code-pro',
|
'source-code-pro',
|
||||||
'monospace'
|
'monospace'
|
||||||
]
|
]
|
||||||
export default class MarkdownPreview extends React.Component {
|
|
||||||
|
// return the line number of the line that used to generate the specified element
|
||||||
|
// return -1 if the line is not found
|
||||||
|
function getSourceLineNumberByElement(element) {
|
||||||
|
let isHasLineNumber = element.dataset.line !== undefined
|
||||||
|
let parent = element
|
||||||
|
while (!isHasLineNumber && parent.parentElement !== null) {
|
||||||
|
parent = parent.parentElement
|
||||||
|
isHasLineNumber = parent.dataset.line !== undefined
|
||||||
|
}
|
||||||
|
return parent.dataset.line !== undefined ? parseInt(parent.dataset.line) : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
class MarkdownPreview extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
@@ -208,7 +345,9 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.saveAsTextHandler = () => this.handleSaveAsText()
|
this.saveAsTextHandler = () => this.handleSaveAsText()
|
||||||
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
||||||
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
|
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
|
||||||
|
this.saveAsPdfHandler = () => this.handleSaveAsPdf()
|
||||||
this.printHandler = () => this.handlePrint()
|
this.printHandler = () => this.handlePrint()
|
||||||
|
this.resizeHandler = _.throttle(this.handleResize.bind(this), 100)
|
||||||
|
|
||||||
this.linkClickHandler = this.handleLinkClick.bind(this)
|
this.linkClickHandler = this.handleLinkClick.bind(this)
|
||||||
this.initMarkdown = this.initMarkdown.bind(this)
|
this.initMarkdown = this.initMarkdown.bind(this)
|
||||||
@@ -235,30 +374,12 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleContextMenu(event) {
|
handleContextMenu(event) {
|
||||||
// If a contextMenu handler was passed to us, use it instead of the self-defined one -> return
|
const menu = buildMarkdownPreviewContextMenu(this, event)
|
||||||
if (_.isFunction(this.props.onContextMenu)) {
|
const switchPreview = ConfigManager.get().editor.switchPreview
|
||||||
|
if (menu != null && switchPreview !== 'RIGHTCLICK') {
|
||||||
|
menu.popup(remote.getCurrentWindow())
|
||||||
|
} else if (_.isFunction(this.props.onContextMenu)) {
|
||||||
this.props.onContextMenu(event)
|
this.props.onContextMenu(event)
|
||||||
return
|
|
||||||
}
|
|
||||||
// No contextMenu was passed to us -> execute our own link-opener
|
|
||||||
if (event.target.tagName.toLowerCase() === 'a') {
|
|
||||||
const href = event.target.href
|
|
||||||
const isLocalFile = href.startsWith('file:')
|
|
||||||
if (isLocalFile) {
|
|
||||||
const absPath = uri2path(href)
|
|
||||||
try {
|
|
||||||
if (fs.lstatSync(absPath).isFile()) {
|
|
||||||
context.popup([
|
|
||||||
{
|
|
||||||
label: i18n.__('Show in explorer'),
|
|
||||||
click: (e) => shell.showItemInFolder(absPath)
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Error while evaluating if the file is locally available', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,17 +389,32 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
handleMouseDown(e) {
|
handleMouseDown(e) {
|
||||||
const config = ConfigManager.get()
|
const config = ConfigManager.get()
|
||||||
if (config.editor.switchPreview === 'RIGHTCLICK' && e.buttons === 2 && config.editor.type === 'SPLIT') {
|
const clickElement = e.target
|
||||||
|
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.
|
||||||
|
|
||||||
|
if (
|
||||||
|
config.editor.switchPreview === 'RIGHTCLICK' &&
|
||||||
|
e.buttons === 2 &&
|
||||||
|
config.editor.type === 'SPLIT'
|
||||||
|
) {
|
||||||
eventEmitter.emit('topbar:togglemodebutton', 'CODE')
|
eventEmitter.emit('topbar:togglemodebutton', 'CODE')
|
||||||
}
|
}
|
||||||
if (e.target != null) {
|
if (e.ctrlKey) {
|
||||||
switch (e.target.tagName) {
|
if (config.editor.type === 'SPLIT') {
|
||||||
case 'A':
|
if (lineNumber !== -1) {
|
||||||
case 'INPUT':
|
eventEmitter.emit('line:jump', lineNumber)
|
||||||
return null
|
}
|
||||||
|
} else {
|
||||||
|
if (lineNumber !== -1) {
|
||||||
|
eventEmitter.emit('editor:focus')
|
||||||
|
eventEmitter.emit('line:jump', lineNumber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.props.onMouseDown != null) this.props.onMouseDown(e)
|
}
|
||||||
|
|
||||||
|
if (this.props.onMouseDown != null && targetTag === 'BODY')
|
||||||
|
this.props.onMouseDown(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp(e) {
|
handleMouseUp(e) {
|
||||||
@@ -297,8 +433,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.exportAsDocument('md')
|
this.exportAsDocument('md')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsHtml () {
|
htmlContentFormatter(noteContent, exportTasks, targetDir) {
|
||||||
this.exportAsDocument('html', (noteContent, exportTasks) => {
|
|
||||||
const {
|
const {
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize,
|
fontSize,
|
||||||
@@ -308,10 +443,11 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
scrollPastEnd,
|
scrollPastEnd,
|
||||||
theme,
|
theme,
|
||||||
allowCustomCSS,
|
allowCustomCSS,
|
||||||
customCSS
|
customCSS,
|
||||||
|
RTL
|
||||||
} = this.getStyleParams()
|
} = this.getStyleParams()
|
||||||
|
|
||||||
const inlineStyles = buildStyle(
|
const inlineStyles = buildStyle({
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize,
|
fontSize,
|
||||||
codeBlockFontFamily,
|
codeBlockFontFamily,
|
||||||
@@ -319,10 +455,12 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
scrollPastEnd,
|
scrollPastEnd,
|
||||||
theme,
|
theme,
|
||||||
allowCustomCSS,
|
allowCustomCSS,
|
||||||
customCSS
|
customCSS,
|
||||||
)
|
RTL
|
||||||
let body = this.markdown.render(noteContent)
|
})
|
||||||
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
let body = this.refs.root.contentWindow.document.body.innerHTML
|
||||||
|
body = attachmentManagement.fixLocalURLS(body, this.props.storagePath)
|
||||||
|
const files = [this.getCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
if (global.process.platform === 'win32') {
|
if (global.process.platform === 'win32') {
|
||||||
file = file.replace('file:///', '')
|
file = file.replace('file:///', '')
|
||||||
@@ -337,11 +475,12 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
let styles = ''
|
let styles = ''
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
|
styles += `<link rel="stylesheet" href="../css/${path.basename(file)}">`
|
||||||
})
|
})
|
||||||
|
|
||||||
return `<html>
|
return `<html>
|
||||||
<head>
|
<head>
|
||||||
|
<base href="file://${targetDir}/">
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
|
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
|
||||||
<style id="style">${inlineStyles}</style>
|
<style id="style">${inlineStyles}</style>
|
||||||
@@ -349,6 +488,35 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
</head>
|
</head>
|
||||||
<body>${body}</body>
|
<body>${body}</body>
|
||||||
</html>`
|
</html>`
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSaveAsHtml() {
|
||||||
|
this.exportAsDocument('html', (noteContent, exportTasks, targetDir) =>
|
||||||
|
Promise.resolve(
|
||||||
|
this.htmlContentFormatter(noteContent, exportTasks, targetDir)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSaveAsPdf() {
|
||||||
|
this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => {
|
||||||
|
const printout = new remote.BrowserWindow({
|
||||||
|
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) => {
|
||||||
|
printout.webContents.on('did-finish-load', () => {
|
||||||
|
printout.webContents.printToPDF({}, (err, data) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
else resolve(data)
|
||||||
|
printout.destroy()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,7 +540,8 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
.then(res => {
|
.then(res => {
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
message: `Exported to ${filename}`
|
message: `Exported to ${filename}`,
|
||||||
|
buttons: [i18n.__('Ok')]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
@@ -398,18 +567,38 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Convert special characters between three ```
|
||||||
|
* @param {string[]} splitWithCodeTag Array of HTML strings separated by three ```
|
||||||
|
* @returns {string} HTML in which special characters between three ``` have been converted
|
||||||
|
*/
|
||||||
|
escapeHtmlCharactersInCodeTag(splitWithCodeTag) {
|
||||||
|
for (let index = 0; index < splitWithCodeTag.length; index++) {
|
||||||
|
const codeTagRequired =
|
||||||
|
splitWithCodeTag[index] !== '```' && index < splitWithCodeTag.length - 1
|
||||||
|
if (codeTagRequired) {
|
||||||
|
splitWithCodeTag.splice(index + 1, 0, '```')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let inCodeTag = false
|
||||||
|
let result = ''
|
||||||
|
for (let content of splitWithCodeTag) {
|
||||||
|
if (content === '```') {
|
||||||
|
inCodeTag = !inCodeTag
|
||||||
|
} else if (inCodeTag) {
|
||||||
|
content = escapeHtmlCharacters(content)
|
||||||
|
}
|
||||||
|
result += content
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
getScrollBarStyle() {
|
getScrollBarStyle() {
|
||||||
const { theme } = this.props
|
const { theme } = this.props
|
||||||
|
|
||||||
switch (theme) {
|
return uiThemes.some(item => item.name === theme && item.isDark)
|
||||||
case 'dark':
|
? scrollBarDarkStyle
|
||||||
case 'solarized-dark':
|
: scrollBarStyle
|
||||||
case 'monokai':
|
|
||||||
case 'dracula':
|
|
||||||
return scrollBarDarkStyle
|
|
||||||
default:
|
|
||||||
return scrollBarStyle
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -462,9 +651,11 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
'scroll',
|
'scroll',
|
||||||
this.scrollHandler
|
this.scrollHandler
|
||||||
)
|
)
|
||||||
|
this.refs.root.contentWindow.addEventListener('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)
|
||||||
|
eventEmitter.on('export:save-pdf', this.saveAsPdfHandler)
|
||||||
eventEmitter.on('print', this.printHandler)
|
eventEmitter.on('print', this.printHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,23 +690,31 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
'scroll',
|
'scroll',
|
||||||
this.scrollHandler
|
this.scrollHandler
|
||||||
)
|
)
|
||||||
|
this.refs.root.contentWindow.removeEventListener(
|
||||||
|
'resize',
|
||||||
|
this.resizeHandler
|
||||||
|
)
|
||||||
eventEmitter.off('export:save-text', this.saveAsTextHandler)
|
eventEmitter.off('export:save-text', this.saveAsTextHandler)
|
||||||
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
||||||
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
|
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
|
||||||
|
eventEmitter.off('export:save-pdf', this.saveAsPdfHandler)
|
||||||
eventEmitter.off('print', this.printHandler)
|
eventEmitter.off('print', this.printHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
// actual rewriteIframe function should be called only once
|
||||||
|
let needsRewriteIframe = false
|
||||||
|
if (prevProps.value !== this.props.value) needsRewriteIframe = true
|
||||||
if (
|
if (
|
||||||
prevProps.smartQuotes !== this.props.smartQuotes ||
|
prevProps.smartQuotes !== this.props.smartQuotes ||
|
||||||
prevProps.sanitize !== this.props.sanitize ||
|
prevProps.sanitize !== this.props.sanitize ||
|
||||||
|
prevProps.mermaidHTMLLabel !== this.props.mermaidHTMLLabel ||
|
||||||
prevProps.smartArrows !== this.props.smartArrows ||
|
prevProps.smartArrows !== this.props.smartArrows ||
|
||||||
prevProps.breaks !== this.props.breaks ||
|
prevProps.breaks !== this.props.breaks ||
|
||||||
prevProps.lineThroughCheckbox !== this.props.lineThroughCheckbox
|
prevProps.lineThroughCheckbox !== this.props.lineThroughCheckbox
|
||||||
) {
|
) {
|
||||||
this.initMarkdown()
|
this.initMarkdown()
|
||||||
this.rewriteIframe()
|
needsRewriteIframe = true
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
prevProps.fontFamily !== this.props.fontFamily ||
|
prevProps.fontFamily !== this.props.fontFamily ||
|
||||||
@@ -527,11 +726,21 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
prevProps.theme !== this.props.theme ||
|
prevProps.theme !== this.props.theme ||
|
||||||
prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
|
prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
|
||||||
prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
|
prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
|
||||||
prevProps.customCSS !== this.props.customCSS
|
prevProps.customCSS !== this.props.customCSS ||
|
||||||
|
prevProps.RTL !== this.props.RTL
|
||||||
) {
|
) {
|
||||||
this.applyStyle()
|
this.applyStyle()
|
||||||
|
needsRewriteIframe = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsRewriteIframe) {
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Should scroll to top after selecting another note
|
||||||
|
if (prevProps.noteKey !== this.props.noteKey) {
|
||||||
|
this.scrollTo(0, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getStyleParams() {
|
getStyleParams() {
|
||||||
@@ -542,17 +751,19 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
scrollPastEnd,
|
scrollPastEnd,
|
||||||
theme,
|
theme,
|
||||||
allowCustomCSS,
|
allowCustomCSS,
|
||||||
customCSS
|
customCSS,
|
||||||
|
RTL
|
||||||
} = this.props
|
} = this.props
|
||||||
let { fontFamily, codeBlockFontFamily } = this.props
|
let { fontFamily, codeBlockFontFamily } = this.props
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
fontFamily =
|
||||||
|
_.isString(fontFamily) && fontFamily.trim().length > 0
|
||||||
? fontFamily
|
? fontFamily
|
||||||
.split(',')
|
.split(',')
|
||||||
.map(fontName => fontName.trim())
|
.map(fontName => fontName.trim())
|
||||||
.concat(defaultFontFamily)
|
.concat(defaultFontFamily)
|
||||||
: defaultFontFamily
|
: defaultFontFamily
|
||||||
codeBlockFontFamily = _.isString(codeBlockFontFamily) &&
|
codeBlockFontFamily =
|
||||||
codeBlockFontFamily.trim().length > 0
|
_.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0
|
||||||
? codeBlockFontFamily
|
? codeBlockFontFamily
|
||||||
.split(',')
|
.split(',')
|
||||||
.map(fontName => fontName.trim())
|
.map(fontName => fontName.trim())
|
||||||
@@ -568,7 +779,8 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
scrollPastEnd,
|
scrollPastEnd,
|
||||||
theme,
|
theme,
|
||||||
allowCustomCSS,
|
allowCustomCSS,
|
||||||
customCSS
|
customCSS,
|
||||||
|
RTL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,13 +794,14 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
scrollPastEnd,
|
scrollPastEnd,
|
||||||
theme,
|
theme,
|
||||||
allowCustomCSS,
|
allowCustomCSS,
|
||||||
customCSS
|
customCSS,
|
||||||
|
RTL
|
||||||
} = this.getStyleParams()
|
} = this.getStyleParams()
|
||||||
|
|
||||||
this.getWindow().document.getElementById(
|
this.getWindow().document.getElementById(
|
||||||
'codeTheme'
|
'codeTheme'
|
||||||
).href = this.GetCodeThemeLink(codeBlockTheme)
|
).href = this.getCodeThemeLink(codeBlockTheme)
|
||||||
this.getWindow().document.getElementById('style').innerHTML = buildStyle(
|
this.getWindow().document.getElementById('style').innerHTML = buildStyle({
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize,
|
fontSize,
|
||||||
codeBlockFontFamily,
|
codeBlockFontFamily,
|
||||||
@@ -596,18 +809,17 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
scrollPastEnd,
|
scrollPastEnd,
|
||||||
theme,
|
theme,
|
||||||
allowCustomCSS,
|
allowCustomCSS,
|
||||||
customCSS
|
customCSS,
|
||||||
)
|
RTL
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
GetCodeThemeLink (theme) {
|
getCodeThemeLink(name) {
|
||||||
theme = consts.THEMES.some(_theme => _theme === theme) &&
|
const theme = consts.THEMES.find(theme => theme.name === name)
|
||||||
theme !== 'default'
|
|
||||||
? theme
|
return theme != null
|
||||||
: 'elegant'
|
? theme.path
|
||||||
return theme.startsWith('solarized')
|
: `${appPath}/node_modules/codemirror/theme/elegant.css`
|
||||||
? `${appPath}/node_modules/codemirror/theme/solarized.css`
|
|
||||||
: `${appPath}/node_modules/codemirror/theme/${theme}.css`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rewriteIframe() {
|
rewriteIframe() {
|
||||||
@@ -632,11 +844,17 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
indentSize,
|
indentSize,
|
||||||
showCopyNotification,
|
showCopyNotification,
|
||||||
storagePath,
|
storagePath,
|
||||||
noteKey
|
noteKey,
|
||||||
|
sanitize,
|
||||||
|
mermaidHTMLLabel
|
||||||
} = this.props
|
} = this.props
|
||||||
let { value, codeBlockTheme } = this.props
|
let { value, codeBlockTheme } = this.props
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
||||||
|
if (sanitize === 'NONE') {
|
||||||
|
const splitWithCodeTag = value.split('```')
|
||||||
|
value = this.escapeHtmlCharactersInCodeTag(splitWithCodeTag)
|
||||||
|
}
|
||||||
const renderedHTML = this.markdown.render(value)
|
const renderedHTML = this.markdown.render(value)
|
||||||
attachmentManagement.migrateAttachments(value, storagePath, noteKey)
|
attachmentManagement.migrateAttachments(value, storagePath, noteKey)
|
||||||
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(
|
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(
|
||||||
@@ -660,9 +878,11 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
codeBlockTheme = consts.THEMES.some(_theme => _theme === codeBlockTheme)
|
codeBlockTheme = consts.THEMES.find(theme => theme.name === codeBlockTheme)
|
||||||
? codeBlockTheme
|
|
||||||
: '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'),
|
||||||
@@ -675,6 +895,8 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
copyIcon.innerHTML =
|
copyIcon.innerHTML =
|
||||||
'<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
|
'<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
|
||||||
copyIcon.onclick = e => {
|
copyIcon.onclick = e => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
copy(content)
|
copy(content)
|
||||||
if (showCopyNotification) {
|
if (showCopyNotification) {
|
||||||
this.notify('Saved to Clipboard!', {
|
this.notify('Saved to Clipboard!', {
|
||||||
@@ -683,14 +905,11 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
el.parentNode.appendChild(copyIcon)
|
el.parentNode.appendChild(copyIcon)
|
||||||
el.innerHTML = ''
|
el.innerHTML = ''
|
||||||
if (codeBlockTheme.indexOf('solarized') === 0) {
|
el.parentNode.className += ` ${codeBlockThemeClassName}`
|
||||||
const [refThema, color] = codeBlockTheme.split(' ')
|
|
||||||
el.parentNode.className += ` cm-s-${refThema} cm-s-${color}`
|
|
||||||
} else {
|
|
||||||
el.parentNode.className += ` cm-s-${codeBlockTheme}`
|
|
||||||
}
|
|
||||||
CodeMirror.runMode(content, syntax.mime, el, {
|
CodeMirror.runMode(content, syntax.mime, el, {
|
||||||
tabSize: indentSize
|
tabSize: indentSize
|
||||||
})
|
})
|
||||||
@@ -749,7 +968,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')
|
||||||
@@ -761,6 +983,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
canvas.height = height.value + 'vh'
|
canvas.height = height.value + 'vh'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
const chart = new Chart(canvas, chartConfig)
|
const chart = new Chart(canvas, chartConfig)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
el.className = 'chart-error'
|
el.className = 'chart-error'
|
||||||
@@ -771,7 +994,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)
|
mermaidRender(
|
||||||
|
el,
|
||||||
|
htmlTextHelper.decodeEntities(el.innerHTML),
|
||||||
|
theme,
|
||||||
|
mermaidHTMLLabel
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -793,13 +1021,116 @@ 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 rect = markdownPreviewIframe.getBoundingClientRect()
|
||||||
|
const config = { attributes: true, subtree: true }
|
||||||
|
const imgObserver = new MutationObserver(mutationList => {
|
||||||
|
for (const mu of mutationList) {
|
||||||
|
if (mu.target.className === 'carouselContent-enter-done') {
|
||||||
|
this.setImgOnClickEventHelper(mu.target, rect)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll(
|
||||||
|
'img'
|
||||||
|
)
|
||||||
|
for (const img of imgList) {
|
||||||
|
const parentEl = img.parentElement
|
||||||
|
this.setImgOnClickEventHelper(img, rect)
|
||||||
|
imgObserver.observe(parentEl, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
const aList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll(
|
||||||
|
'a'
|
||||||
|
)
|
||||||
|
for (const a of aList) {
|
||||||
|
a.removeEventListener('click', this.linkClickHandler)
|
||||||
|
a.addEventListener('click', this.linkClickHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setImgOnClickEventHelper(img, rect) {
|
||||||
|
img.onclick = () => {
|
||||||
|
const widthMagnification = document.body.clientWidth / img.width
|
||||||
|
const heightMagnification = document.body.clientHeight / img.height
|
||||||
|
const baseOnWidth = widthMagnification < heightMagnification
|
||||||
|
const magnification = baseOnWidth
|
||||||
|
? widthMagnification
|
||||||
|
: heightMagnification
|
||||||
|
|
||||||
|
const zoomImgWidth = img.width * magnification
|
||||||
|
const zoomImgHeight = img.height * magnification
|
||||||
|
const zoomImgTop = (document.body.clientHeight - zoomImgHeight) / 2
|
||||||
|
const zoomImgLeft = (document.body.clientWidth - zoomImgWidth) / 2
|
||||||
|
const originalImgTop = img.y + rect.top
|
||||||
|
const originalImgLeft = img.x + rect.left
|
||||||
|
const originalImgRect = {
|
||||||
|
top: `${originalImgTop}px`,
|
||||||
|
left: `${originalImgLeft}px`,
|
||||||
|
width: `${img.width}px`,
|
||||||
|
height: `${img.height}px`
|
||||||
|
}
|
||||||
|
const zoomInImgRect = {
|
||||||
|
top: `${baseOnWidth ? zoomImgTop : 0}px`,
|
||||||
|
left: `${baseOnWidth ? 0 : zoomImgLeft}px`,
|
||||||
|
width: `${zoomImgWidth}px`,
|
||||||
|
height: `${zoomImgHeight}px`
|
||||||
|
}
|
||||||
|
const animationSpeed = 300
|
||||||
|
|
||||||
|
const zoomImg = document.createElement('img')
|
||||||
|
zoomImg.src = img.src
|
||||||
|
zoomImg.style = `
|
||||||
|
position: absolute;
|
||||||
|
top: ${baseOnWidth ? zoomImgTop : 0}px;
|
||||||
|
left: ${baseOnWidth ? 0 : zoomImgLeft}px;
|
||||||
|
width: ${zoomImgWidth};
|
||||||
|
height: ${zoomImgHeight}px;
|
||||||
|
`
|
||||||
|
zoomImg.animate([originalImgRect, zoomInImgRect], animationSpeed)
|
||||||
|
|
||||||
|
const overlay = document.createElement('div')
|
||||||
|
overlay.style = `
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
cursor: zoom-out;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: ${document.body.clientHeight}px;
|
||||||
|
z-index: 100;
|
||||||
|
`
|
||||||
|
overlay.onclick = () => {
|
||||||
|
zoomImg.style = `
|
||||||
|
position: absolute;
|
||||||
|
top: ${originalImgTop}px;
|
||||||
|
left: ${originalImgLeft}px;
|
||||||
|
width: ${img.width}px;
|
||||||
|
height: ${img.height}px;
|
||||||
|
`
|
||||||
|
const zoomOutImgAnimation = zoomImg.animate(
|
||||||
|
[zoomInImgRect, originalImgRect],
|
||||||
|
animationSpeed
|
||||||
|
)
|
||||||
|
zoomOutImgAnimation.onfinish = () => overlay.remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
overlay.appendChild(zoomImg)
|
||||||
|
document.body.appendChild(overlay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResize() {
|
||||||
|
_.forEach(
|
||||||
|
this.refs.root.contentWindow.document.querySelectorAll('svg[ratio]'),
|
||||||
|
el => {
|
||||||
|
el.setAttribute('height', el.clientWidth / el.getAttribute('ratio'))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -812,7 +1143,11 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
return this.refs.root.contentWindow
|
return this.refs.root.contentWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollTo (targetRow) {
|
/**
|
||||||
|
* @public
|
||||||
|
* @param {Number} targetRow
|
||||||
|
*/
|
||||||
|
scrollToRow(targetRow) {
|
||||||
const blocks = this.getWindow().document.querySelectorAll(
|
const blocks = this.getWindow().document.querySelectorAll(
|
||||||
'body>[data-line]'
|
'body>[data-line]'
|
||||||
)
|
)
|
||||||
@@ -822,12 +1157,21 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
const row = parseInt(block.getAttribute('data-line'))
|
const row = parseInt(block.getAttribute('data-line'))
|
||||||
if (row > targetRow || index === blocks.length - 1) {
|
if (row > targetRow || index === blocks.length - 1) {
|
||||||
block = blocks[index - 1]
|
block = blocks[index - 1]
|
||||||
block != null && this.getWindow().scrollTo(0, block.offsetTop)
|
block != null && this.scrollTo(0, block.offsetTop)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `document.body.scrollTo`
|
||||||
|
* @param {Number} x
|
||||||
|
* @param {Number} y
|
||||||
|
*/
|
||||||
|
scrollTo(x, y) {
|
||||||
|
this.getWindow().document.body.scrollTo(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
preventImageDroppedHandler(e) {
|
preventImageDroppedHandler(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
@@ -848,21 +1192,32 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
const href = e.target.href
|
const rawHref = e.target.getAttribute('href')
|
||||||
const linkHash = href.split('/').pop()
|
const { dispatch } = this.props
|
||||||
|
if (!rawHref) return // not checked href because parser will create file://... string for [empty link]()
|
||||||
|
|
||||||
const regexNoteInternalLink = /main.html#(.+)/
|
const parser = document.createElement('a')
|
||||||
if (regexNoteInternalLink.test(linkHash)) {
|
parser.href = rawHref
|
||||||
const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1])
|
const isStartWithHash = rawHref[0] === '#'
|
||||||
const targetElement = this.refs.root.contentWindow.document.getElementById(
|
const { href, hash } = parser
|
||||||
targetId
|
|
||||||
)
|
const linkHash = hash === '' ? rawHref : hash // needed because we're having special link formats that are removed by parser e.g. :line:10
|
||||||
|
|
||||||
|
const extractIdRegex = /file:\/\/.*main.?\w*.html#/ // file://path/to/main(.development.)html
|
||||||
|
const regexNoteInternalLink = new RegExp(`${extractIdRegex.source}(.+)`)
|
||||||
|
if (isStartWithHash || regexNoteInternalLink.test(rawHref)) {
|
||||||
|
const posOfHash = linkHash.indexOf('#')
|
||||||
|
if (posOfHash > -1) {
|
||||||
|
const extractedId = linkHash.slice(posOfHash + 1)
|
||||||
|
const targetId = mdurl.encode(extractedId)
|
||||||
|
const targetElement = this.getWindow().document.getElementById(targetId)
|
||||||
|
|
||||||
if (targetElement != null) {
|
if (targetElement != null) {
|
||||||
this.getWindow().scrollTo(0, targetElement.offsetTop)
|
this.scrollTo(0, targetElement.offsetTop)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// this will match the new uuid v4 hash and the old hash
|
// this will match the new uuid v4 hash and the old hash
|
||||||
// e.g.
|
// e.g.
|
||||||
@@ -892,8 +1247,26 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const regexIsTagLink = /^:tag:([\w]+)$/
|
||||||
|
if (regexIsTagLink.test(rawHref)) {
|
||||||
|
const tag = rawHref.match(regexIsTagLink)[1]
|
||||||
|
dispatch(push(`/tags/${encodeURIComponent(tag)}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// other case
|
// other case
|
||||||
shell.openExternal(href)
|
this.openExternal(href)
|
||||||
|
}
|
||||||
|
|
||||||
|
openExternal(href) {
|
||||||
|
try {
|
||||||
|
const success =
|
||||||
|
shell.openExternal(href) || shell.openExternal(decodeURI(href))
|
||||||
|
if (!success) console.error('failed to open url ' + href)
|
||||||
|
} catch (e) {
|
||||||
|
// URI Error threw from decodeURI
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -925,3 +1298,10 @@ MarkdownPreview.propTypes = {
|
|||||||
smartArrows: PropTypes.bool,
|
smartArrows: PropTypes.bool,
|
||||||
breaks: PropTypes.bool
|
breaks: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
{ forwardRef: true }
|
||||||
|
)(MarkdownPreview)
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
this.userScroll = true
|
this.userScroll = true
|
||||||
this.state = {
|
this.state = {
|
||||||
isSliderFocused: false,
|
isSliderFocused: false,
|
||||||
codeEditorWidthInPercent: 50
|
codeEditorWidthInPercent: 50,
|
||||||
|
codeEditorHeightInPercent: 50
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +33,10 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
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 +53,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,14 +64,22 @@ 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 =
|
||||||
|
time < 0.5
|
||||||
? 2 * time * time // ease in
|
? 2 * time * time // ease in
|
||||||
: -1 + (4 - 2 * time) * time // ease out
|
: -1 + (4 - 2 * time) * time // ease out
|
||||||
if (e.doc) _.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
|
if (e.doc)
|
||||||
else _.get(this, 'refs.code.editor').scrollTo(0, targetTop + scrollPos * distance)
|
_.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)
|
||||||
@@ -78,33 +90,53 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
const idMatch = /checkbox-([0-9]+)/
|
const idMatch = /checkbox-([0-9]+)/
|
||||||
const checkedMatch = /^\s*[\+\-\*] \[x\]/i
|
const checkedMatch = /^(\s*>?)*\s*[+\-*] \[x]/i
|
||||||
const uncheckedMatch = /^\s*[\+\-\*] \[ \]/
|
const uncheckedMatch = /^(\s*>?)*\s*[+\-*] \[ ]/
|
||||||
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
|
||||||
|
|
||||||
if (targetLine.match(checkedMatch)) {
|
if (targetLine.match(checkedMatch)) {
|
||||||
lines[lineIndex] = targetLine.replace(checkReplace, '[ ]')
|
newLine = targetLine.replace(checkReplace, '[ ]')
|
||||||
}
|
}
|
||||||
if (targetLine.match(uncheckedMatch)) {
|
if (targetLine.match(uncheckedMatch)) {
|
||||||
lines[lineIndex] = targetLine.replace(uncheckReplace, '[x]')
|
newLine = targetLine.replace(uncheckReplace, '[x]')
|
||||||
}
|
}
|
||||||
this.refs.code.setValue(lines.join('\n'))
|
this.refs.code.setLineContent(lineIndex, newLine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseMove(e) {
|
handleMouseMove(e) {
|
||||||
if (this.state.isSliderFocused) {
|
if (this.state.isSliderFocused) {
|
||||||
const rootRect = this.refs.root.getBoundingClientRect()
|
const rootRect = this.refs.root.getBoundingClientRect()
|
||||||
|
if (this.props.isStacking) {
|
||||||
|
const rootHeight = rootRect.height
|
||||||
|
const offset = rootRect.top
|
||||||
|
let newCodeEditorHeightInPercent =
|
||||||
|
((e.pageY - offset) / rootHeight) * 100
|
||||||
|
|
||||||
|
// limit minSize to 10%, maxSize to 90%
|
||||||
|
if (newCodeEditorHeightInPercent <= 10) {
|
||||||
|
newCodeEditorHeightInPercent = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newCodeEditorHeightInPercent >= 90) {
|
||||||
|
newCodeEditorHeightInPercent = 90
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
codeEditorHeightInPercent: newCodeEditorHeightInPercent
|
||||||
|
})
|
||||||
|
} else {
|
||||||
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) {
|
||||||
@@ -120,6 +152,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleMouseUp(e) {
|
handleMouseUp(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -136,38 +169,108 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {config, value, storageKey, noteKey, linesHighlighted} = this.props
|
const {
|
||||||
const storage = findStorage(storageKey)
|
config,
|
||||||
|
value,
|
||||||
|
storageKey,
|
||||||
|
noteKey,
|
||||||
|
linesHighlighted,
|
||||||
|
isStacking,
|
||||||
|
RTL
|
||||||
|
} = this.props
|
||||||
|
let storage
|
||||||
|
try {
|
||||||
|
storage = findStorage(storageKey)
|
||||||
|
} catch (e) {
|
||||||
|
return <div />
|
||||||
|
}
|
||||||
|
|
||||||
|
let editorStyle = {}
|
||||||
|
let previewStyle = {}
|
||||||
|
let sliderStyle = {}
|
||||||
|
|
||||||
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
|
||||||
|
editorStyle.fontSize = editorFontSize
|
||||||
|
|
||||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
if (!(editorStyle.fontSize > 0 && editorStyle.fontSize < 132))
|
||||||
const previewStyle = {}
|
editorIndentSize = 4
|
||||||
previewStyle.width = (100 - this.state.codeEditorWidthInPercent) + '%'
|
editorStyle.indentSize = editorIndentSize
|
||||||
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused) previewStyle.pointerEvents = 'none'
|
|
||||||
|
editorStyle = Object.assign(
|
||||||
|
editorStyle,
|
||||||
|
isStacking
|
||||||
|
? {
|
||||||
|
width: '100%',
|
||||||
|
height: `${this.state.codeEditorHeightInPercent}%`
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
width: `${this.state.codeEditorWidthInPercent}%`,
|
||||||
|
height: '100%'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
previewStyle = Object.assign(
|
||||||
|
previewStyle,
|
||||||
|
isStacking
|
||||||
|
? {
|
||||||
|
width: '100%',
|
||||||
|
height: `${100 - this.state.codeEditorHeightInPercent}%`
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
width: `${100 - this.state.codeEditorWidthInPercent}%`,
|
||||||
|
height: '100%'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
sliderStyle = Object.assign(
|
||||||
|
sliderStyle,
|
||||||
|
isStacking
|
||||||
|
? {
|
||||||
|
left: 0,
|
||||||
|
top: `${this.state.codeEditorHeightInPercent}%`
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
left: `${this.state.codeEditorWidthInPercent}%`,
|
||||||
|
top: 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
styleName='codeEditor'
|
|
||||||
ref='code'
|
ref='code'
|
||||||
width={this.state.codeEditorWidthInPercent + '%'}
|
width={editorStyle.width}
|
||||||
|
height={editorStyle.height}
|
||||||
mode='Boost Flavored Markdown'
|
mode='Boost Flavored Markdown'
|
||||||
value={value}
|
value={value}
|
||||||
theme={config.editor.theme}
|
theme={config.editor.theme}
|
||||||
keyMap={config.editor.keyMap}
|
keyMap={config.editor.keyMap}
|
||||||
fontFamily={config.editor.fontFamily}
|
fontFamily={config.editor.fontFamily}
|
||||||
fontSize={editorFontSize}
|
fontSize={editorStyle.fontSize}
|
||||||
displayLineNumbers={config.editor.displayLineNumbers}
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
|
lineWrapping
|
||||||
matchingPairs={config.editor.matchingPairs}
|
matchingPairs={config.editor.matchingPairs}
|
||||||
|
matchingCloseBefore={config.editor.matchingCloseBefore}
|
||||||
matchingTriples={config.editor.matchingTriples}
|
matchingTriples={config.editor.matchingTriples}
|
||||||
explodingPairs={config.editor.explodingPairs}
|
explodingPairs={config.editor.explodingPairs}
|
||||||
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
||||||
|
codeBlockMatchingCloseBefore={
|
||||||
|
config.editor.codeBlockMatchingCloseBefore
|
||||||
|
}
|
||||||
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
||||||
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
||||||
indentType={config.editor.indentType}
|
indentType={config.editor.indentType}
|
||||||
indentSize={editorIndentSize}
|
indentSize={editorStyle.indentSize}
|
||||||
enableRulers={config.editor.enableRulers}
|
enableRulers={config.editor.enableRulers}
|
||||||
rulers={config.editor.rulers}
|
rulers={config.editor.rulers}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
@@ -176,19 +279,27 @@ 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}
|
||||||
hotkey={config.hotkey}
|
hotkey={config.hotkey}
|
||||||
switchPreview={config.editor.switchPreview}
|
switchPreview={config.editor.switchPreview}
|
||||||
|
enableMarkdownLint={config.editor.enableMarkdownLint}
|
||||||
|
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
||||||
|
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
||||||
|
RTL={RTL}
|
||||||
/>
|
/>
|
||||||
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
<div
|
||||||
|
styleName={isStacking ? 'slider-hoz' : 'slider'}
|
||||||
|
style={{ left: sliderStyle.left, top: sliderStyle.top }}
|
||||||
|
onMouseDown={e => this.handleMouseDown(e)}
|
||||||
|
>
|
||||||
<div styleName='slider-hitbox' />
|
<div styleName='slider-hitbox' />
|
||||||
</div>
|
</div>
|
||||||
<MarkdownPreview
|
<MarkdownPreview
|
||||||
|
ref='preview'
|
||||||
style={previewStyle}
|
style={previewStyle}
|
||||||
styleName='preview'
|
|
||||||
theme={config.ui.theme}
|
theme={config.ui.theme}
|
||||||
keyMap={config.editor.keyMap}
|
keyMap={config.editor.keyMap}
|
||||||
fontSize={config.preview.fontSize}
|
fontSize={config.preview.fontSize}
|
||||||
@@ -201,10 +312,10 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
smartArrows={config.preview.smartArrows}
|
smartArrows={config.preview.smartArrows}
|
||||||
breaks={config.preview.breaks}
|
breaks={config.preview.breaks}
|
||||||
sanitize={config.preview.sanitize}
|
sanitize={config.preview.sanitize}
|
||||||
ref='preview'
|
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
|
||||||
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}
|
||||||
@@ -212,6 +323,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
customCSS={config.preview.customCSS}
|
customCSS={config.preview.customCSS}
|
||||||
allowCustomCSS={config.preview.allowCustomCSS}
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||||
|
RTL={RTL}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,14 +3,36 @@
|
|||||||
height 100%
|
height 100%
|
||||||
font-size 30px
|
font-size 30px
|
||||||
display flex
|
display flex
|
||||||
|
flex-wrap wrap
|
||||||
.slider
|
.slider
|
||||||
absolute top bottom
|
absolute top bottom
|
||||||
top -2px
|
top -2px
|
||||||
width 0
|
width 0
|
||||||
z-index 0
|
z-index 0
|
||||||
|
border-left 1px solid $ui-borderColor
|
||||||
.slider-hitbox
|
.slider-hitbox
|
||||||
absolute top bottom left right
|
absolute top bottom left right
|
||||||
width 7px
|
width 7px
|
||||||
left -3px
|
left -3px
|
||||||
z-index 10
|
z-index 10
|
||||||
cursor col-resize
|
cursor col-resize
|
||||||
|
.slider-hoz
|
||||||
|
absolute left right
|
||||||
|
.slider-hitbox
|
||||||
|
absolute left right
|
||||||
|
width: 100%
|
||||||
|
height 7px
|
||||||
|
cursor row-resize
|
||||||
|
|
||||||
|
|
||||||
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
|
.root
|
||||||
|
.slider
|
||||||
|
border-left 1px solid get-theme-var(theme, 'borderColor')
|
||||||
|
|
||||||
|
for theme in 'dark' 'dracula' 'solarized-dark'
|
||||||
|
apply-theme(theme)
|
||||||
|
|
||||||
|
for theme in $themes
|
||||||
|
apply-theme(theme)
|
||||||
@@ -3,12 +3,10 @@ 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 styleName='esc-text'>esc</div>
|
<div>esc</div>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,12 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
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' />
|
<i className='fa fa-angle-double-left fa-2x' />
|
||||||
: <i className='fa fa-angle-double-left' />
|
)}
|
||||||
}
|
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
border-radius 16.5px
|
border-radius 16.5px
|
||||||
height 34px
|
height 34px
|
||||||
width 34px
|
width 34px
|
||||||
line-height 32px
|
line-height 100%
|
||||||
padding 0
|
padding 0
|
||||||
&:hover
|
&:hover
|
||||||
border: 1px solid #1EC38B;
|
border: 1px solid #1EC38B;
|
||||||
@@ -17,10 +17,16 @@
|
|||||||
body[data-theme="white"]
|
body[data-theme="white"]
|
||||||
navWhiteButtonColor()
|
navWhiteButtonColor()
|
||||||
|
|
||||||
body[data-theme="dark"]
|
apply-theme(theme)
|
||||||
.navToggle
|
body[data-theme={theme}]
|
||||||
&:hover
|
.navToggle:hover
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 20%)
|
||||||
|
border 1px solid get-theme-var(theme, 'button--active-backgroundColor')
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
|
for theme in 'dark' 'dracula' 'solarized-dark'
|
||||||
|
apply-theme(theme)
|
||||||
|
|
||||||
|
for theme in $themes
|
||||||
|
apply-theme(theme)
|
||||||
@@ -3,7 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { isArray } from 'lodash'
|
import { isArray, sortBy } from 'lodash'
|
||||||
|
import invertColor from 'invert-color'
|
||||||
|
import Emoji from 'react-emoji-render'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import { getTodoStatus } from 'browser/lib/getTodoStatus'
|
import { getTodoStatus } from 'browser/lib/getTodoStatus'
|
||||||
import styles from './NoteItem.styl'
|
import styles from './NoteItem.styl'
|
||||||
@@ -13,29 +15,46 @@ import i18n from 'browser/lib/i18n'
|
|||||||
/**
|
/**
|
||||||
* @description Tag element component.
|
* @description Tag element component.
|
||||||
* @param {string} tagName
|
* @param {string} tagName
|
||||||
|
* @param {string} color
|
||||||
* @return {React.Component}
|
* @return {React.Component}
|
||||||
*/
|
*/
|
||||||
const TagElement = ({ tagName }) => (
|
const TagElement = ({ tagName, color }) => {
|
||||||
<span styleName='item-bottom-tagList-item' key={tagName}>
|
const style = {}
|
||||||
|
if (color) {
|
||||||
|
style.backgroundColor = color
|
||||||
|
style.color = invertColor(color, {
|
||||||
|
black: '#222',
|
||||||
|
white: '#f1f1f1',
|
||||||
|
threshold: 0.3
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span styleName='item-bottom-tagList-item' key={tagName} style={style}>
|
||||||
#{tagName}
|
#{tagName}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Tag element list component.
|
* @description Tag element list component.
|
||||||
* @param {Array|null} tags
|
* @param {Array|null} tags
|
||||||
* @param {boolean} showTagsAlphabetically
|
* @param {boolean} showTagsAlphabetically
|
||||||
|
* @param {Object} coloredTags
|
||||||
* @return {React.Component}
|
* @return {React.Component}
|
||||||
*/
|
*/
|
||||||
const TagElementList = (tags, showTagsAlphabetically) => {
|
const TagElementList = (tags, showTagsAlphabetically, coloredTags) => {
|
||||||
if (!isArray(tags)) {
|
if (!isArray(tags)) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showTagsAlphabetically) {
|
if (showTagsAlphabetically) {
|
||||||
return _.sortBy(tags).map(tag => TagElement({ tagName: tag }))
|
return sortBy(tags).map(tag =>
|
||||||
|
TagElement({ tagName: tag, color: coloredTags[tag] })
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
return tags.map(tag => TagElement({ tagName: tag }))
|
return tags.map(tag =>
|
||||||
|
TagElement({ tagName: tag, color: coloredTags[tag] })
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +65,7 @@ const TagElementList = (tags, showTagsAlphabetically) => {
|
|||||||
* @param {Function} handleNoteClick
|
* @param {Function} handleNoteClick
|
||||||
* @param {Function} handleNoteContextMenu
|
* @param {Function} handleNoteContextMenu
|
||||||
* @param {Function} handleDragStart
|
* @param {Function} handleDragStart
|
||||||
|
* @param {Object} coloredTags
|
||||||
* @param {string} dateDisplay
|
* @param {string} dateDisplay
|
||||||
*/
|
*/
|
||||||
const NoteItem = ({
|
const NoteItem = ({
|
||||||
@@ -59,7 +79,8 @@ const NoteItem = ({
|
|||||||
storageName,
|
storageName,
|
||||||
folderName,
|
folderName,
|
||||||
viewType,
|
viewType,
|
||||||
showTagsAlphabetically
|
showTagsAlphabetically,
|
||||||
|
coloredTags
|
||||||
}) => (
|
}) => (
|
||||||
<div
|
<div
|
||||||
styleName={isActive ? 'item--active' : 'item'}
|
styleName={isActive ? 'item--active' : 'item'}
|
||||||
@@ -70,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 ? (
|
||||||
? 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>
|
||||||
@@ -85,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'
|
||||||
>
|
>
|
||||||
@@ -96,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)
|
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>
|
||||||
@@ -127,6 +162,7 @@ const NoteItem = ({
|
|||||||
NoteItem.propTypes = {
|
NoteItem.propTypes = {
|
||||||
isActive: PropTypes.bool.isRequired,
|
isActive: PropTypes.bool.isRequired,
|
||||||
dateDisplay: PropTypes.string.isRequired,
|
dateDisplay: PropTypes.string.isRequired,
|
||||||
|
coloredTags: PropTypes.object,
|
||||||
note: PropTypes.shape({
|
note: PropTypes.shape({
|
||||||
storage: PropTypes.string.isRequired,
|
storage: PropTypes.string.isRequired,
|
||||||
key: PropTypes.string.isRequired,
|
key: PropTypes.string.isRequired,
|
||||||
@@ -135,15 +171,14 @@ NoteItem.propTypes = {
|
|||||||
tags: PropTypes.array,
|
tags: PropTypes.array,
|
||||||
isStarred: PropTypes.bool.isRequired,
|
isStarred: PropTypes.bool.isRequired,
|
||||||
isTrashed: PropTypes.bool.isRequired,
|
isTrashed: PropTypes.bool.isRequired,
|
||||||
blog: {
|
blog: PropTypes.shape({
|
||||||
blogLink: PropTypes.string,
|
blogLink: PropTypes.string,
|
||||||
blogId: PropTypes.number
|
blogId: PropTypes.number
|
||||||
}
|
})
|
||||||
}),
|
}),
|
||||||
handleNoteClick: PropTypes.func.isRequired,
|
handleNoteClick: PropTypes.func.isRequired,
|
||||||
handleNoteContextMenu: PropTypes.func.isRequired,
|
handleNoteContextMenu: PropTypes.func.isRequired,
|
||||||
handleDragStart: PropTypes.func.isRequired,
|
handleDragStart: PropTypes.func.isRequired
|
||||||
handleDragEnd: PropTypes.func.isRequired
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(NoteItem, styles)
|
export default CSSModules(NoteItem, styles)
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color alpha(#fff, 20%)
|
background-color alpha($ui-dark-tagList-backgroundColor, 20%)
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
&:active
|
&:active
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
@@ -207,7 +207,7 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color alpha(white, 10%)
|
background-color alpha($ui-dark-tagList-backgroundColor, 10%)
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
.item-wrapper
|
.item-wrapper
|
||||||
@@ -223,13 +223,13 @@ body[data-theme="dark"]
|
|||||||
.item-bottom-time
|
.item-bottom-time
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
background-color alpha(white, 10%)
|
background-color alpha($ui-dark-tagList-backgroundColor, 10%)
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
&:hover
|
&:hover
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
color #c0392b
|
color $ui-dark-button--hover-color
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
background-color alpha(#fff, 20%)
|
background-color alpha($ui-dark-tagList-backgroundColor, 20%)
|
||||||
|
|
||||||
.item-title
|
.item-title
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
@@ -322,61 +322,62 @@ body[data-theme="solarized-dark"]
|
|||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
.root
|
.root
|
||||||
border-color $ui-monokai-borderColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
|
|
||||||
.item
|
.item
|
||||||
border-color $ui-monokai-borderColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
&:hover
|
&:hover
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
// background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
|
// background-color alpha(get-theme-var(theme, 'noteList-backgroundColor'), 20%)
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
.item-title
|
.item-title
|
||||||
.item-title-icon
|
.item-title-icon
|
||||||
.item-bottom-time
|
.item-bottom-time
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
|
background-color alpha(get-theme-var(theme, 'noteList-backgroundColor'), 20%)
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
&:active
|
&:active
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
.item-title
|
.item-title
|
||||||
.item-title-icon
|
.item-title-icon
|
||||||
.item-bottom-time
|
.item-bottom-time
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color alpha($ui-monokai-noteList-backgroundColor, 10%)
|
background-color alpha(get-theme-var(theme, 'noteList-backgroundColor'), 10%)
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
.item-wrapper
|
.item-wrapper
|
||||||
border-color alpha($ui-monokai-button-backgroundColor, 60%)
|
border-color alpha(get-theme-var(theme, 'button-backgroundColor'), 60%)
|
||||||
|
|
||||||
.item--active
|
.item--active
|
||||||
border-color $ui-monokai-borderColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
background-color $ui-monokai-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
.item-wrapper
|
.item-wrapper
|
||||||
border-color transparent
|
border-color transparent
|
||||||
.item-title
|
.item-title
|
||||||
.item-title-icon
|
.item-title-icon
|
||||||
.item-bottom-time
|
.item-bottom-time
|
||||||
color $ui-monokai-active-color
|
color get-theme-var(theme, 'active-color')
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
background-color alpha(white, 10%)
|
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 10%)
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
&:hover
|
&:hover
|
||||||
// background-color alpha($ui-monokai-button--active-backgroundColor, 60%)
|
// background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 60%)
|
||||||
color #f92672
|
color get-theme-var(theme, 'button--hover-color')
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
background-color alpha(#fff, 20%)
|
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 20%)
|
||||||
|
|
||||||
.item-title
|
.item-title
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
@@ -395,75 +396,8 @@ body[data-theme="monokai"]
|
|||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
for theme in 'dracula'
|
||||||
.root
|
apply-theme(theme)
|
||||||
border-color $ui-dracula-borderColor
|
|
||||||
background-color $ui-dracula-noteList-backgroundColor
|
|
||||||
|
|
||||||
.item
|
for theme in $themes
|
||||||
border-color $ui-dracula-borderColor
|
apply-theme(theme)
|
||||||
background-color $ui-dracula-noteList-backgroundColor
|
|
||||||
&:hover
|
|
||||||
transition 0.15s
|
|
||||||
// background-color alpha($ui-dracula-noteList-backgroundColor, 20%)
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
.item-title
|
|
||||||
.item-title-icon
|
|
||||||
.item-bottom-time
|
|
||||||
transition 0.15s
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
.item-bottom-tagList-item
|
|
||||||
transition 0.15s
|
|
||||||
background-color alpha($ui-dracula-noteList-backgroundColor, 20%)
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
&:active
|
|
||||||
transition 0.15s
|
|
||||||
background-color $ui-dracula-noteList-backgroundColor
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
.item-title
|
|
||||||
.item-title-icon
|
|
||||||
.item-bottom-time
|
|
||||||
transition 0.15s
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
.item-bottom-tagList-item
|
|
||||||
transition 0.15s
|
|
||||||
background-color alpha($ui-dracula-noteList-backgroundColor, 10%)
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
|
|
||||||
.item-wrapper
|
|
||||||
border-color alpha($ui-dracula-button-backgroundColor, 60%)
|
|
||||||
|
|
||||||
.item--active
|
|
||||||
border-color $ui-dracula-borderColor
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
.item-wrapper
|
|
||||||
border-color transparent
|
|
||||||
.item-title
|
|
||||||
.item-title-icon
|
|
||||||
.item-bottom-time
|
|
||||||
color $ui-dracula-active-color
|
|
||||||
.item-bottom-tagList-item
|
|
||||||
background-color alpha(#f8f8f2, 10%)
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
&:hover
|
|
||||||
// background-color alpha($ui-dracula-button--active-backgroundColor, 60%)
|
|
||||||
color #ff79c6
|
|
||||||
.item-bottom-tagList-item
|
|
||||||
background-color alpha(#f8f8f2, 20%)
|
|
||||||
|
|
||||||
.item-title
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.item-title-icon
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.item-title-empty
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.item-bottom-tagList-item
|
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.item-bottom-tagList-empty
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
vertical-align middle
|
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -223,61 +223,62 @@ body[data-theme="solarized-dark"]
|
|||||||
padding-left 4px
|
padding-left 4px
|
||||||
opacity 0.4
|
opacity 0.4
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
.root
|
.root
|
||||||
border-color $ui-monokai-borderColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
|
|
||||||
.item-simple
|
.item-simple
|
||||||
border-color $ui-monokai-borderColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
&:hover
|
&:hover
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color alpha($ui-monokai-button-backgroundColor, 60%)
|
background-color alpha(get-theme-var(theme, 'button-backgroundColor'), 60%)
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
.item-simple-title-empty
|
.item-simple-title-empty
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
.item-simple-bottom-time
|
.item-simple-bottom-time
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
.item-simple-bottom-tagList-item
|
.item-simple-bottom-tagList-item
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color alpha(#fff, 20%)
|
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 20%)
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
&:active
|
&:active
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color $ui-monokai-button--active-backgroundColor
|
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
.item-simple-title-empty
|
.item-simple-title-empty
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
.item-simple-bottom-time
|
.item-simple-bottom-time
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
.item-simple-bottom-tagList-item
|
.item-simple-bottom-tagList-item
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color alpha(white, 10%)
|
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 10%)
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
.item-simple--active
|
.item-simple--active
|
||||||
border-color $ui-monokai-borderColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
background-color $ui-monokai-button--active-backgroundColor
|
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||||
.item-simple-wrapper
|
.item-simple-wrapper
|
||||||
border-color transparent
|
border-color transparent
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
.item-simple-title-empty
|
.item-simple-title-empty
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
.item-simple-bottom-time
|
.item-simple-bottom-time
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
.item-simple-bottom-tagList-item
|
.item-simple-bottom-tagList-item
|
||||||
background-color alpha(white, 10%)
|
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 10%)
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
&:hover
|
&:hover
|
||||||
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
// background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 60%)
|
||||||
color #c0392b
|
color #c0392b
|
||||||
.item-simple-bottom-tagList-item
|
.item-simple-bottom-tagList-item
|
||||||
background-color alpha(#fff, 20%)
|
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 20%)
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
border-bottom $ui-dark-borderColor
|
border-bottom $ui-dark-borderColor
|
||||||
@@ -287,66 +288,8 @@ body[data-theme="monokai"]
|
|||||||
padding-left 4px
|
padding-left 4px
|
||||||
opacity 0.4
|
opacity 0.4
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
for theme in 'dracula'
|
||||||
.root
|
apply-theme(theme)
|
||||||
border-color $ui-dracula-borderColor
|
|
||||||
background-color $ui-dracula-noteList-backgroundColor
|
|
||||||
|
|
||||||
.item-simple
|
for theme in $themes
|
||||||
border-color $ui-dracula-borderColor
|
apply-theme(theme)
|
||||||
background-color $ui-dracula-noteList-backgroundColor
|
|
||||||
&:hover
|
|
||||||
transition 0.15s
|
|
||||||
background-color alpha($ui-dracula-button-backgroundColor, 60%)
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
.item-simple-title
|
|
||||||
.item-simple-title-empty
|
|
||||||
.item-simple-title-icon
|
|
||||||
.item-simple-bottom-time
|
|
||||||
transition 0.15s
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
.item-simple-bottom-tagList-item
|
|
||||||
transition 0.15s
|
|
||||||
background-color alpha(#f8f8f2, 20%)
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
&:active
|
|
||||||
transition 0.15s
|
|
||||||
background-color $ui-dracula-button--active-backgroundColor
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
.item-simple-title
|
|
||||||
.item-simple-title-empty
|
|
||||||
.item-simple-title-icon
|
|
||||||
.item-simple-bottom-time
|
|
||||||
transition 0.15s
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
.item-simple-bottom-tagList-item
|
|
||||||
transition 0.15s
|
|
||||||
background-color alpha(#f8f8f2, 10%)
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
|
|
||||||
.item-simple--active
|
|
||||||
border-color $ui-dracula-borderColor
|
|
||||||
background-color $ui-dracula-button--active-backgroundColor
|
|
||||||
.item-simple-wrapper
|
|
||||||
border-color transparent
|
|
||||||
.item-simple-title
|
|
||||||
.item-simple-title-empty
|
|
||||||
.item-simple-title-icon
|
|
||||||
.item-simple-bottom-time
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
.item-simple-bottom-tagList-item
|
|
||||||
background-color alpha(#f8f8f2, 10%)
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
&:hover
|
|
||||||
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
|
||||||
color #c0392b
|
|
||||||
.item-simple-bottom-tagList-item
|
|
||||||
background-color alpha(#f8f8f2, 20%)
|
|
||||||
.item-simple-title
|
|
||||||
color $ui-dark-text-color
|
|
||||||
border-bottom $ui-dark-borderColor
|
|
||||||
.item-simple-right
|
|
||||||
float right
|
|
||||||
.item-simple-right-storageName
|
|
||||||
padding-left 4px
|
|
||||||
opacity 0.4
|
|
||||||
@@ -19,7 +19,8 @@ class RealtimeNotification extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
@@ -36,16 +37,23 @@ class RealtimeNotification extends React.Component {
|
|||||||
|
|
||||||
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'
|
||||||
|
href={notifications[0].linkUrl}
|
||||||
|
onClick={e => this.handleLinkClick(e)}
|
||||||
>
|
>
|
||||||
Info: {notifications[0].text}
|
Info: {notifications[0].text}
|
||||||
</a>
|
</a>
|
||||||
: ''
|
) : (
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div styleName='notification-area' style={this.props.style}>{link}</div>
|
<div styleName='notification-area' style={this.props.style}>
|
||||||
|
{link}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,36 +30,20 @@ body[data-theme="dark"]
|
|||||||
&:hover
|
&:hover
|
||||||
color #5CB85C
|
color #5CB85C
|
||||||
|
|
||||||
|
apply-theme(theme)
|
||||||
body[data-theme="solarized-dark"]
|
body[data-theme={theme}]
|
||||||
.notification-area
|
.notification-area
|
||||||
background-color none
|
background-color none
|
||||||
|
|
||||||
.notification-link
|
.notification-link
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
border none
|
border none
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
&:hover
|
&:hover
|
||||||
color #5CB85C
|
color get-theme-var(theme, 'button--hover-color')
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
for theme in 'solarized-dark' 'dracula'
|
||||||
.notification-area
|
apply-theme(theme)
|
||||||
background-color none
|
|
||||||
|
|
||||||
.notification-link
|
for theme in $themes
|
||||||
color $ui-monokai-text-color
|
apply-theme(theme)
|
||||||
border none
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
&:hover
|
|
||||||
color #5CB85C
|
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
|
||||||
.notification-area
|
|
||||||
background-color none
|
|
||||||
|
|
||||||
.notification-link
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
border none
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
&:hover
|
|
||||||
color #ff79c6
|
|
||||||
@@ -16,17 +16,27 @@ 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
|
||||||
|
src={
|
||||||
|
isHomeActive
|
||||||
? '../resources/icon/icon-all-active.svg'
|
? '../resources/icon/icon-all-active.svg'
|
||||||
: '../resources/icon/icon-all.svg'
|
: '../resources/icon/icon-all.svg'
|
||||||
}
|
}
|
||||||
@@ -36,11 +46,14 @@ const SideNavFilter = ({
|
|||||||
<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
|
||||||
|
src={
|
||||||
|
isStarredActive
|
||||||
? '../resources/icon/icon-star-active.svg'
|
? '../resources/icon/icon-star-active.svg'
|
||||||
: '../resources/icon/icon-star-sidenav.svg'
|
: '../resources/icon/icon-star-sidenav.svg'
|
||||||
}
|
}
|
||||||
@@ -50,11 +63,15 @@ const SideNavFilter = ({
|
|||||||
<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
|
||||||
|
src={
|
||||||
|
isTrashedActive
|
||||||
? '../resources/icon/icon-trash-active.svg'
|
? '../resources/icon/icon-trash-active.svg'
|
||||||
: '../resources/icon/icon-trash-sidenav.svg'
|
: '../resources/icon/icon-trash-sidenav.svg'
|
||||||
}
|
}
|
||||||
@@ -63,7 +80,6 @@ const SideNavFilter = ({
|
|||||||
<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>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,7 +90,7 @@ SideNavFilter.propTypes = {
|
|||||||
isStarredActive: PropTypes.bool.isRequired,
|
isStarredActive: PropTypes.bool.isRequired,
|
||||||
isTrashedActive: PropTypes.bool.isRequired,
|
isTrashedActive: PropTypes.bool.isRequired,
|
||||||
handleStarredButtonClick: PropTypes.func.isRequired,
|
handleStarredButtonClick: PropTypes.func.isRequired,
|
||||||
handleTrashdButtonClick: PropTypes.func.isRequired
|
handleTrashedButtonClick: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(SideNavFilter, styles)
|
export default CSSModules(SideNavFilter, styles)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.menu
|
.menu
|
||||||
margin-bottom 30px
|
margin-bottom 20px
|
||||||
|
|
||||||
.menu-button
|
.menu-button
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
@@ -180,129 +180,51 @@ body[data-theme="dark"]
|
|||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
apply-theme(theme)
|
||||||
body[data-theme="solarized-dark"]
|
body[data-theme={theme}]
|
||||||
.menu-button
|
.menu-button
|
||||||
&:active
|
&:active
|
||||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
.menu-button--active
|
.menu-button--active
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
.menu-button-star--active
|
.menu-button-star--active
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
.menu-button-trash--active
|
.menu-button-trash--active
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
for theme in 'solarized-dark' 'dracula'
|
||||||
.menu-button
|
apply-theme(theme)
|
||||||
&:active
|
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
.menu-button--active
|
for theme in $themes
|
||||||
color $ui-monokai-text-color
|
apply-theme(theme)
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
.menu-button-star--active
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
.menu-button-trash--active
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
|
||||||
.menu-button
|
|
||||||
&:active
|
|
||||||
background-color $ui-dracula-noteList-backgroundColor
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
|
|
||||||
.menu-button--active
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
|
|
||||||
.menu-button-star--active
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
|
|
||||||
.menu-button-trash--active
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
@@ -30,7 +30,7 @@ class SnippetTab extends React.Component {
|
|||||||
context.popup([
|
context.popup([
|
||||||
{
|
{
|
||||||
label: i18n.__('Rename'),
|
label: i18n.__('Rename'),
|
||||||
click: (e) => this.handleRenameClick(e)
|
click: e => this.handleRenameClick(e)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@@ -64,13 +64,16 @@ class SnippetTab extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleRename() {
|
handleRename() {
|
||||||
this.setState({
|
this.setState(
|
||||||
|
{
|
||||||
isRenaming: false
|
isRenaming: false
|
||||||
}, () => {
|
},
|
||||||
|
() => {
|
||||||
if (this.props.snippet.name !== this.state.name) {
|
if (this.props.snippet.name !== this.state.name) {
|
||||||
this.props.onRename(this.state.name)
|
this.props.onRename(this.state.name)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteButtonClick(e) {
|
handleDeleteButtonClick(e) {
|
||||||
@@ -78,12 +81,15 @@ class SnippetTab extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startRenaming() {
|
startRenaming() {
|
||||||
this.setState({
|
this.setState(
|
||||||
|
{
|
||||||
isRenaming: true
|
isRenaming: true
|
||||||
}, () => {
|
},
|
||||||
|
() => {
|
||||||
this.refs.name.focus()
|
this.refs.name.focus()
|
||||||
this.refs.name.select()
|
this.refs.name.select()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDragStart(e) {
|
handleDragStart(e) {
|
||||||
@@ -98,49 +104,46 @@ class SnippetTab extends React.Component {
|
|||||||
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 styleName='button-unnamed'>
|
) : (
|
||||||
{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)
|
||||||
|
|||||||
@@ -100,61 +100,28 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
.root
|
.root
|
||||||
border-color $ui-solarized-dark-borderColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
.deleteButton
|
.deleteButton
|
||||||
color $ui-solarized-dark-button--active-color
|
color get-theme-var(theme, 'text-color')
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
.button
|
.button
|
||||||
color $ui-solarized-dark-button--active-color
|
color get-theme-var(theme, 'text-color')
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
|
|
||||||
.root--active
|
.root--active
|
||||||
color $ui-solarized-dark-button--active-color
|
color get-theme-var(theme, 'active-color')
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
border-color $ui-solarized-dark-borderColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
.deleteButton
|
.deleteButton
|
||||||
color $ui-solarized-dark-button--active-color
|
color get-theme-var(theme, 'text-color')
|
||||||
.button
|
.button
|
||||||
color $ui-solarized-dark-button--active-color
|
color get-theme-var(theme, 'active-color')
|
||||||
|
|
||||||
.button
|
|
||||||
border none
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
background-color transparent
|
|
||||||
transition color background-color 0.15s
|
|
||||||
border-left 4px solid transparent
|
|
||||||
|
|
||||||
.input
|
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
|
||||||
color $ui-solarized-dark-button--active-color
|
|
||||||
transition 0.15s
|
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
|
||||||
.root
|
|
||||||
border-color $ui-monokai-borderColor
|
|
||||||
&:hover
|
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
|
||||||
transition 0.15s
|
|
||||||
.deleteButton
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
transition 0.15s
|
|
||||||
.button
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
transition 0.15s
|
|
||||||
|
|
||||||
.root--active
|
|
||||||
color $ui-monokai-active-color
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
border-color $ui-monokai-borderColor
|
|
||||||
.deleteButton
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
.button
|
|
||||||
color $ui-monokai-active-color
|
|
||||||
|
|
||||||
.button
|
.button
|
||||||
border none
|
border none
|
||||||
@@ -164,39 +131,12 @@ body[data-theme="monokai"]
|
|||||||
border-left 4px solid transparent
|
border-left 4px solid transparent
|
||||||
|
|
||||||
.input
|
.input
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
for theme in 'solarized-dark' 'dracula'
|
||||||
.root
|
apply-theme(theme)
|
||||||
border-color $ui-dracula-borderColor
|
|
||||||
&:hover
|
|
||||||
background-color $ui-dracula-noteDetail-backgroundColor
|
|
||||||
transition 0.15s
|
|
||||||
.deleteButton
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
transition 0.15s
|
|
||||||
.button
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
transition 0.15s
|
|
||||||
|
|
||||||
.root--active
|
for theme in $themes
|
||||||
color $ui-dracula-text-color
|
apply-theme(theme)
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
border-color $ui-dracula-borderColor
|
|
||||||
.deleteButton
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
.button
|
|
||||||
color $ui-dracula-active-color
|
|
||||||
|
|
||||||
.button
|
|
||||||
border none
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
background-color transparent
|
|
||||||
transition color background-color 0.15s
|
|
||||||
border-left 4px solid transparent
|
|
||||||
|
|
||||||
.input
|
|
||||||
background-color $ui-dracula-noteDetail-backgroundColor
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
@@ -21,7 +21,9 @@ const FolderIcon = ({ className, color, isActive }) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} isActive
|
* @param {boolean} isActive
|
||||||
|
* @param {object} tooltipRef,
|
||||||
* @param {Function} handleButtonClick
|
* @param {Function} handleButtonClick
|
||||||
|
* @param {Function} handleMouseEnter
|
||||||
* @param {Function} handleContextMenu
|
* @param {Function} handleContextMenu
|
||||||
* @param {string} folderName
|
* @param {string} folderName
|
||||||
* @param {string} folderColor
|
* @param {string} folderColor
|
||||||
@@ -35,7 +37,9 @@ const FolderIcon = ({ className, color, isActive }) => {
|
|||||||
const StorageItem = ({
|
const StorageItem = ({
|
||||||
styles,
|
styles,
|
||||||
isActive,
|
isActive,
|
||||||
|
tooltipRef,
|
||||||
handleButtonClick,
|
handleButtonClick,
|
||||||
|
handleMouseEnter,
|
||||||
handleContextMenu,
|
handleContextMenu,
|
||||||
folderName,
|
folderName,
|
||||||
folderColor,
|
folderColor,
|
||||||
@@ -49,13 +53,15 @@ const StorageItem = ({
|
|||||||
<button
|
<button
|
||||||
styleName={isActive ? 'folderList-item--active' : 'folderList-item'}
|
styleName={isActive ? 'folderList-item--active' : 'folderList-item'}
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
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,18 +76,23 @@ 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' ref={tooltipRef}>
|
||||||
|
{folderName}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
StorageItem.propTypes = {
|
StorageItem.propTypes = {
|
||||||
isActive: PropTypes.bool.isRequired,
|
isActive: PropTypes.bool.isRequired,
|
||||||
|
tooltipRef: PropTypes.object,
|
||||||
handleButtonClick: PropTypes.func,
|
handleButtonClick: PropTypes.func,
|
||||||
|
handleMouseEnter: PropTypes.func,
|
||||||
handleContextMenu: PropTypes.func,
|
handleContextMenu: PropTypes.func,
|
||||||
folderName: PropTypes.string.isRequired,
|
folderName: PropTypes.string.isRequired,
|
||||||
folderColor: PropTypes.string,
|
folderColor: PropTypes.string,
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
border-bottom-right-radius 2px
|
border-bottom-right-radius 2px
|
||||||
height 34px
|
height 34px
|
||||||
line-height 32px
|
line-height 32px
|
||||||
|
transition-property opacity
|
||||||
|
|
||||||
.folderList-item:hover, .folderList-item--active:hover
|
.folderList-item:hover, .folderList-item--active:hover
|
||||||
.folderList-item-tooltip
|
.folderList-item-tooltip
|
||||||
@@ -120,59 +121,28 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
.folderList-item
|
.folderList-item
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
&:active
|
&:active
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
|
|
||||||
.folderList-item--active
|
.folderList-item--active
|
||||||
@extend .folderList-item
|
@extend .folderList-item
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
&:active
|
&:active
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
for theme in 'solarized-dark' 'dracula'
|
||||||
.folderList-item
|
apply-theme(theme)
|
||||||
&:hover
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
&:active
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
|
|
||||||
.folderList-item--active
|
for theme in $themes
|
||||||
@extend .folderList-item
|
apply-theme(theme)
|
||||||
color $ui-monokai-text-color
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
&:active
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
&:hover
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
|
||||||
.folderList-item
|
|
||||||
&:hover
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
&:active
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
|
|
||||||
.folderList-item--active
|
|
||||||
@extend .folderList-item
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
&:active
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
&:hover
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
@@ -12,7 +12,9 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
|
|
||||||
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>
|
||||||
|
|||||||
@@ -10,19 +10,49 @@ 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 {bool} isActive
|
* @param {boolean} isActive
|
||||||
* @param {bool} isRelated
|
* @param {boolean} isRelated
|
||||||
|
* @param {string} bgColor tab backgroundColor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count}) => (
|
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
|
||||||
|
styleName={
|
||||||
|
isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'
|
||||||
}
|
}
|
||||||
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
|
/>
|
||||||
|
)}
|
||||||
|
<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>
|
||||||
@@ -33,7 +63,8 @@ const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, hand
|
|||||||
|
|
||||||
TagListItem.propTypes = {
|
TagListItem.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
handleClickTagListItem: PropTypes.func.isRequired
|
handleClickTagListItem: PropTypes.func.isRequired,
|
||||||
|
color: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(TagListItem, styles)
|
export default CSSModules(TagListItem, styles)
|
||||||
|
|||||||
@@ -71,6 +71,11 @@
|
|||||||
padding-right 15px
|
padding-right 15px
|
||||||
font-size 13px
|
font-size 13px
|
||||||
|
|
||||||
|
.tagList-item-color
|
||||||
|
height 26px
|
||||||
|
width 3px
|
||||||
|
display inline-block
|
||||||
|
|
||||||
body[data-theme="white"]
|
body[data-theme="white"]
|
||||||
.tagList-item
|
.tagList-item
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
@@ -89,23 +94,30 @@ body[data-theme="white"]
|
|||||||
.tagList-item-count
|
.tagList-item-count
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
|
|
||||||
body[data-theme="dark"]
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
.tagList-item
|
.tagList-item
|
||||||
color $ui-dark-inactive-text-color
|
color get-theme-var(theme, 'inactive-text-color')
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 20%)
|
||||||
&:active
|
&:active
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||||
|
|
||||||
.tagList-item-active
|
.tagList-item-active
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
&:active
|
&:active
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 50%)
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 50%)
|
||||||
.tagList-item-count
|
.tagList-item-count
|
||||||
color $ui-dark-button--active-color
|
color get-theme-var(theme, 'button--active-color')
|
||||||
|
|
||||||
|
for theme in 'dark'
|
||||||
|
apply-theme(theme)
|
||||||
|
|
||||||
|
for theme in $themes
|
||||||
|
apply-theme(theme)
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -71,25 +71,19 @@ body[data-theme="solarized-dark"]
|
|||||||
.todoClearText
|
.todoClearText
|
||||||
color #fdf6e3
|
color #fdf6e3
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
.percentageBar
|
.percentageBar
|
||||||
background-color: $ui-monokai-borderColor
|
background-color: get-theme-var(theme, 'borderColor')
|
||||||
|
|
||||||
.progressBar
|
.progressBar
|
||||||
background-color $ui-monokai-active-color
|
background-color get-theme-var(theme, 'active-color')
|
||||||
|
|
||||||
.percentageText
|
.percentageText
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
for theme in 'dracula'
|
||||||
.percentageBar
|
apply-theme(theme)
|
||||||
background-color $ui-dracula-borderColor
|
|
||||||
|
|
||||||
.progressBar
|
for theme in $themes
|
||||||
background-color: $ui-dracula-active-color
|
apply-theme(theme)
|
||||||
|
|
||||||
.percentageText
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
|
|
||||||
.percentageText
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
@@ -8,27 +8,30 @@ 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>
|
||||||
)
|
)
|
||||||
|
|
||||||
TodoProcess.propTypes = {
|
TodoProcess.propTypes = {
|
||||||
todoStatus: {
|
todoStatus: PropTypes.exact({
|
||||||
total: PropTypes.number.isRequired,
|
total: PropTypes.number.isRequired,
|
||||||
completed: PropTypes.number.isRequired
|
completed: PropTypes.number.isRequired
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(TodoProcess, styles)
|
export default CSSModules(TodoProcess, styles)
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ body
|
|||||||
// allow inline line breaks
|
// allow inline line breaks
|
||||||
.katex
|
.katex
|
||||||
white-space initial
|
white-space initial
|
||||||
|
.katex .katex-html
|
||||||
|
display inline-flex
|
||||||
.katex .mfrac>.vlist>span:nth-child(2)
|
.katex .mfrac>.vlist>span:nth-child(2)
|
||||||
top 0 !important
|
top 0 !important
|
||||||
.katex-error
|
.katex-error
|
||||||
@@ -122,40 +124,34 @@ hr
|
|||||||
border-bottom solid 1px borderColor
|
border-bottom solid 1px borderColor
|
||||||
margin 15px 0
|
margin 15px 0
|
||||||
h1, h2, h3, h4, h5, h6
|
h1, h2, h3, h4, h5, h6
|
||||||
|
margin 1em 0 1.5em
|
||||||
|
line-height 1.4
|
||||||
font-weight bold
|
font-weight bold
|
||||||
word-wrap break-word
|
word-wrap break-word
|
||||||
|
padding .2em 0 .2em
|
||||||
h1
|
h1
|
||||||
font-size 2.55em
|
font-size 2.55em
|
||||||
padding-bottom 0.3em
|
line-height 1.2
|
||||||
line-height 1.2em
|
|
||||||
border-bottom solid 1px borderColor
|
border-bottom solid 1px borderColor
|
||||||
margin 1em 0 0.44em
|
|
||||||
&:first-child
|
&:first-child
|
||||||
margin-top 0
|
margin-top 0
|
||||||
h2
|
h2
|
||||||
font-size 1.75em
|
font-size 1.75em
|
||||||
padding-bottom 0.3em
|
line-height 1.225
|
||||||
line-height 1.225em
|
|
||||||
border-bottom solid 1px borderColor
|
border-bottom solid 1px borderColor
|
||||||
margin 1em 0 0.57em
|
|
||||||
&:first-child
|
&:first-child
|
||||||
margin-top 0
|
margin-top 0
|
||||||
h3
|
h3
|
||||||
font-size 1.5em
|
font-size 1.5em
|
||||||
line-height 1.43em
|
line-height 1.43
|
||||||
margin 1em 0 0.66em
|
|
||||||
h4
|
h4
|
||||||
font-size 1.25em
|
font-size 1.25em
|
||||||
line-height 1.4em
|
line-height 1.4
|
||||||
margin 1em 0 0.8em
|
|
||||||
h5
|
h5
|
||||||
font-size 1em
|
font-size 1em
|
||||||
line-height 1.4em
|
line-height 1.1
|
||||||
margin 1em 0 1em
|
|
||||||
h6
|
h6
|
||||||
font-size 1em
|
font-size 1em
|
||||||
line-height 1.4em
|
|
||||||
margin 1em 0 1em
|
|
||||||
color #777
|
color #777
|
||||||
p
|
p
|
||||||
line-height 1.6em
|
line-height 1.6em
|
||||||
@@ -163,6 +159,7 @@ p
|
|||||||
white-space pre-line
|
white-space pre-line
|
||||||
word-wrap break-word
|
word-wrap break-word
|
||||||
img
|
img
|
||||||
|
cursor zoom-in
|
||||||
max-width 100%
|
max-width 100%
|
||||||
strong, b
|
strong, b
|
||||||
font-weight bold
|
font-weight bold
|
||||||
@@ -360,7 +357,10 @@ admonition_types = {
|
|||||||
danger: {color: #c2185b, icon: "block"},
|
danger: {color: #c2185b, icon: "block"},
|
||||||
caution: {color: #ffa726, icon: "warning"},
|
caution: {color: #ffa726, icon: "warning"},
|
||||||
error: {color: #d32f2f, icon: "error_outline"},
|
error: {color: #d32f2f, icon: "error_outline"},
|
||||||
attention: {color: #455a64, icon: "priority_high"}
|
question: {color: #64dd17, icon: "help_outline"},
|
||||||
|
quote: {color: #9e9e9e, icon: "format_quote"},
|
||||||
|
abstract: {color: #00b0ff, icon: "subject"},
|
||||||
|
attention: {color: #455a64, icon: "priority_high"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, val in admonition_types
|
for name, val in admonition_types
|
||||||
@@ -421,6 +421,9 @@ pre.fence
|
|||||||
canvas, svg
|
canvas, svg
|
||||||
max-width 100% !important
|
max-width 100% !important
|
||||||
|
|
||||||
|
svg[ratio]
|
||||||
|
width 100%
|
||||||
|
|
||||||
.gallery
|
.gallery
|
||||||
width 100%
|
width 100%
|
||||||
height 50vh
|
height 50vh
|
||||||
@@ -441,6 +444,44 @@ pre.fence
|
|||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
background-color $ui-tag-backgroundColor
|
background-color $ui-tag-backgroundColor
|
||||||
|
|
||||||
|
.markdownIt-TOC-wrapper
|
||||||
|
list-style none
|
||||||
|
position fixed
|
||||||
|
right 0
|
||||||
|
top 0
|
||||||
|
margin-left 15px
|
||||||
|
z-index 1000
|
||||||
|
transition transform .2s ease-in-out
|
||||||
|
transform translateX(100%)
|
||||||
|
|
||||||
|
.markdownIt-TOC
|
||||||
|
display block
|
||||||
|
max-height 90vh
|
||||||
|
overflow-y auto
|
||||||
|
padding 25px
|
||||||
|
padding-left 38px
|
||||||
|
|
||||||
|
&,
|
||||||
|
&:before
|
||||||
|
background-color $ui-dark-backgroundColor
|
||||||
|
color: $ui-dark-text-color
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
transform translateX(-15px)
|
||||||
|
|
||||||
|
&:before
|
||||||
|
content 'TOC'
|
||||||
|
position absolute
|
||||||
|
width 60px
|
||||||
|
height 30px
|
||||||
|
top 60px
|
||||||
|
left -29px
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
justify-content center
|
||||||
|
transform-origin top left
|
||||||
|
transform rotate(-90deg)
|
||||||
|
|
||||||
themeDarkBackground = darken(#21252B, 10%)
|
themeDarkBackground = darken(#21252B, 10%)
|
||||||
themeDarkText = #f9f9f9
|
themeDarkText = #f9f9f9
|
||||||
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
||||||
@@ -508,137 +549,63 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
background-color $ui-dark-tag-backgroundColor
|
background-color $ui-dark-tag-backgroundColor
|
||||||
|
|
||||||
themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor
|
.markdownIt-TOC-wrapper
|
||||||
themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%)
|
&,
|
||||||
themeSolarizedDarkTableHead = themeSolarizedDarkTableEven
|
&:before
|
||||||
themeSolarizedDarkTableBorder = themeDarkBorder
|
background-color darken(themeDarkBackground, 5%)
|
||||||
|
color themeDarkText
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
apply-theme(theme)
|
||||||
color $ui-solarized-dark-text-color
|
body[data-theme={theme}]
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
border-color themeDarkBorder
|
border-color themeDarkBorder
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
table
|
table
|
||||||
thead
|
thead
|
||||||
tr
|
tr
|
||||||
background-color themeSolarizedDarkTableHead
|
background-color get-theme-var(theme, 'table-head-backgroundColor')
|
||||||
th
|
th
|
||||||
border-color themeSolarizedDarkTableBorder
|
border-color get-theme-var(theme, 'table-borderColor')
|
||||||
&:last-child
|
&:last-child
|
||||||
border-right solid 1px themeSolarizedDarkTableBorder
|
border-right solid 1px get-theme-var(theme, 'table-borderColor')
|
||||||
tbody
|
tbody
|
||||||
tr:nth-child(2n + 1)
|
tr:nth-child(2n + 1)
|
||||||
background-color themeSolarizedDarkTableOdd
|
background-color get-theme-var(theme, 'table-odd-backgroundColor')
|
||||||
tr:nth-child(2n)
|
tr:nth-child(2n)
|
||||||
background-color themeSolarizedDarkTableEven
|
background-color get-theme-var(theme, 'table-even-backgroundColor')
|
||||||
td
|
td
|
||||||
border-color themeSolarizedDarkTableBorder
|
border-color get-theme-var(theme, 'table-borderColor')
|
||||||
&:last-child
|
&:last-child
|
||||||
border-right solid 1px themeSolarizedDarkTableBorder
|
border-right solid 1px get-theme-var(theme, 'table-borderColor')
|
||||||
dl
|
|
||||||
border-color themeDarkBorder
|
|
||||||
background-color themeSolarizedDarkTableHead
|
|
||||||
dt
|
|
||||||
border-color themeDarkBorder
|
|
||||||
dd
|
|
||||||
border-color themeDarkBorder
|
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
pre.fence
|
|
||||||
.gallery
|
|
||||||
.carousel-main, .carousel-footer
|
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
|
||||||
.prev, .next
|
|
||||||
color $ui-solarized-dark-button--active-color
|
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
|
||||||
|
|
||||||
themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
|
|
||||||
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
|
|
||||||
themeMonokaiTableHead = themeMonokaiTableEven
|
|
||||||
themeMonokaiTableBorder = themeDarkBorder
|
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
border-color themeDarkBorder
|
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
|
||||||
table
|
|
||||||
thead
|
|
||||||
tr
|
|
||||||
background-color themeMonokaiTableHead
|
|
||||||
th
|
|
||||||
border-color themeMonokaiTableBorder
|
|
||||||
&:last-child
|
|
||||||
border-right solid 1px themeMonokaiTableBorder
|
|
||||||
tbody
|
|
||||||
tr:nth-child(2n + 1)
|
|
||||||
background-color themeMonokaiTableOdd
|
|
||||||
tr:nth-child(2n)
|
|
||||||
background-color themeMonokaiTableEven
|
|
||||||
td
|
|
||||||
border-color themeMonokaiTableBorder
|
|
||||||
&:last-child
|
|
||||||
border-right solid 1px themeMonokaiTableBorder
|
|
||||||
kbd
|
kbd
|
||||||
background-color themeDarkBackground
|
background-color get-theme-var(theme, 'kbd-backgroundColor')
|
||||||
|
color get-theme-var(theme, 'kbd-color')
|
||||||
|
|
||||||
dl
|
dl
|
||||||
border-color themeDarkBorder
|
border-color themeDarkBorder
|
||||||
background-color themeMonokaiTableHead
|
background-color get-theme-var(theme, 'table-head-backgroundColor')
|
||||||
dt
|
dt
|
||||||
border-color themeDarkBorder
|
border-color themeDarkBorder
|
||||||
dd
|
dd
|
||||||
border-color themeDarkBorder
|
border-color themeDarkBorder
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
|
|
||||||
pre.fence
|
pre.fence
|
||||||
.gallery
|
.gallery
|
||||||
.carousel-main, .carousel-footer
|
.carousel-main, .carousel-footer
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
.prev, .next
|
.prev, .next
|
||||||
color $ui-monokai-button--active-color
|
color get-theme-var(theme, 'button--active-color')
|
||||||
background-color $ui-monokai-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
|
|
||||||
themeDraculaTableOdd = $ui-dracula-noteDetail-backgroundColor
|
.markdownIt-TOC-wrapper
|
||||||
themeDraculaTableEven = darken($ui-dracula-noteDetail-backgroundColor, 10%)
|
&,
|
||||||
themeDraculaTableHead = themeDraculaTableEven
|
&:before
|
||||||
themeDraculaTableBorder = themeDarkBorder
|
background-color darken(get-theme-var(theme, 'noteDetail-backgroundColor'), 15%)
|
||||||
|
color themeDarkText
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
for theme in 'solarized-dark' 'dracula'
|
||||||
color $ui-dracula-text-color
|
apply-theme(theme)
|
||||||
border-color themeDarkBorder
|
|
||||||
background-color $ui-dracula-noteDetail-backgroundColor
|
|
||||||
table
|
|
||||||
thead
|
|
||||||
tr
|
|
||||||
background-color themeDraculaTableHead
|
|
||||||
th
|
|
||||||
border-color themeDraculaTableBorder
|
|
||||||
&:last-child
|
|
||||||
border-right solid 1px themeDraculaTableBorder
|
|
||||||
tbody
|
|
||||||
tr:nth-child(2n + 1)
|
|
||||||
background-color themeDraculaTableOdd
|
|
||||||
tr:nth-child(2n)
|
|
||||||
background-color themeDraculaTableEven
|
|
||||||
td
|
|
||||||
border-color themeDraculaTableBorder
|
|
||||||
&:last-child
|
|
||||||
border-right solid 1px themeDraculaTableBorder
|
|
||||||
kbd
|
|
||||||
background-color themeDarkBackground
|
|
||||||
|
|
||||||
dl
|
for theme in $themes
|
||||||
border-color themeDarkBorder
|
apply-theme(theme)
|
||||||
background-color themeDraculaTableHead
|
|
||||||
dt
|
|
||||||
border-color themeDarkBorder
|
|
||||||
dd
|
|
||||||
border-color themeDarkBorder
|
|
||||||
background-color $ui-dracula-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
pre.fence
|
|
||||||
.gallery
|
|
||||||
.carousel-main, .carousel-footer
|
|
||||||
background-color $ui-dracula-noteDetail-backgroundColor
|
|
||||||
.prev, .next
|
|
||||||
color $ui-dracula-button--active-color
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import mermaidAPI from 'mermaid'
|
import mermaidAPI from 'mermaid'
|
||||||
|
import uiThemes from 'browser/lib/ui-themes'
|
||||||
|
|
||||||
// fixes bad styling in the mermaid dark theme
|
// fixes bad styling in the mermaid dark theme
|
||||||
const darkThemeStyling = `
|
const darkThemeStyling = `
|
||||||
@@ -19,20 +20,49 @@ function getId () {
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
function render (element, content, theme) {
|
function render(element, content, theme, enableHTMLLabel) {
|
||||||
try {
|
try {
|
||||||
const height = element.attributes.getNamedItem('data-height')
|
const height = element.attributes.getNamedItem('data-height')
|
||||||
if (height && height.value !== 'undefined') {
|
const isPredefined = height && height.value !== 'undefined'
|
||||||
|
|
||||||
|
if (isPredefined) {
|
||||||
element.style.height = height.value + 'vh'
|
element.style.height = height.value + 'vh'
|
||||||
}
|
}
|
||||||
const isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai' || theme === 'dracula'
|
|
||||||
|
const isDarkTheme = uiThemes.some(
|
||||||
|
item => item.name === theme && item.isDark
|
||||||
|
)
|
||||||
|
|
||||||
mermaidAPI.initialize({
|
mermaidAPI.initialize({
|
||||||
theme: isDarkTheme ? 'dark' : 'default',
|
theme: isDarkTheme ? 'dark' : 'default',
|
||||||
themeCSS: isDarkTheme ? darkThemeStyling : '',
|
themeCSS: isDarkTheme ? darkThemeStyling : '',
|
||||||
useMaxWidth: false
|
flowchart: {
|
||||||
|
htmlLabels: enableHTMLLabel
|
||||||
|
},
|
||||||
|
gantt: {
|
||||||
|
useWidth: element.clientWidth
|
||||||
|
}
|
||||||
})
|
})
|
||||||
mermaidAPI.render(getId(), content, (svgGraph) => {
|
|
||||||
|
mermaidAPI.render(getId(), content, svgGraph => {
|
||||||
element.innerHTML = svgGraph
|
element.innerHTML = svgGraph
|
||||||
|
|
||||||
|
if (!isPredefined) {
|
||||||
|
const el = element.firstChild
|
||||||
|
const viewBox = el.getAttribute('viewBox').split(' ')
|
||||||
|
|
||||||
|
let ratio = viewBox[2] / viewBox[3]
|
||||||
|
|
||||||
|
if (el.style.maxWidth) {
|
||||||
|
const maxWidth = parseFloat(el.style.maxWidth)
|
||||||
|
|
||||||
|
ratio *= el.parentNode.clientWidth / maxWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
el.setAttribute('ratio', ratio)
|
||||||
|
el.setAttribute('height', el.parentNode.clientWidth / ratio)
|
||||||
|
console.log(el)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
element.className = 'mermaid-error'
|
element.className = 'mermaid-error'
|
||||||
|
|||||||
78
browser/lib/CMLanguageList.js
Normal file
78
browser/lib/CMLanguageList.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
export const languageMaps = {
|
||||||
|
brainfuck: 'Brainfuck',
|
||||||
|
cpp: 'C++',
|
||||||
|
cs: 'C#',
|
||||||
|
clojure: 'Clojure',
|
||||||
|
'clojure-repl': 'ClojureScript',
|
||||||
|
cmake: 'CMake',
|
||||||
|
coffeescript: 'CoffeeScript',
|
||||||
|
crystal: 'Crystal',
|
||||||
|
css: 'CSS',
|
||||||
|
d: 'D',
|
||||||
|
dart: 'Dart',
|
||||||
|
delphi: 'Pascal',
|
||||||
|
diff: 'Diff',
|
||||||
|
django: 'Django',
|
||||||
|
dockerfile: 'Dockerfile',
|
||||||
|
ebnf: 'EBNF',
|
||||||
|
elm: 'Elm',
|
||||||
|
erlang: 'Erlang',
|
||||||
|
'erlang-repl': 'Erlang',
|
||||||
|
fortran: 'Fortran',
|
||||||
|
fsharp: 'F#',
|
||||||
|
gherkin: 'Gherkin',
|
||||||
|
go: 'Go',
|
||||||
|
groovy: 'Groovy',
|
||||||
|
haml: 'HAML',
|
||||||
|
haskell: 'Haskell',
|
||||||
|
haxe: 'Haxe',
|
||||||
|
http: 'HTTP',
|
||||||
|
ini: 'toml',
|
||||||
|
java: 'Java',
|
||||||
|
javascript: 'JavaScript',
|
||||||
|
json: 'JSON',
|
||||||
|
julia: 'Julia',
|
||||||
|
kotlin: 'Kotlin',
|
||||||
|
less: 'LESS',
|
||||||
|
livescript: 'LiveScript',
|
||||||
|
lua: 'Lua',
|
||||||
|
markdown: 'Markdown',
|
||||||
|
mathematica: 'Mathematica',
|
||||||
|
nginx: 'Nginx',
|
||||||
|
nsis: 'NSIS',
|
||||||
|
objectivec: 'Objective-C',
|
||||||
|
ocaml: 'Ocaml',
|
||||||
|
perl: 'Perl',
|
||||||
|
php: 'PHP',
|
||||||
|
powershell: 'PowerShell',
|
||||||
|
properties: 'Properties files',
|
||||||
|
protobuf: 'ProtoBuf',
|
||||||
|
python: 'Python',
|
||||||
|
puppet: 'Puppet',
|
||||||
|
q: 'Q',
|
||||||
|
r: 'R',
|
||||||
|
ruby: 'Ruby',
|
||||||
|
rust: 'Rust',
|
||||||
|
sas: 'SAS',
|
||||||
|
scala: 'Scala',
|
||||||
|
scheme: 'Scheme',
|
||||||
|
scss: 'SCSS',
|
||||||
|
shell: 'Shell',
|
||||||
|
smalltalk: 'Smalltalk',
|
||||||
|
sml: 'SML',
|
||||||
|
sql: 'SQL',
|
||||||
|
stylus: 'Stylus',
|
||||||
|
swift: 'Swift',
|
||||||
|
tcl: 'Tcl',
|
||||||
|
tex: 'LaTex',
|
||||||
|
typescript: 'TypeScript',
|
||||||
|
twig: 'Twig',
|
||||||
|
vbnet: 'VB.NET',
|
||||||
|
vbscript: 'VBScript',
|
||||||
|
verilog: 'Verilog',
|
||||||
|
vhdl: 'VHDL',
|
||||||
|
xml: 'HTML',
|
||||||
|
xquery: 'XQuery',
|
||||||
|
yaml: 'YAML',
|
||||||
|
elixir: 'Elixir'
|
||||||
|
}
|
||||||
@@ -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, {errorWhenNotFound: false})
|
return CSSModules(component, styles, { handleNotFoundStyleName: 'log' })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ const languages = [
|
|||||||
name: 'Chinese (zh-TW)',
|
name: 'Chinese (zh-TW)',
|
||||||
locale: 'zh-TW'
|
locale: 'zh-TW'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Czech',
|
||||||
|
locale: 'cs'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Danish',
|
name: 'Danish',
|
||||||
locale: 'da'
|
locale: 'da'
|
||||||
@@ -62,10 +66,12 @@ const languages = [
|
|||||||
{
|
{
|
||||||
name: 'Spanish',
|
name: 'Spanish',
|
||||||
locale: 'es-ES'
|
locale: 'es-ES'
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
name: 'Turkish',
|
name: 'Turkish',
|
||||||
locale: 'tr'
|
locale: 'tr'
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
name: 'Thai',
|
name: 'Thai',
|
||||||
locale: 'th'
|
locale: 'th'
|
||||||
}
|
}
|
||||||
@@ -82,4 +88,3 @@ module.exports = {
|
|||||||
return languages
|
return languages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function parse (boostnotercPath = _boostnotercPath) {
|
|||||||
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 {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
92
browser/lib/SnippetManager.js
Normal file
92
browser/lib/SnippetManager.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import crypto from 'crypto'
|
||||||
|
import fs from 'fs'
|
||||||
|
import consts from './consts'
|
||||||
|
|
||||||
|
class SnippetManager {
|
||||||
|
constructor() {
|
||||||
|
this.defaultSnippet = [
|
||||||
|
{
|
||||||
|
id: crypto.randomBytes(16).toString('hex'),
|
||||||
|
name: 'Dummy text',
|
||||||
|
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.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
this.snippets = []
|
||||||
|
this.expandSnippet = this.expandSnippet.bind(this)
|
||||||
|
this.init = this.init.bind(this)
|
||||||
|
this.assignSnippets = this.assignSnippets.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if (fs.existsSync(consts.SNIPPET_FILE)) {
|
||||||
|
try {
|
||||||
|
this.snippets = JSON.parse(
|
||||||
|
fs.readFileSync(consts.SNIPPET_FILE, { encoding: 'UTF-8' })
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Error while parsing snippet file')
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fs.writeFileSync(
|
||||||
|
consts.SNIPPET_FILE,
|
||||||
|
JSON.stringify(this.defaultSnippet, null, 4),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
this.snippets = this.defaultSnippet
|
||||||
|
}
|
||||||
|
|
||||||
|
assignSnippets(snippets) {
|
||||||
|
this.snippets = snippets
|
||||||
|
}
|
||||||
|
|
||||||
|
expandSnippet(wordBeforeCursor, cursor, cm) {
|
||||||
|
const templateCursorString = ':{}'
|
||||||
|
for (let i = 0; i < this.snippets.length; i++) {
|
||||||
|
if (this.snippets[i].prefix.indexOf(wordBeforeCursor.text) === -1) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (this.snippets[i].content.indexOf(templateCursorString) !== -1) {
|
||||||
|
const snippetLines = this.snippets[i].content.split('\n')
|
||||||
|
let cursorLineNumber = 0
|
||||||
|
let cursorLinePosition = 0
|
||||||
|
|
||||||
|
let cursorIndex
|
||||||
|
for (let j = 0; j < snippetLines.length; j++) {
|
||||||
|
cursorIndex = snippetLines[j].indexOf(templateCursorString)
|
||||||
|
|
||||||
|
if (cursorIndex !== -1) {
|
||||||
|
cursorLineNumber = j
|
||||||
|
cursorLinePosition = cursorIndex
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cm.replaceRange(
|
||||||
|
this.snippets[i].content.replace(templateCursorString, ''),
|
||||||
|
wordBeforeCursor.range.from,
|
||||||
|
wordBeforeCursor.range.to
|
||||||
|
)
|
||||||
|
cm.setCursor({
|
||||||
|
line: cursor.line + cursorLineNumber,
|
||||||
|
ch: cursorLinePosition + cursor.ch - wordBeforeCursor.text.length
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
cm.replaceRange(
|
||||||
|
this.snippets[i].content,
|
||||||
|
wordBeforeCursor.range.from,
|
||||||
|
wordBeforeCursor.range.to
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const manager = new SnippetManager()
|
||||||
|
export default manager
|
||||||
@@ -76,11 +76,7 @@ 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 }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,15 @@ const { dialog } = remote
|
|||||||
export function confirmDeleteNote(confirmDeletion, permanent) {
|
export function confirmDeleteNote(confirmDeletion, permanent) {
|
||||||
if (confirmDeletion || permanent) {
|
if (confirmDeletion || permanent) {
|
||||||
const alertConfig = {
|
const alertConfig = {
|
||||||
ype: 'warning',
|
type: 'warning',
|
||||||
message: i18n.__('Confirm note deletion'),
|
message: i18n.__('Confirm note deletion'),
|
||||||
detail: i18n.__('This will permanently remove this note.'),
|
detail: i18n.__('This will permanently remove this note.'),
|
||||||
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialogButtonIndex = dialog.showMessageBox(
|
const dialogButtonIndex = dialog.showMessageBox(
|
||||||
remote.getCurrentWindow(), alertConfig
|
remote.getCurrentWindow(),
|
||||||
|
alertConfig
|
||||||
)
|
)
|
||||||
|
|
||||||
return dialogButtonIndex === 0
|
return dialogButtonIndex === 0
|
||||||
|
|||||||
@@ -3,16 +3,57 @@ const fs = require('sander')
|
|||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { app } = remote
|
const { app } = remote
|
||||||
|
|
||||||
const themePath = process.env.NODE_ENV === 'production'
|
const CODEMIRROR_THEME_PATH = 'node_modules/codemirror/theme'
|
||||||
? path.join(app.getAppPath(), './node_modules/codemirror/theme')
|
const CODEMIRROR_EXTRA_THEME_PATH = 'extra_scripts/codemirror/theme'
|
||||||
: require('path').resolve('./node_modules/codemirror/theme')
|
|
||||||
const themes = fs.readdirSync(themePath)
|
|
||||||
.map((themePath) => {
|
|
||||||
return themePath.substring(0, themePath.lastIndexOf('.'))
|
|
||||||
})
|
|
||||||
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
|
|
||||||
|
|
||||||
const snippetFile = process.env.NODE_ENV !== 'test'
|
const isProduction = process.env.NODE_ENV === 'production'
|
||||||
|
|
||||||
|
const paths = [
|
||||||
|
isProduction
|
||||||
|
? 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
|
||||||
|
.map(directory =>
|
||||||
|
fs.readdirSync(directory).map(file => {
|
||||||
|
const name = file.substring(0, file.lastIndexOf('.'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
path: path.join(directory, file),
|
||||||
|
className: `cm-s-${name}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.reduce((accumulator, value) => accumulator.concat(value), [])
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
|
||||||
|
themes.splice(
|
||||||
|
themes.findIndex(({ name }) => name === 'solarized'),
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
name: 'solarized dark',
|
||||||
|
path: path.join(paths[0], 'solarized.css'),
|
||||||
|
className: `cm-s-solarized cm-s-dark`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'solarized light',
|
||||||
|
path: path.join(paths[0], 'solarized.css'),
|
||||||
|
className: `cm-s-solarized cm-s-light`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
themes.splice(0, 0, {
|
||||||
|
name: 'default',
|
||||||
|
path: path.join(paths[0], 'elegant.css'),
|
||||||
|
className: `cm-s-default`
|
||||||
|
})
|
||||||
|
|
||||||
|
const snippetFile =
|
||||||
|
process.env.NODE_ENV !== 'test'
|
||||||
? path.join(app.getPath('userData'), 'snippets.json')
|
? path.join(app.getPath('userData'), 'snippets.json')
|
||||||
: '' // return nothing as we specified different path to snippets.json in test
|
: '' // return nothing as we specified different path to snippets.json in test
|
||||||
|
|
||||||
@@ -35,7 +76,7 @@ const consts = {
|
|||||||
'Dodger Blue',
|
'Dodger Blue',
|
||||||
'Violet Eggplant'
|
'Violet Eggplant'
|
||||||
],
|
],
|
||||||
THEMES: ['default'].concat(themes),
|
THEMES: themes,
|
||||||
SNIPPET_FILE: snippetFile,
|
SNIPPET_FILE: snippetFile,
|
||||||
DEFAULT_EDITOR_FONT_FAMILY: [
|
DEFAULT_EDITOR_FONT_FAMILY: [
|
||||||
'Monaco',
|
'Monaco',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ 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,6 +1,12 @@
|
|||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
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 { shell } = remote.require('electron')
|
||||||
const spellcheck = require('./spellcheck')
|
const spellcheck = require('./spellcheck')
|
||||||
|
const uri2path = require('file-uri-to-path')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the context menu that is shown when there is a right click in the editor of a (not-snippet) note.
|
* Creates the context menu that is shown when there is a right click in the editor of a (not-snippet) note.
|
||||||
@@ -11,7 +17,12 @@ const spellcheck = require('./spellcheck')
|
|||||||
* @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 })
|
||||||
@@ -34,32 +45,108 @@ const buildEditorContextMenu = function (editor, event) {
|
|||||||
isMisspelled: isMisspelled,
|
isMisspelled: isMisspelled,
|
||||||
spellingSuggestions: suggestion
|
spellingSuggestions: suggestion
|
||||||
}
|
}
|
||||||
const template = [{
|
const template = [
|
||||||
|
{
|
||||||
role: 'cut'
|
role: 'cut'
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
role: 'copy'
|
role: 'copy'
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
role: 'paste'
|
role: 'paste'
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
role: 'selectall'
|
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(
|
||||||
|
template,
|
||||||
|
suggestions
|
||||||
|
.map(function(suggestion) {
|
||||||
return {
|
return {
|
||||||
label: suggestion,
|
label: suggestion,
|
||||||
click: function(suggestion) {
|
click: function(suggestion) {
|
||||||
if (editor != null) {
|
if (editor != null) {
|
||||||
editor.replaceRange(suggestion.label, wordRange.anchor, wordRange.head)
|
editor.replaceRange(
|
||||||
|
suggestion.label,
|
||||||
|
wordRange.anchor,
|
||||||
|
wordRange.head
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).concat({
|
})
|
||||||
|
.concat({
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return Menu.buildFromTemplate(template)
|
return Menu.buildFromTemplate(template)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = buildEditorContextMenu
|
/**
|
||||||
|
* Creates the context menu that is shown when there is a right click Markdown preview of a (not-snippet) note.
|
||||||
|
* @param {MarkdownPreview} markdownPreview
|
||||||
|
* @param {MouseEvent} event that has triggered the creation of the context menu
|
||||||
|
* @returns {Electron.Menu} The created electron context menu
|
||||||
|
*/
|
||||||
|
const buildMarkdownPreviewContextMenu = function(markdownPreview, event) {
|
||||||
|
if (
|
||||||
|
markdownPreview == null ||
|
||||||
|
event == null ||
|
||||||
|
event.pageX == null ||
|
||||||
|
event.pageY == null
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default context menu inclusions
|
||||||
|
const template = [
|
||||||
|
{
|
||||||
|
role: 'copy'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'selectall'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if (
|
||||||
|
event.target.tagName.toLowerCase() === 'a' &&
|
||||||
|
event.target.getAttribute('href')
|
||||||
|
) {
|
||||||
|
// Link opener for files on the local system pointed to by href
|
||||||
|
const href = event.target.href
|
||||||
|
const isLocalFile = href.startsWith('file:')
|
||||||
|
if (isLocalFile) {
|
||||||
|
const absPath = uri2path(href)
|
||||||
|
try {
|
||||||
|
if (fs.lstatSync(absPath).isFile()) {
|
||||||
|
template.push({
|
||||||
|
label: i18n.__('Show in explorer'),
|
||||||
|
click: e => shell.showItemInFolder(absPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(
|
||||||
|
'Error while evaluating if the file is locally available',
|
||||||
|
e
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add option to context menu to copy url
|
||||||
|
template.push({
|
||||||
|
label: i18n.__('Copy Url'),
|
||||||
|
click: e => clipboard.writeText(href)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Menu.buildFromTemplate(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
buildEditorContextMenu: buildEditorContextMenu,
|
||||||
|
buildMarkdownPreviewContextMenu: buildMarkdownPreviewContextMenu
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
import 'codemirror-mode-elixir'
|
import 'codemirror-mode-elixir'
|
||||||
|
|
||||||
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
|
const stylusCodeInfo = CodeMirror.modeInfo.find(info => info.name === 'Stylus')
|
||||||
CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']})
|
if (stylusCodeInfo == null) {
|
||||||
|
CodeMirror.modeInfo.push({
|
||||||
|
name: 'Stylus',
|
||||||
|
mime: 'text/x-styl',
|
||||||
|
mode: 'stylus',
|
||||||
|
ext: ['styl'],
|
||||||
|
alias: ['styl']
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
stylusCodeInfo.alias = ['styl']
|
||||||
|
}
|
||||||
|
CodeMirror.modeInfo.push({
|
||||||
|
name: 'Elixir',
|
||||||
|
mime: 'text/x-elixir',
|
||||||
|
mode: 'elixir',
|
||||||
|
ext: ['ex']
|
||||||
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ 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))
|
||||||
|
throw new Error("Target storage doesn't exist.")
|
||||||
const storage = _.find(cachedStorageList, { key: storageKey })
|
const storage = _.find(cachedStorageList, { key: storageKey })
|
||||||
if (storage === undefined) throw new Error('Target storage doesn\'t exist.')
|
if (storage === undefined) throw new Error("Target storage doesn't exist.")
|
||||||
|
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ export function getTodoStatus (content) {
|
|||||||
let numberOfTodo = 0
|
let numberOfTodo = 0
|
||||||
let numberOfCompletedTodo = 0
|
let numberOfCompletedTodo = 0
|
||||||
|
|
||||||
splitted.forEach((line) => {
|
splitted.forEach(line => {
|
||||||
const trimmedLine = line.trim()
|
const trimmedLine = line.trim().replace(/^(>\s*)*/, '')
|
||||||
if (trimmedLine.match(/^[\+\-\*] \[(\s|x)\] ./i)) {
|
if (trimmedLine.match(/^[+\-*] \[(\s|x)] ./i)) {
|
||||||
numberOfTodo++
|
numberOfTodo++
|
||||||
}
|
}
|
||||||
if (trimmedLine.match(/^[\+\-\*] \[x\] ./i)) {
|
if (trimmedLine.match(/^[+\-*] \[x] ./i)) {
|
||||||
numberOfCompletedTodo++
|
numberOfCompletedTodo++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -21,5 +21,5 @@ 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
export function decodeEntities(text) {
|
export function decodeEntities(text) {
|
||||||
var entities = [
|
var entities = [
|
||||||
['apos', '\''],
|
['apos', "'"],
|
||||||
['amp', '&'],
|
['amp', '&'],
|
||||||
['lt', '<'],
|
['lt', '<'],
|
||||||
['gt', '>'],
|
['gt', '>'],
|
||||||
@@ -26,14 +26,14 @@ export function decodeEntities (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,7 +8,8 @@ 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:
|
||||||
|
process.env.NODE_ENV === 'production'
|
||||||
? path.join(app.getAppPath(), './locales')
|
? path.join(app.getAppPath(), './locales')
|
||||||
: path.resolve('./locales'),
|
: path.resolve('./locales'),
|
||||||
devMode: false
|
devMode: false
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const _ = require('lodash')
|
|
||||||
const uuidv4 = require('uuid/v4')
|
const uuidv4 = require('uuid/v4')
|
||||||
|
|
||||||
module.exports = function(uuid) {
|
module.exports = function(uuid) {
|
||||||
|
|||||||
@@ -9,16 +9,22 @@ module.exports = function definitionListPlugin (md) {
|
|||||||
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
|
||||||
}
|
}
|
||||||
@@ -29,7 +35,10 @@ module.exports = function definitionListPlugin (md) {
|
|||||||
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
|
||||||
@@ -63,21 +72,31 @@ 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
|
||||||
@@ -100,8 +119,7 @@ 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)
|
||||||
@@ -109,7 +127,9 @@ module.exports = function definitionListPlugin (md) {
|
|||||||
|
|
||||||
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)
|
||||||
@@ -120,14 +140,17 @@ module.exports = function definitionListPlugin (md) {
|
|||||||
|
|
||||||
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']
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,18 @@
|
|||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ module.exports = function sanitizePlugin (md, options) {
|
|||||||
options
|
options
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (state.tokens[tokenIdx].type === '_fence') {
|
if (state.tokens[tokenIdx].type.match(/.*_fence$/)) {
|
||||||
// escapeHtmlCharacters has better performance
|
// escapeHtmlCharacters has better performance
|
||||||
state.tokens[tokenIdx].content = escapeHtmlCharacters(
|
state.tokens[tokenIdx].content = escapeHtmlCharacters(
|
||||||
state.tokens[tokenIdx].content,
|
state.tokens[tokenIdx].content,
|
||||||
@@ -38,7 +38,7 @@ 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)
|
||||||
@@ -46,7 +46,12 @@ function sanitizeInline (html, options) {
|
|||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,6 +109,10 @@ 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) {
|
||||||
|
// No href
|
||||||
|
return false
|
||||||
|
}
|
||||||
href = href.replace(/<\!\-\-.*?\-\-\>/g, '')
|
href = href.replace(/<\!\-\-.*?\-\-\>/g, '')
|
||||||
|
|
||||||
const matches = href.match(/^([a-zA-Z]+)\:/)
|
const matches = href.match(/^([a-zA-Z]+)\:/)
|
||||||
|
|||||||
@@ -21,13 +21,15 @@ function uniqueSlug (slug, slugs, opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function linkify(token) {
|
function linkify(token) {
|
||||||
token.content = mdlink(token.content, '#' + token.slug)
|
token.content = mdlink(token.content, `#${decodeURI(token.slug)}`)
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
const TOC_MARKER_START = '<!-- toc -->'
|
const TOC_MARKER_START = '<!-- toc -->'
|
||||||
const TOC_MARKER_END = '<!-- tocstop -->'
|
const TOC_MARKER_END = '<!-- tocstop -->'
|
||||||
|
|
||||||
|
const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes care of proper updating given editor with TOC.
|
* Takes care of proper updating given editor with TOC.
|
||||||
* If TOC doesn't exit in the editor, it's inserted at current caret position.
|
* If TOC doesn't exit in the editor, it's inserted at current caret position.
|
||||||
@@ -35,12 +37,6 @@ const TOC_MARKER_END = '<!-- tocstop -->'
|
|||||||
* @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) {
|
||||||
const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
|
|
||||||
|
|
||||||
function tocExistsInEditor () {
|
|
||||||
return tocRegex.test(editor.getValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateExistingToc() {
|
function updateExistingToc() {
|
||||||
const toc = generate(editor.getValue())
|
const toc = generate(editor.getValue())
|
||||||
const search = editor.getSearchCursor(tocRegex)
|
const search = editor.getSearchCursor(tocRegex)
|
||||||
@@ -50,17 +46,23 @@ 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())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tocExistsInEditor()) {
|
if (tocExistsInEditor(editor)) {
|
||||||
updateExistingToc()
|
updateExistingToc()
|
||||||
} else {
|
} else {
|
||||||
addTocAtCursorPosition()
|
addTocAtCursorPosition()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function tocExistsInEditor(editor) {
|
||||||
|
return tocRegex.test(editor.getValue())
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates MD TOC based on MD document passed as string.
|
* Generates MD TOC based on MD document passed as string.
|
||||||
* @param markdownText MD document
|
* @param markdownText MD document
|
||||||
@@ -88,11 +90,15 @@ export function generate (markdownText) {
|
|||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
generate,
|
generate,
|
||||||
generateInEditor
|
generateInEditor,
|
||||||
|
tocExistsInEditor
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import markdownit from 'markdown-it'
|
|||||||
import sanitize from './markdown-it-sanitize-html'
|
import sanitize from './markdown-it-sanitize-html'
|
||||||
import emoji from 'markdown-it-emoji'
|
import emoji from 'markdown-it-emoji'
|
||||||
import math from '@rokt33r/markdown-it-math'
|
import math from '@rokt33r/markdown-it-math'
|
||||||
|
import mdurl from 'mdurl'
|
||||||
import smartArrows from 'markdown-it-smartarrows'
|
import smartArrows from 'markdown-it-smartarrows'
|
||||||
|
import markdownItTocAndAnchor from '@hikerpig/markdown-it-toc-and-anchor'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import katex from 'katex'
|
import katex from 'katex'
|
||||||
@@ -15,7 +17,9 @@ function createGutter (str, firstLineNumber) {
|
|||||||
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 {
|
||||||
@@ -32,31 +36,132 @@ class Markdown {
|
|||||||
|
|
||||||
const updatedOptions = Object.assign(defaultOptions, options)
|
const updatedOptions = Object.assign(defaultOptions, options)
|
||||||
this.md = markdownit(updatedOptions)
|
this.md = markdownit(updatedOptions)
|
||||||
|
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') {
|
||||||
@@ -69,15 +174,15 @@ 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'],
|
||||||
@@ -121,14 +226,41 @@ 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']})
|
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'))
|
||||||
|
|
||||||
|
this.md.use(md => {
|
||||||
|
markdownItTocAndAnchor(md, {
|
||||||
|
toc: true,
|
||||||
|
tocPattern: /\[TOC\]/i,
|
||||||
|
anchorLink: false,
|
||||||
|
appendIdToHeading: false
|
||||||
|
})
|
||||||
|
|
||||||
|
md.renderer.rules.toc_open = () => '<div class="markdownIt-TOC-wrapper">'
|
||||||
|
md.renderer.rules.toc_close = () => '</div>'
|
||||||
|
})
|
||||||
|
|
||||||
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(
|
||||||
|
require('./markdown-it-fence'),
|
||||||
|
{
|
||||||
chart: token => {
|
chart: token => {
|
||||||
if (token.parameters.hasOwnProperty('yaml')) {
|
if (token.parameters.hasOwnProperty('yaml')) {
|
||||||
token.parameters.format = 'yaml'
|
token.parameters.format = 'yaml'
|
||||||
@@ -136,81 +268,125 @@ class Markdown {
|
|||||||
|
|
||||||
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="chart" data-height="${token.parameters.height}" data-format="${token.parameters.format || 'json'}">${token.content}</div>
|
<div class="chart" data-height="${
|
||||||
|
token.parameters.height
|
||||||
|
}" data-format="${token.parameters.format || 'json'}">${
|
||||||
|
token.content
|
||||||
|
}</div>
|
||||||
</pre>`
|
</pre>`
|
||||||
},
|
},
|
||||||
flowchart: token => {
|
flowchart: token => {
|
||||||
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="flowchart" data-height="${token.parameters.height}">${token.content}</div>
|
<div class="flowchart" data-height="${token.parameters.height}">${
|
||||||
|
token.content
|
||||||
|
}</div>
|
||||||
</pre>`
|
</pre>`
|
||||||
},
|
},
|
||||||
gallery: token => {
|
gallery: token => {
|
||||||
const content = token.content.split('\n').slice(0, -1).map(line => {
|
const content = token.content
|
||||||
|
.split('\n')
|
||||||
|
.slice(0, -1)
|
||||||
|
.map(line => {
|
||||||
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
|
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
|
||||||
if (match) {
|
if (match) {
|
||||||
return match[1]
|
return mdurl.encode(match[1])
|
||||||
} else {
|
} else {
|
||||||
return line
|
return mdurl.encode(line)
|
||||||
}
|
}
|
||||||
}).join('\n')
|
})
|
||||||
|
.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="gallery" data-autoplay="${
|
||||||
|
token.parameters.autoplay
|
||||||
|
}" data-height="${token.parameters.height}">${content}</div>
|
||||||
</pre>`
|
</pre>`
|
||||||
},
|
},
|
||||||
mermaid: token => {
|
mermaid: token => {
|
||||||
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="mermaid" data-height="${token.parameters.height}">${token.content}</div>
|
<div class="mermaid" data-height="${token.parameters.height}">${
|
||||||
|
token.content
|
||||||
|
}</div>
|
||||||
</pre>`
|
</pre>`
|
||||||
},
|
},
|
||||||
sequence: token => {
|
sequence: token => {
|
||||||
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="sequence" data-height="${token.parameters.height}">${token.content}</div>
|
<div class="sequence" data-height="${token.parameters.height}">${
|
||||||
|
token.content
|
||||||
|
}</div>
|
||||||
</pre>`
|
</pre>`
|
||||||
}
|
}
|
||||||
}, token => {
|
},
|
||||||
|
token => {
|
||||||
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
|
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')
|
||||||
this.md.use(require('markdown-it-plantuml'), '', {
|
const plantuml = require('markdown-it-plantuml')
|
||||||
generateSource: function (umlCode) {
|
const plantUmlStripTrailingSlash = url =>
|
||||||
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
|
url.endsWith('/') ? url.slice(0, -1) : url
|
||||||
const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg'
|
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(`@startuml\n${s}\n@enduml`, 9)
|
deflate.zip_deflate(`${openMarker}\n${s}\n${closeMarker}`, 9)
|
||||||
)
|
)
|
||||||
return `${serverAddress}/${zippedCode}`
|
return `${plantUmlServerAddress}/${type}/${zippedCode}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.md.use(plantuml, {
|
||||||
|
generateSource: umlCode =>
|
||||||
|
parsePlantUml(umlCode, '@startuml', '@enduml', 'svg')
|
||||||
})
|
})
|
||||||
|
|
||||||
// Ditaa support
|
// Ditaa support. PlantUML server doesn't support Ditaa in SVG, so we set the format as PNG at the moment.
|
||||||
this.md.use(require('markdown-it-plantuml'), {
|
this.md.use(plantuml, {
|
||||||
openMarker: '@startditaa',
|
openMarker: '@startditaa',
|
||||||
closeMarker: '@endditaa',
|
closeMarker: '@endditaa',
|
||||||
generateSource: function (umlCode) {
|
generateSource: umlCode =>
|
||||||
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
|
parsePlantUml(umlCode, '@startditaa', '@endditaa', 'png')
|
||||||
// Currently PlantUML server doesn't support Ditaa in SVG, so we set the format as PNG at the moment.
|
})
|
||||||
const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/png'
|
|
||||||
const s = unescape(encodeURIComponent(umlCode))
|
// Mindmap support
|
||||||
const zippedCode = deflate.encode64(
|
this.md.use(plantuml, {
|
||||||
deflate.zip_deflate(`@startditaa\n${s}\n@endditaa`, 9)
|
openMarker: '@startmindmap',
|
||||||
)
|
closeMarker: '@endmindmap',
|
||||||
return `${serverAddress}/${zippedCode}`
|
generateSource: umlCode =>
|
||||||
}
|
parsePlantUml(umlCode, '@startmindmap', '@endmindmap', 'svg')
|
||||||
|
})
|
||||||
|
|
||||||
|
// WBS support
|
||||||
|
this.md.use(plantuml, {
|
||||||
|
openMarker: '@startwbs',
|
||||||
|
closeMarker: '@endwbs',
|
||||||
|
generateSource: umlCode =>
|
||||||
|
parsePlantUml(umlCode, '@startwbs', '@endwbs', 'svg')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Gantt support
|
||||||
|
this.md.use(plantuml, {
|
||||||
|
openMarker: '@startgantt',
|
||||||
|
closeMarker: '@endgantt',
|
||||||
|
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')
|
||||||
@@ -220,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
|
||||||
@@ -233,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
|
||||||
|
|
||||||
@@ -246,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>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,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':
|
||||||
@@ -287,8 +484,10 @@ class Markdown {
|
|||||||
case 'list_item_open':
|
case 'list_item_open':
|
||||||
case 'paragraph_open':
|
case 'paragraph_open':
|
||||||
case 'table_open':
|
case 'table_open':
|
||||||
|
if (token.map) {
|
||||||
token.attrPush(['data-line', token.map[0]])
|
token.attrPush(['data-line', token.map[0]])
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
const result = originalRender.call(this.md.renderer, tokens, options, env)
|
const result = originalRender.call(this.md.renderer, tokens, options, env)
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function strip (input) {
|
|||||||
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
|
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
|
||||||
.replace(/>/g, '')
|
.replace(/>/g, '')
|
||||||
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
|
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
|
||||||
.replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1')
|
.replace(/^#{1,6}\s*/gm, '')
|
||||||
.replace(/(`{3,})(.*?)\1/gm, '$2')
|
.replace(/(`{3,})(.*?)\1/gm, '$2')
|
||||||
.replace(/^-{3,}\s*$/g, '')
|
.replace(/^-{3,}\s*$/g, '')
|
||||||
.replace(/`(.+?)`/g, '$1')
|
.replace(/`(.+?)`/g, '$1')
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
import { hashHistory } from 'react-router'
|
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
|
import queryString from 'query-string'
|
||||||
|
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(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,24 +39,41 @@ export function createMarkdownNote (storage, folder, dispatch, location, params,
|
|||||||
note: note
|
note: note
|
||||||
})
|
})
|
||||||
|
|
||||||
hashHistory.push({
|
dispatch(
|
||||||
|
push({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: { key: noteHash }
|
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
|
||||||
|
|
||||||
return dataApi
|
return dataApi
|
||||||
.createNote(storage, {
|
.createNote(storage, {
|
||||||
type: 'SNIPPET_NOTE',
|
type: 'SNIPPET_NOTE',
|
||||||
@@ -56,7 +84,7 @@ export function createSnippetNote (storage, folder, dispatch, location, params,
|
|||||||
snippets: [
|
snippets: [
|
||||||
{
|
{
|
||||||
name: '',
|
name: '',
|
||||||
mode: config.editor.snippetDefaultLanguage || 'text',
|
mode: defaultLanguage,
|
||||||
content: '',
|
content: '',
|
||||||
linesHighlighted: []
|
linesHighlighted: []
|
||||||
}
|
}
|
||||||
@@ -68,10 +96,12 @@ export function createSnippetNote (storage, folder, dispatch, location, params,
|
|||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
})
|
})
|
||||||
hashHistory.push({
|
dispatch(
|
||||||
|
push({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: { key: noteHash }
|
search: queryString.stringify({ key: noteHash })
|
||||||
})
|
})
|
||||||
|
)
|
||||||
ee.emit('list:jump', noteHash)
|
ee.emit('list:jump', noteHash)
|
||||||
ee.emit('detail:focus')
|
ee.emit('detail:focus')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ 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
|
||||||
@@ -18,14 +20,19 @@ function findByWordOrTag (notes, block) {
|
|||||||
}
|
}
|
||||||
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,17 +1,15 @@
|
|||||||
import diacritics from 'diacritics-map'
|
|
||||||
|
|
||||||
function replaceDiacritics (str) {
|
|
||||||
return str.replace(/[À-ž]/g, function (ch) {
|
|
||||||
return diacritics[ch] || ch
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function slugify(title) {
|
module.exports = function slugify(title) {
|
||||||
let slug = title.trim()
|
const slug = encodeURI(
|
||||||
|
title
|
||||||
|
.trim()
|
||||||
|
.replace(/^\s+/, '')
|
||||||
|
.replace(/\s+$/, '')
|
||||||
|
.replace(/\s+/g, '-')
|
||||||
|
.replace(
|
||||||
|
/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`]/g,
|
||||||
|
''
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
slug = replaceDiacritics(slug)
|
return slug
|
||||||
|
|
||||||
slug = slug.replace(/[^\w\s-]/g, '').replace(/\s+/g, '-')
|
|
||||||
|
|
||||||
return encodeURI(slug).replace(/\-+$/, '')
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ let self
|
|||||||
|
|
||||||
function getAvailableDictionaries() {
|
function getAvailableDictionaries() {
|
||||||
return [
|
return [
|
||||||
{label: i18n.__('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' }
|
||||||
@@ -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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +76,10 @@ function checkWholeDocument (editor) {
|
|||||||
*/
|
*/
|
||||||
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 (
|
||||||
|
pos1.line > pos2.line ||
|
||||||
|
(pos1.line === pos2.line && pos1.ch > pos2.ch)
|
||||||
|
) {
|
||||||
return { from: pos2, to: pos1 }
|
return { from: pos2, to: pos1 }
|
||||||
}
|
}
|
||||||
return { from: pos1, to: pos2 }
|
return { from: pos1, to: pos2 }
|
||||||
@@ -97,7 +99,7 @@ function checkMultiLineRange (editor, from, to) {
|
|||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,7 +118,9 @@ function checkWord (editor, wordRange) {
|
|||||||
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]
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,17 +142,25 @@ function checkChangeRange (editor, fromChangeObject, toChangeObject) {
|
|||||||
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)
|
||||||
@@ -165,7 +177,10 @@ 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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,14 +188,22 @@ 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
|
||||||
|
|||||||
9
browser/lib/turndown.js
Normal file
9
browser/lib/turndown.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const TurndownService = require('turndown')
|
||||||
|
const { gfm } = require('turndown-plugin-gfm')
|
||||||
|
|
||||||
|
export const createTurndownService = function() {
|
||||||
|
const turndown = new TurndownService()
|
||||||
|
turndown.use(gfm)
|
||||||
|
turndown.remove('script')
|
||||||
|
return turndown
|
||||||
|
}
|
||||||
44
browser/lib/ui-themes.js
Normal file
44
browser/lib/ui-themes.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
name: 'dark',
|
||||||
|
label: i18n.__('Dark'),
|
||||||
|
isDark: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'default',
|
||||||
|
label: i18n.__('Default'),
|
||||||
|
isDark: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dracula',
|
||||||
|
label: i18n.__('Dracula'),
|
||||||
|
isDark: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'monokai',
|
||||||
|
label: i18n.__('Monokai'),
|
||||||
|
isDark: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'nord',
|
||||||
|
label: i18n.__('Nord'),
|
||||||
|
isDark: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'solarized-dark',
|
||||||
|
label: i18n.__('Solarized Dark'),
|
||||||
|
isDark: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vulcan',
|
||||||
|
label: i18n.__('Vulcan'),
|
||||||
|
isDark: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'white',
|
||||||
|
label: i18n.__('White'),
|
||||||
|
isDark: false
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -132,8 +132,30 @@ export function isObjectEqual (a, b) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isMarkdownTitleURL(str) {
|
||||||
|
return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(
|
||||||
|
str
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function humanFileSize(bytes) {
|
||||||
|
const threshold = 1000
|
||||||
|
if (Math.abs(bytes) < threshold) {
|
||||||
|
return bytes + ' B'
|
||||||
|
}
|
||||||
|
var units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||||
|
var u = -1
|
||||||
|
do {
|
||||||
|
bytes /= threshold
|
||||||
|
++u
|
||||||
|
} while (Math.abs(bytes) >= threshold && u < units.length - 1)
|
||||||
|
return bytes.toFixed(1) + ' ' + units[u]
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
lastFindInArray,
|
lastFindInArray,
|
||||||
escapeHtmlCharacters,
|
escapeHtmlCharacters,
|
||||||
isObjectEqual
|
isObjectEqual,
|
||||||
|
isMarkdownTitleURL,
|
||||||
|
humanFileSize
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,23 +24,16 @@ body[data-theme="dark"]
|
|||||||
.empty-message
|
.empty-message
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
.root
|
.root
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
border-left 1px solid $ui-solarized-dark-borderColor
|
border-left 1px solid get-theme-var(theme, 'borderColor')
|
||||||
.empty-message
|
.empty-message
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
for theme in 'solarized-dark' 'dracula'
|
||||||
.root
|
apply-theme(theme)
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
|
||||||
border-left 1px solid $ui-monokai-borderColor
|
|
||||||
.empty-message
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
for theme in $themes
|
||||||
.root
|
apply-theme(theme)
|
||||||
background-color $ui-dracula-noteDetail-backgroundColor
|
|
||||||
border-left 1px solid $ui-dracula-borderColor
|
|
||||||
.empty-message
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
@@ -25,12 +25,15 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleClick(e) {
|
handleClick(e) {
|
||||||
this.setState({
|
this.setState(
|
||||||
|
{
|
||||||
status: 'SEARCH',
|
status: 'SEARCH',
|
||||||
optionIndex: -1
|
optionIndex: -1
|
||||||
}, () => {
|
},
|
||||||
|
() => {
|
||||||
this.refs.search.focus()
|
this.refs.search.focus()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFocus(e) {
|
handleFocus(e) {
|
||||||
@@ -53,30 +56,39 @@ class FolderSelect extends React.Component {
|
|||||||
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',
|
status: 'SEARCH',
|
||||||
optionIndex: -1
|
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',
|
status: 'SEARCH',
|
||||||
optionIndex: 0
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,9 +105,12 @@ 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(
|
||||||
|
new RegExp('^' + _.escapeRegExp(search), 'i')
|
||||||
|
)
|
||||||
})
|
})
|
||||||
: -1
|
: -1
|
||||||
|
|
||||||
@@ -121,11 +136,14 @@ 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()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,24 +177,30 @@ class FolderSelect extends React.Component {
|
|||||||
|
|
||||||
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.setValue(folder.key)
|
||||||
this.refs.root.focus()
|
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.setValue(storageKey + '-' + folderKey)
|
||||||
this.refs.root.focus()
|
this.refs.root.focus()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +216,7 @@ class FolderSelect extends React.Component {
|
|||||||
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,39 +224,49 @@ 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 styleName={index === this.state.optionIndex
|
<div
|
||||||
|
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'
|
<span
|
||||||
|
styleName='search-optionList-item-name'
|
||||||
style={{ borderColor: option.folder.color }}
|
style={{ borderColor: option.folder.color }}
|
||||||
>
|
>
|
||||||
{option.folder.name}
|
{option.folder.name}
|
||||||
<span styleName='search-optionList-item-name-surfix'>in {option.storage.name}</span>
|
<span styleName='search-optionList-item-name-surfix'>
|
||||||
|
in {option.storage.name}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={_.isString(className)
|
<div
|
||||||
? 'FolderSelect ' + className
|
className={
|
||||||
: 'FolderSelect'
|
_.isString(className) ? 'FolderSelect ' + className : 'FolderSelect'
|
||||||
}
|
}
|
||||||
styleName={this.state.status === 'SEARCH'
|
styleName={
|
||||||
|
this.state.status === 'SEARCH'
|
||||||
? 'root--search'
|
? 'root--search'
|
||||||
: this.state.status === 'FOCUS'
|
: this.state.status === 'FOCUS'
|
||||||
? 'root--focus'
|
? 'root--focus'
|
||||||
@@ -240,28 +274,28 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
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}}>
|
) : currentOption ? (
|
||||||
|
<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>
|
||||||
}
|
) : null}
|
||||||
|
|
||||||
</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(
|
||||||
|
PropTypes.shape({
|
||||||
key: PropTypes.string,
|
key: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
color: PropTypes.string
|
color: PropTypes.string
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(FolderSelect, styles)
|
export default CSSModules(FolderSelect, styles)
|
||||||
|
|||||||
@@ -134,54 +134,39 @@ body[data-theme="dark"]
|
|||||||
.search-optionList-item-name-surfix
|
.search-optionList-item-name-surfix
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
.root
|
.root
|
||||||
color $ui-dark-text-color
|
|
||||||
&:hover
|
&:hover
|
||||||
color white
|
background-color get-theme-var(theme, 'button--hover-backgroundColor')
|
||||||
background-color $ui-monokai-button--hover-backgroundColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
border-color $ui-monokai-borderColor
|
|
||||||
|
.search-input
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
background-color transparent
|
||||||
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
|
|
||||||
.search-optionList
|
.search-optionList
|
||||||
color white
|
color get-theme-var(theme, 'text-color')
|
||||||
border-color $ui-monokai-borderColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
background-color $ui-monokai-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
|
|
||||||
.search-optionList-item
|
.search-optionList-item
|
||||||
&:hover
|
&:hover
|
||||||
background-color lighten($ui-monokai-button--hover-backgroundColor, 15%)
|
background-color lighten(get-theme-var(theme, 'button--hover-backgroundColor'), 15%)
|
||||||
|
|
||||||
.search-optionList-item--active
|
.search-optionList-item--active
|
||||||
background-color $ui-monokai-button--active-backgroundColor
|
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||||
color $ui-monokai-button--active-color
|
color get-theme-var(theme, 'button--active-color')
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-monokai-button--active-backgroundColor
|
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||||
color $ui-monokai-button--active-color
|
color get-theme-var(theme, 'button--active-color')
|
||||||
|
|
||||||
.search-optionList-item-name-surfix
|
.search-optionList-item-name-surfix
|
||||||
color $ui-monokai-inactive-text-color
|
color get-theme-var(theme, 'inactive-text-color')
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
for theme in 'solarized-dark' 'dracula'
|
||||||
.root
|
apply-theme(theme)
|
||||||
color $ui-dracula-text-color
|
|
||||||
&:hover
|
|
||||||
color #f8f8f2
|
|
||||||
background-color $ui-dark-button--hover-backgroundColor
|
|
||||||
border-color $ui-dracula-borderColor
|
|
||||||
|
|
||||||
.search-optionList
|
for theme in $themes
|
||||||
color #f8f8f2
|
apply-theme(theme)
|
||||||
border-color $ui-dracula-borderColor
|
|
||||||
background-color $ui-dracula-button-backgroundColor
|
|
||||||
|
|
||||||
.search-optionList-item
|
|
||||||
&:hover
|
|
||||||
background-color lighten($ui-dracula-button--hover-backgroundColor, 15%)
|
|
||||||
|
|
||||||
.search-optionList-item--active
|
|
||||||
background-color $ui-dracula-button--active-backgroundColor
|
|
||||||
color $ui-dracula-button--active-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-dark-button--hover-backgroundColor
|
|
||||||
color $ui-dracula-button--active-color
|
|
||||||
.search-optionList-item-name-surfix
|
|
||||||
color $ui-dracula-inactive-text-color
|
|
||||||
71
browser/main/Detail/FromUrlButton.js
Normal file
71
browser/main/Detail/FromUrlButton.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './FromUrlButton.styl'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
|
class FromUrlButton extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isActive: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseDown(e) {
|
||||||
|
this.setState({
|
||||||
|
isActive: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseUp(e) {
|
||||||
|
this.setState({
|
||||||
|
isActive: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseLeave(e) {
|
||||||
|
this.setState({
|
||||||
|
isActive: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { className } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
_.isString(className) ? 'FromUrlButton ' + className : 'FromUrlButton'
|
||||||
|
}
|
||||||
|
styleName={
|
||||||
|
this.state.isActive || this.props.isActive ? 'root--active' : 'root'
|
||||||
|
}
|
||||||
|
onMouseDown={e => this.handleMouseDown(e)}
|
||||||
|
onMouseUp={e => this.handleMouseUp(e)}
|
||||||
|
onMouseLeave={e => this.handleMouseLeave(e)}
|
||||||
|
onClick={this.props.onClick}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
styleName='icon'
|
||||||
|
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>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FromUrlButton.propTypes = {
|
||||||
|
isActive: PropTypes.bool,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
className: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(FromUrlButton, styles)
|
||||||
41
browser/main/Detail/FromUrlButton.styl
Normal file
41
browser/main/Detail/FromUrlButton.styl
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
.root
|
||||||
|
top 45px
|
||||||
|
topBarButtonRight()
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
color alpha($ui-favorite-star-button-color, 0.6)
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
position absolute
|
||||||
|
pointer-events none
|
||||||
|
top 50px
|
||||||
|
right 125px
|
||||||
|
width 90px
|
||||||
|
z-index 200
|
||||||
|
padding 5px
|
||||||
|
line-height normal
|
||||||
|
border-radius 2px
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
|
||||||
|
.root--active
|
||||||
|
@extend .root
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-favorite-star-button-color
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
color alpha($ui-favorite-star-button-color, 0.6)
|
||||||
|
|
||||||
|
.icon
|
||||||
|
transition transform 0.15s
|
||||||
|
height 13px
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.root
|
||||||
|
topBarButtonDark()
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
color alpha($ui-favorite-star-button-color, 0.6)
|
||||||
@@ -5,15 +5,21 @@ 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 = ({ onClick }) => {
|
||||||
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
|
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
|
||||||
const FullscreenButton = ({
|
return (
|
||||||
onClick
|
<button
|
||||||
}) => (
|
styleName='control-fullScreenButton'
|
||||||
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
|
title={i18n.__('Fullscreen')}
|
||||||
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
|
onMouseDown={e => onClick(e)}
|
||||||
<span styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
|
>
|
||||||
|
<img src='../resources/icon/icon-full.svg' />
|
||||||
|
<span lang={i18n.locale} styleName='tooltip'>
|
||||||
|
{i18n.__('Fullscreen')}({hotkey})
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
FullscreenButton.propTypes = {
|
FullscreenButton.propTypes = {
|
||||||
onClick: PropTypes.func.isRequired
|
onClick: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -17,6 +17,10 @@
|
|||||||
opacity 0
|
opacity 0
|
||||||
transition 0.1s
|
transition 0.1s
|
||||||
|
|
||||||
|
.tooltip:lang(ja)
|
||||||
|
@extend .tooltip
|
||||||
|
right 35px
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.control-fullScreenButton
|
.control-fullScreenButton
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -14,20 +14,39 @@ class InfoPanel extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, 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' value={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,22 +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) => print(e, 'print')}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsPdf(e, 'export-pdf')}
|
||||||
|
>
|
||||||
|
<i className='fa fa-file-pdf-o' />
|
||||||
|
<p>{i18n.__('.pdf')}</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<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>
|
||||||
@@ -104,6 +147,7 @@ InfoPanel.propTypes = {
|
|||||||
exportAsMd: PropTypes.func.isRequired,
|
exportAsMd: PropTypes.func.isRequired,
|
||||||
exportAsTxt: PropTypes.func.isRequired,
|
exportAsTxt: PropTypes.func.isRequired,
|
||||||
exportAsHtml: PropTypes.func.isRequired,
|
exportAsHtml: PropTypes.func.isRequired,
|
||||||
|
exportAsPdf: PropTypes.func.isRequired,
|
||||||
wordCount: PropTypes.number,
|
wordCount: PropTypes.number,
|
||||||
letterCount: PropTypes.number,
|
letterCount: PropTypes.number,
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
right 25px
|
right 25px
|
||||||
position absolute
|
position absolute
|
||||||
padding 20px 25px 0 25px
|
padding 20px 25px 0 25px
|
||||||
width 300px
|
// width 300px
|
||||||
overflow auto
|
overflow auto
|
||||||
background-color $ui-noteList-backgroundColor
|
background-color $ui-noteList-backgroundColor
|
||||||
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
|
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
|
||||||
@@ -138,162 +138,49 @@
|
|||||||
.export--unable
|
.export--unable
|
||||||
cursor not-allowed
|
cursor not-allowed
|
||||||
|
|
||||||
body[data-theme="dark"]
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
.control-infoButton-panel
|
.control-infoButton-panel
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
|
|
||||||
.control-infoButton-panel-trash
|
.control-infoButton-panel-trash
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
|
|
||||||
.modification-date
|
.modification-date
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
.modification-date-desc
|
.modification-date-desc
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.infoPanel-defaul-count
|
.infoPanel-defaul-count
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
.infoPanel-sub-count
|
.infoPanel-sub-count
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.infoPanel-default
|
.infoPanel-default
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
.infoPanel-sub
|
.infoPanel-sub
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.infoPanel-noteLink
|
.infoPanel-noteLink
|
||||||
background-color alpha($ui-dark-borderColor, 60%)
|
background-color alpha(get-theme-var(theme, 'borderColor'), 20%)
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
[id=export-wrap]
|
[id=export-wrap]
|
||||||
button
|
button
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
&:hover
|
&:hover
|
||||||
background-color alpha($ui-dark-borderColor, 20%)
|
background-color alpha(get-theme-var(theme, 'borderColor'), 20%)
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
p
|
p
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
for theme in 'dark' 'solarized-dark' 'dracula'
|
||||||
.control-infoButton-panel
|
apply-theme(theme)
|
||||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
|
||||||
|
|
||||||
.control-infoButton-panel-trash
|
for theme in $themes
|
||||||
background-color $ui-solarized-ark-noteList-backgroundColor
|
apply-theme(theme)
|
||||||
|
|
||||||
.modification-date
|
|
||||||
color $ui-solarized-ark-text-color
|
|
||||||
|
|
||||||
.modification-date-desc
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.infoPanel-defaul-count
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
|
|
||||||
.infoPanel-sub-count
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.infoPanel-default
|
|
||||||
color $ui-solarized-ark-text-color
|
|
||||||
|
|
||||||
.infoPanel-sub
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.infoPanel-noteLink
|
|
||||||
background-color alpha($ui-solarized-dark-borderColor, 20%)
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
|
|
||||||
[id=export-wrap]
|
|
||||||
button
|
|
||||||
color $ui-dark-inactive-text-color
|
|
||||||
&:hover
|
|
||||||
background-color alpha($ui-solarized-dark-borderColor, 20%)
|
|
||||||
color $ui-solarized-ark-text-color
|
|
||||||
p
|
|
||||||
color $ui-dark-inactive-text-color
|
|
||||||
&:hover
|
|
||||||
color $ui-solarized-ark-text-color
|
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
|
||||||
.control-infoButton-panel
|
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
|
||||||
|
|
||||||
.control-infoButton-panel-trash
|
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
|
||||||
|
|
||||||
.modification-date
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
.modification-date-desc
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.infoPanel-defaul-count
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
.infoPanel-sub-count
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.infoPanel-default
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
.infoPanel-sub
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.infoPanel-noteLink
|
|
||||||
background-color alpha($ui-monokai-borderColor, 20%)
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
[id=export-wrap]
|
|
||||||
button
|
|
||||||
color $ui-dark-inactive-text-color
|
|
||||||
&:hover
|
|
||||||
background-color alpha($ui-monokai-borderColor, 20%)
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
p
|
|
||||||
color $ui-dark-inactive-text-color
|
|
||||||
&:hover
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
|
||||||
.control-infoButton-panel
|
|
||||||
background-color $ui-dracula-noteList-backgroundColor
|
|
||||||
|
|
||||||
.control-infoButton-panel-trash
|
|
||||||
background-color $ui-dracula-noteList-backgroundColor
|
|
||||||
|
|
||||||
.modification-date
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
|
|
||||||
.modification-date-desc
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.infoPanel-defaul-count
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
|
|
||||||
.infoPanel-sub-count
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.infoPanel-default
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
|
|
||||||
.infoPanel-sub
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.infoPanel-noteLink
|
|
||||||
background-color alpha($ui-dracula-borderColor, 20%)
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
|
|
||||||
[id=export-wrap]
|
|
||||||
button
|
|
||||||
color $ui-dark-inactive-text-color
|
|
||||||
&:hover
|
|
||||||
background-color alpha($ui-dracula-borderColor, 20%)
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
p
|
|
||||||
color $ui-dark-inactive-text-color
|
|
||||||
&:hover
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
@@ -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
|
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--unable'>
|
<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>
|
||||||
@@ -61,7 +87,8 @@ InfoPanelTrashed.propTypes = {
|
|||||||
createdAt: PropTypes.string.isRequired,
|
createdAt: PropTypes.string.isRequired,
|
||||||
exportAsMd: PropTypes.func.isRequired,
|
exportAsMd: PropTypes.func.isRequired,
|
||||||
exportAsTxt: PropTypes.func.isRequired,
|
exportAsTxt: PropTypes.func.isRequired,
|
||||||
exportAsHtml: PropTypes.func.isRequired
|
exportAsHtml: PropTypes.func.isRequired,
|
||||||
|
exportAsPdf: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(InfoPanelTrashed, styles)
|
export default CSSModules(InfoPanelTrashed, styles)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable camelcase */
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
@@ -9,7 +10,6 @@ import StarButton from './StarButton'
|
|||||||
import TagSelect from './TagSelect'
|
import TagSelect from './TagSelect'
|
||||||
import FolderSelect from './FolderSelect'
|
import FolderSelect from './FolderSelect'
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import { hashHistory } from 'react-router'
|
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import markdown from 'browser/lib/markdownTextHelper'
|
import markdown from 'browser/lib/markdownTextHelper'
|
||||||
import StatusBar from '../StatusBar'
|
import StatusBar from '../StatusBar'
|
||||||
@@ -30,6 +30,9 @@ import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
|||||||
import striptags from 'striptags'
|
import striptags from 'striptags'
|
||||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||||
import markdownToc from 'browser/lib/markdown-toc-generator'
|
import markdownToc from 'browser/lib/markdown-toc-generator'
|
||||||
|
import queryString from 'query-string'
|
||||||
|
import { replace } from 'connected-react-router'
|
||||||
|
import ToggleDirectionButton from 'browser/main/Detail/ToggleDirectionButton'
|
||||||
|
|
||||||
class MarkdownNoteDetail extends React.Component {
|
class MarkdownNoteDetail extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -37,20 +40,27 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isMovingNote: false,
|
isMovingNote: false,
|
||||||
note: Object.assign({
|
note: Object.assign(
|
||||||
|
{
|
||||||
title: '',
|
title: '',
|
||||||
content: '',
|
content: '',
|
||||||
linesHighlighted: []
|
linesHighlighted: []
|
||||||
}, props.note),
|
},
|
||||||
isLockButtonShown: false,
|
props.note
|
||||||
|
),
|
||||||
|
isLockButtonShown: props.config.editor.type !== 'SPLIT',
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
editorType: props.config.editor.type
|
editorType: props.config.editor.type,
|
||||||
|
switchPreview: props.config.editor.switchPreview,
|
||||||
|
RTL: false
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dispatchTimer = null
|
this.dispatchTimer = null
|
||||||
|
|
||||||
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
||||||
this.generateToc = () => this.handleGenerateToc()
|
this.generateToc = this.handleGenerateToc.bind(this)
|
||||||
|
this.handleUpdateContent = this.handleUpdateContent.bind(this)
|
||||||
|
this.handleSwitchStackDirection = this.handleSwitchStackDirection.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
@@ -58,31 +68,53 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
ee.on('editor:orientation', this.handleSwitchStackDirection)
|
||||||
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
|
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) {
|
UNSAFE_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()
|
this.refs.content.reload()
|
||||||
if (this.refs.tags) this.refs.tags.reset()
|
if (this.refs.tags) this.refs.tags.reset()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
const { switchPreview } = nextProps.config.editor
|
||||||
|
|
||||||
|
if (this.state.switchPreview !== switchPreview) {
|
||||||
|
this.setState({
|
||||||
|
switchPreview
|
||||||
})
|
})
|
||||||
|
if (switchPreview === 'BLUR' || switchPreview === 'DBL_CLICK') {
|
||||||
|
console.log('setting focus', switchPreview)
|
||||||
|
this.focus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
@@ -96,7 +128,16 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
handleUpdateContent() {
|
handleUpdateContent() {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
note.content = this.refs.content.value
|
note.content = this.refs.content.value
|
||||||
note.title = markdown.strip(striptags(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 = markdown.strip(title)
|
||||||
|
note.title = title
|
||||||
|
|
||||||
this.updateNote(note)
|
this.updateNote(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,9 +160,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
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)
|
|
||||||
.then((note) => {
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
@@ -139,46 +178,53 @@ 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,
|
isMovingNote: true,
|
||||||
note: Object.assign({}, newNote)
|
note: Object.assign({}, newNote)
|
||||||
}, () => {
|
},
|
||||||
|
() => {
|
||||||
const { dispatch, location } = this.props
|
const { dispatch, location } = this.props
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'MOVE_NOTE',
|
type: 'MOVE_NOTE',
|
||||||
originNote: note,
|
originNote: note,
|
||||||
note: newNote
|
note: newNote
|
||||||
})
|
})
|
||||||
hashHistory.replace({
|
dispatch(
|
||||||
|
replace({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
search: queryString.stringify({
|
||||||
key: newNote.key
|
key: newNote.key
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
this.setState({
|
this.setState({
|
||||||
isMovingNote: false
|
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')
|
||||||
@@ -192,6 +238,10 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
ee.emit('export:save-html')
|
ee.emit('export:save-html')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportAsPdf() {
|
||||||
|
ee.emit('export:save-pdf')
|
||||||
|
}
|
||||||
|
|
||||||
handleKeyDown(e) {
|
handleKeyDown(e) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
// tab key
|
// tab key
|
||||||
@@ -202,7 +252,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()
|
||||||
}
|
}
|
||||||
@@ -210,9 +264,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)
|
||||||
@@ -232,7 +285,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
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',
|
||||||
@@ -248,11 +301,14 @@ 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')
|
||||||
}
|
}
|
||||||
@@ -264,13 +320,16 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
note.isTrashed = false
|
note.isTrashed = false
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
|
{
|
||||||
note
|
note
|
||||||
}, () => {
|
},
|
||||||
|
() => {
|
||||||
this.save()
|
this.save()
|
||||||
this.refs.content.reload()
|
this.refs.content.reload()
|
||||||
ee.emit('list:next')
|
ee.emit('list:next')
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFullScreenButton(e) {
|
handleFullScreenButton(e) {
|
||||||
@@ -285,7 +344,9 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getToggleLockButton() {
|
getToggleLockButton() {
|
||||||
return this.state.isLocked ? '../resources/icon/icon-previewoff-on.svg' : '../resources/icon/icon-previewoff-off.svg'
|
return this.state.isLocked
|
||||||
|
? '../resources/icon/icon-lock.svg'
|
||||||
|
: '../resources/icon/icon-unlock.svg'
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteKeyDown(e) {
|
handleDeleteKeyDown(e) {
|
||||||
@@ -294,7 +355,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
handleToggleLockButton(event, noteStatus) {
|
handleToggleLockButton(event, noteStatus) {
|
||||||
// first argument event is not used
|
// first argument event is not used
|
||||||
if (this.props.config.editor.switchPreview === 'BLUR' && noteStatus === 'CODE') {
|
if (noteStatus === 'CODE') {
|
||||||
this.setState({ isLockButtonShown: true })
|
this.setState({ isLockButtonShown: true })
|
||||||
} else {
|
} else {
|
||||||
this.setState({ isLockButtonShown: false })
|
this.setState({ isLockButtonShown: false })
|
||||||
@@ -312,7 +373,9 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
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) {
|
||||||
@@ -320,12 +383,37 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSwitchMode(type) {
|
handleSwitchMode(type) {
|
||||||
this.setState({ editorType: type }, () => {
|
// If in split mode, hide the lock button
|
||||||
|
this.setState(
|
||||||
|
{ editorType: type, isLockButtonShown: type !== 'SPLIT' },
|
||||||
|
() => {
|
||||||
this.focus()
|
this.focus()
|
||||||
const newConfig = Object.assign({}, this.props.config)
|
const newConfig = Object.assign({}, this.props.config)
|
||||||
newConfig.editor.type = type
|
newConfig.editor.type = type
|
||||||
ConfigManager.set(newConfig)
|
ConfigManager.set(newConfig)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSwitchStackDirection() {
|
||||||
|
this.setState(
|
||||||
|
prevState => ({ isStacking: !prevState.isStacking }),
|
||||||
|
() => {
|
||||||
|
this.focus()
|
||||||
|
const newConfig = Object.assign({}, this.props.config)
|
||||||
|
newConfig.ui.isStacking = this.state.isStacking
|
||||||
|
ConfigManager.set(newConfig)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSwitchDirection() {
|
||||||
|
if (!this.props.config.editor.rtlEnabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If in split mode, hide the lock button
|
||||||
|
const direction = this.state.RTL
|
||||||
|
this.setState({ RTL: !direction })
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteNote() {
|
handleDeleteNote() {
|
||||||
@@ -336,14 +424,16 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
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
|
||||||
|
.map(line => {
|
||||||
const trimmedLine = line.trim()
|
const trimmedLine = line.trim()
|
||||||
if (trimmedLine.match(/\[x\]/i)) {
|
if (trimmedLine.match(/\[x\]/i)) {
|
||||||
return line.replace(/\[x\]/i, '[ ]')
|
return line.replace(/\[x\]/i, '[ ]')
|
||||||
} else {
|
} else {
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
}).join('\n')
|
})
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
note.content = clearTodoContent
|
note.content = clearTodoContent
|
||||||
this.refs.content.setValue(note.content)
|
this.refs.content.setValue(note.content)
|
||||||
@@ -353,10 +443,11 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
renderEditor() {
|
renderEditor() {
|
||||||
const { config, ignorePreviewPointerEvents } = this.props
|
const { config, ignorePreviewPointerEvents } = this.props
|
||||||
const { note } = this.state
|
const { note, isStacking } = this.state
|
||||||
|
|
||||||
if (this.state.editorType === 'EDITOR_PREVIEW') {
|
if (this.state.editorType === 'EDITOR_PREVIEW') {
|
||||||
return <MarkdownEditor
|
return (
|
||||||
|
<MarkdownEditor
|
||||||
ref='content'
|
ref='content'
|
||||||
styleName='body-noteEditor'
|
styleName='body-noteEditor'
|
||||||
config={config}
|
config={config}
|
||||||
@@ -364,69 +455,90 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
storageKey={note.storage}
|
storageKey={note.storage}
|
||||||
noteKey={note.key}
|
noteKey={note.key}
|
||||||
linesHighlighted={note.linesHighlighted}
|
linesHighlighted={note.linesHighlighted}
|
||||||
onChange={this.handleUpdateContent.bind(this)}
|
onChange={this.handleUpdateContent}
|
||||||
|
isLocked={this.state.isLocked}
|
||||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
|
RTL={config.editor.rtlEnabled && this.state.RTL}
|
||||||
/>
|
/>
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
return <MarkdownSplitEditor
|
return (
|
||||||
|
<MarkdownSplitEditor
|
||||||
ref='content'
|
ref='content'
|
||||||
config={config}
|
config={config}
|
||||||
value={note.content}
|
value={note.content}
|
||||||
storageKey={note.storage}
|
storageKey={note.storage}
|
||||||
noteKey={note.key}
|
noteKey={note.key}
|
||||||
|
isStacking={isStacking}
|
||||||
linesHighlighted={note.linesHighlighted}
|
linesHighlighted={note.linesHighlighted}
|
||||||
onChange={this.handleUpdateContent.bind(this)}
|
onChange={this.handleUpdateContent}
|
||||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
|
RTL={config.editor.rtlEnabled && this.state.RTL}
|
||||||
/>
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { data, 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
|
||||||
const folderKey = note.folder
|
const folderKey = note.folder
|
||||||
|
|
||||||
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 trashTopBar = <div styleName='info'>
|
const currentOption = _.find(
|
||||||
|
options,
|
||||||
|
option =>
|
||||||
|
option.storage.key === storageKey && option.folder.key === folderKey
|
||||||
|
)
|
||||||
|
|
||||||
|
// currentOption may be undefined
|
||||||
|
const storageName = _.get(currentOption, 'storage.name') || ''
|
||||||
|
const folderName = _.get(currentOption, 'folder.name') || ''
|
||||||
|
|
||||||
|
const trashTopBar = (
|
||||||
|
<div styleName='info'>
|
||||||
<div styleName='info-left'>
|
<div styleName='info-left'>
|
||||||
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
|
<RestoreButton onClick={e => this.handleUndoButtonClick(e)} />
|
||||||
</div>
|
</div>
|
||||||
<div styleName='info-right'>
|
<div styleName='info-right'>
|
||||||
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
<PermanentDeleteButton
|
||||||
<InfoButton
|
onClick={e => this.handleTrashButtonClick(e)}
|
||||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
|
||||||
/>
|
/>
|
||||||
|
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
|
||||||
<InfoPanelTrashed
|
<InfoPanelTrashed
|
||||||
storageName={currentOption.storage.name}
|
storageName={storageName}
|
||||||
folderName={currentOption.folder.name}
|
folderName={folderName}
|
||||||
updatedAt={formatDate(note.updatedAt)}
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
createdAt={formatDate(note.createdAt)}
|
createdAt={formatDate(note.createdAt)}
|
||||||
exportAsHtml={this.exportAsHtml}
|
exportAsHtml={this.exportAsHtml}
|
||||||
exportAsMd={this.exportAsMd}
|
exportAsMd={this.exportAsMd}
|
||||||
exportAsTxt={this.exportAsTxt}
|
exportAsTxt={this.exportAsTxt}
|
||||||
|
exportAsPdf={this.exportAsPdf}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
const detailTopBar = <div styleName='info'>
|
const detailTopBar = (
|
||||||
|
<div styleName='info'>
|
||||||
<div styleName='info-left'>
|
<div styleName='info-left'>
|
||||||
<div styleName='info-left-top'>
|
<div>
|
||||||
<FolderSelect styleName='info-left-top-folderSelect'
|
<FolderSelect
|
||||||
|
styleName='info-left-top-folderSelect'
|
||||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||||
ref='folder'
|
ref='folder'
|
||||||
data={data}
|
data={data}
|
||||||
onChange={(e) => this.handleFolderChange(e)}
|
onChange={e => this.handleFolderChange(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -436,70 +548,88 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
||||||
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||||
data={data}
|
data={data}
|
||||||
|
dispatch={dispatch}
|
||||||
onChange={this.handleUpdateTag.bind(this)}
|
onChange={this.handleUpdateTag.bind(this)}
|
||||||
|
coloredTags={config.coloredTags}
|
||||||
|
/>
|
||||||
|
<TodoListPercentage
|
||||||
|
onClearCheckboxClick={e => this.handleClearTodo(e)}
|
||||||
|
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
|
||||||
/>
|
/>
|
||||||
<TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
|
||||||
</div>
|
</div>
|
||||||
<div styleName='info-right'>
|
<div styleName='info-right'>
|
||||||
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
<ToggleModeButton
|
||||||
|
onClick={e => this.handleSwitchMode(e)}
|
||||||
|
editorType={editorType}
|
||||||
|
/>
|
||||||
|
{this.props.config.editor.rtlEnabled && (
|
||||||
|
<ToggleDirectionButton
|
||||||
|
onClick={e => this.handleSwitchDirection(e)}
|
||||||
|
isRTL={this.state.RTL}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<StarButton
|
<StarButton
|
||||||
onClick={(e) => this.handleStarButtonClick(e)}
|
onClick={e => this.handleStarButtonClick(e)}
|
||||||
isActive={note.isStarred}
|
isActive={note.isStarred}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{(() => {
|
{(() => {
|
||||||
const imgSrc = `${this.getToggleLockButton()}`
|
const imgSrc = `${this.getToggleLockButton()}`
|
||||||
const lockButtonComponent =
|
const lockButtonComponent = (
|
||||||
<button styleName='control-lockButton'
|
<button
|
||||||
onFocus={(e) => this.handleFocus(e)}
|
styleName='control-lockButton'
|
||||||
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
|
onFocus={e => this.handleFocus(e)}
|
||||||
|
onMouseDown={e => this.handleLockButtonMouseDown(e)}
|
||||||
>
|
>
|
||||||
<img styleName='iconInfo' src={imgSrc} />
|
<img src={imgSrc} />
|
||||||
{this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
|
{this.state.isLocked ? (
|
||||||
|
<span styleName='tooltip'>Unlock</span>
|
||||||
|
) : (
|
||||||
|
<span styleName='tooltip'>Lock</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
return (
|
|
||||||
this.state.isLockButtonShown ? lockButtonComponent : ''
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return this.state.isLockButtonShown ? lockButtonComponent : ''
|
||||||
})()}
|
})()}
|
||||||
|
|
||||||
<FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
|
<FullscreenButton onClick={e => this.handleFullScreenButton(e)} />
|
||||||
|
|
||||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
<TrashButton onClick={e => this.handleTrashButtonClick(e)} />
|
||||||
|
|
||||||
<InfoButton
|
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
|
||||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InfoPanel
|
<InfoPanel
|
||||||
storageName={currentOption.storage.name}
|
storageName={storageName}
|
||||||
folderName={currentOption.folder.name}
|
folderName={folderName}
|
||||||
noteLink={`[${note.title}](:note:${location.query.key})`}
|
noteLink={`[${note.title}](:note:${
|
||||||
|
queryString.parse(location.search).key
|
||||||
|
})`}
|
||||||
updatedAt={formatDate(note.updatedAt)}
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
createdAt={formatDate(note.createdAt)}
|
createdAt={formatDate(note.createdAt)}
|
||||||
exportAsMd={this.exportAsMd}
|
exportAsMd={this.exportAsMd}
|
||||||
exportAsTxt={this.exportAsTxt}
|
exportAsTxt={this.exportAsTxt}
|
||||||
exportAsHtml={this.exportAsHtml}
|
exportAsHtml={this.exportAsHtml}
|
||||||
wordCount={note.content.split(' ').length}
|
exportAsPdf={this.exportAsPdf}
|
||||||
|
wordCount={note.content.trim().split(/\s+/g).length}
|
||||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||||
type={note.type}
|
type={note.type}
|
||||||
print={this.print}
|
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'])}
|
||||||
@@ -513,9 +643,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
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
.control-lockButton
|
.control-lockButton
|
||||||
topBarButtonRight()
|
topBarButtonRight()
|
||||||
position absolute
|
position absolute
|
||||||
right 225px
|
right 265px
|
||||||
&:hover .tooltip
|
&:hover .tooltip
|
||||||
opacity 1
|
opacity 1
|
||||||
|
|
||||||
@@ -66,18 +66,14 @@ body[data-theme="dark"]
|
|||||||
.control-fullScreenButton
|
.control-fullScreenButton
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
|
|
||||||
|
apply-theme(theme)
|
||||||
body[data-theme="solarized-dark"]
|
body[data-theme={theme}]
|
||||||
.root
|
.root
|
||||||
border-left 1px solid $ui-solarized-dark-borderColor
|
border-left 1px solid get-theme-var(theme, 'borderColor')
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
for theme in 'solarized-dark' 'dracula'
|
||||||
.root
|
apply-theme(theme)
|
||||||
border-left 1px solid $ui-monokai-borderColor
|
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
for theme in $themes
|
||||||
.root
|
apply-theme(theme)
|
||||||
border-left 1px solid $ui-dracula-borderColor
|
|
||||||
background-color $ui-dracula-noteDetail-backgroundColor
|
|
||||||
|
|||||||
@@ -15,6 +15,14 @@ $info-margin-under-border = 30px
|
|||||||
padding 0 20px
|
padding 0 20px
|
||||||
z-index 99
|
z-index 99
|
||||||
|
|
||||||
|
.info > div
|
||||||
|
> button
|
||||||
|
-webkit-user-drag none
|
||||||
|
user-select none
|
||||||
|
> img, span
|
||||||
|
-webkit-user-drag none
|
||||||
|
user-select none
|
||||||
|
|
||||||
.info-left
|
.info-left
|
||||||
padding 0 10px
|
padding 0 10px
|
||||||
width 100%
|
width 100%
|
||||||
@@ -94,17 +102,14 @@ body[data-theme="dark"]
|
|||||||
.undo-button
|
.undo-button
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
.info
|
.info
|
||||||
border-color $ui-solarized-dark-borderColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
for theme in 'solarized-dark' 'dracula'
|
||||||
.info
|
apply-theme(theme)
|
||||||
border-color $ui-monokai-borderColor
|
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
for theme in $themes
|
||||||
.info
|
apply-theme(theme)
|
||||||
border-color $ui-dracula-borderColor
|
|
||||||
background-color $ui-dracula-noteDetail-backgroundColor
|
|
||||||
|
|||||||
@@ -4,13 +4,9 @@ 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)}>
|
||||||
}) => (
|
<img src='../resources/icon/icon-trash.svg' />
|
||||||
<button styleName='control-trashButton--in-trash'
|
|
||||||
onClick={(e) => onClick(e)}
|
|
||||||
>
|
|
||||||
<img styleName='iconInfo' 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>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import StarButton from './StarButton'
|
|||||||
import TagSelect from './TagSelect'
|
import TagSelect from './TagSelect'
|
||||||
import FolderSelect from './FolderSelect'
|
import FolderSelect from './FolderSelect'
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import {hashHistory} from 'react-router'
|
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
import 'codemirror-mode-elixir'
|
import 'codemirror-mode-elixir'
|
||||||
@@ -18,8 +17,8 @@ import context from 'browser/lib/context'
|
|||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
||||||
import convertModeName from 'browser/lib/convertModeName'
|
|
||||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
|
import FullscreenButton from './FullscreenButton'
|
||||||
import TrashButton from './TrashButton'
|
import TrashButton from './TrashButton'
|
||||||
import RestoreButton from './RestoreButton'
|
import RestoreButton from './RestoreButton'
|
||||||
import PermanentDeleteButton from './PermanentDeleteButton'
|
import PermanentDeleteButton from './PermanentDeleteButton'
|
||||||
@@ -30,6 +29,8 @@ import { formatDate } from 'browser/lib/date-formatter'
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||||
import markdownToc from 'browser/lib/markdown-toc-generator'
|
import markdownToc from 'browser/lib/markdown-toc-generator'
|
||||||
|
import queryString from 'query-string'
|
||||||
|
import { replace } from 'connected-react-router'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
@@ -45,11 +46,17 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
showArrows: false,
|
showArrows: false,
|
||||||
enableLeftArrow: false,
|
enableLeftArrow: false,
|
||||||
enableRightArrow: false,
|
enableRightArrow: false,
|
||||||
note: Object.assign({
|
note: Object.assign(
|
||||||
|
{
|
||||||
description: ''
|
description: ''
|
||||||
}, props.note, {
|
},
|
||||||
snippets: props.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet))
|
props.note,
|
||||||
})
|
{
|
||||||
|
snippets: props.note.snippets.map(snippet =>
|
||||||
|
Object.assign({ linesHighlighted: [] }, snippet)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scrollToNextTabThreshold = 0.7
|
this.scrollToNextTabThreshold = 0.7
|
||||||
@@ -63,7 +70,8 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
if (visibleTabs.offsetWidth < allTabs.scrollWidth) {
|
if (visibleTabs.offsetWidth < allTabs.scrollWidth) {
|
||||||
this.setState({
|
this.setState({
|
||||||
showArrows: visibleTabs.offsetWidth < allTabs.scrollWidth,
|
showArrows: visibleTabs.offsetWidth < allTabs.scrollWidth,
|
||||||
enableRightArrow: allTabs.offsetLeft !== visibleTabs.offsetWidth - allTabs.scrollWidth,
|
enableRightArrow:
|
||||||
|
allTabs.offsetLeft !== visibleTabs.offsetWidth - allTabs.scrollWidth,
|
||||||
enableLeftArrow: allTabs.offsetLeft !== 0
|
enableLeftArrow: allTabs.offsetLeft !== 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -71,25 +79,37 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
|
if (
|
||||||
|
nextProps.note.key !== this.props.note.key &&
|
||||||
|
!this.state.isMovingNote
|
||||||
|
) {
|
||||||
if (this.saveQueue != null) this.saveNow()
|
if (this.saveQueue != null) this.saveNow()
|
||||||
const nextNote = Object.assign({
|
const nextNote = Object.assign(
|
||||||
|
{
|
||||||
description: ''
|
description: ''
|
||||||
}, nextProps.note, {
|
},
|
||||||
snippets: nextProps.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet))
|
nextProps.note,
|
||||||
})
|
{
|
||||||
|
snippets: nextProps.note.snippets.map(snippet =>
|
||||||
|
Object.assign({ linesHighlighted: [] }, snippet)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
|
{
|
||||||
snippetIndex: 0,
|
snippetIndex: 0,
|
||||||
note: nextNote
|
note: nextNote
|
||||||
}, () => {
|
},
|
||||||
|
() => {
|
||||||
const { snippets } = this.state.note
|
const { snippets } = this.state.note
|
||||||
snippets.forEach((snippet, index) => {
|
snippets.forEach((snippet, index) => {
|
||||||
this.refs['code-' + index].reload()
|
this.refs['code-' + index].reload()
|
||||||
})
|
})
|
||||||
if (this.refs.tags) this.refs.tags.reset()
|
if (this.refs.tags) this.refs.tags.reset()
|
||||||
this.setState(this.getArrowsState())
|
this.setState(this.getArrowsState())
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,11 +135,14 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
note.updatedAt = new Date()
|
note.updatedAt = new Date()
|
||||||
note.title = findNoteTitle(note.description, false)
|
note.title = findNoteTitle(note.description, false)
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
|
{
|
||||||
note
|
note
|
||||||
}, () => {
|
},
|
||||||
|
() => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
@@ -134,9 +157,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
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)
|
|
||||||
.then((note) => {
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
@@ -154,46 +175,53 @@ class SnippetNoteDetail 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,
|
isMovingNote: true,
|
||||||
note: Object.assign({}, newNote)
|
note: Object.assign({}, newNote)
|
||||||
}, () => {
|
},
|
||||||
|
() => {
|
||||||
const { dispatch, location } = this.props
|
const { dispatch, location } = this.props
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'MOVE_NOTE',
|
type: 'MOVE_NOTE',
|
||||||
originNote: note,
|
originNote: note,
|
||||||
note: newNote
|
note: newNote
|
||||||
})
|
})
|
||||||
hashHistory.replace({
|
dispatch(
|
||||||
|
replace({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
search: queryString.stringify({
|
||||||
key: newNote.key
|
key: newNote.key
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
this.setState({
|
this.setState({
|
||||||
isMovingNote: false
|
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() {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTrashButtonClick(e) {
|
handleTrashButtonClick(e) {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
@@ -205,7 +233,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
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',
|
||||||
@@ -221,11 +249,14 @@ class SnippetNoteDetail 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')
|
||||||
}
|
}
|
||||||
@@ -237,12 +268,15 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
|
|
||||||
note.isTrashed = false
|
note.isTrashed = false
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
|
{
|
||||||
note
|
note
|
||||||
}, () => {
|
},
|
||||||
|
() => {
|
||||||
this.save()
|
this.save()
|
||||||
ee.emit('list:next')
|
ee.emit('list:next')
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFullScreenButton(e) {
|
handleFullScreenButton(e) {
|
||||||
@@ -254,14 +288,18 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
const left = this.visibleTabs.scrollLeft
|
const left = this.visibleTabs.scrollLeft
|
||||||
|
|
||||||
const tabs = this.allTabs.querySelectorAll('div')
|
const tabs = this.allTabs.querySelectorAll('div')
|
||||||
const lastVisibleTab = Array.from(tabs).find((tab) => {
|
const lastVisibleTab = Array.from(tabs).find(tab => {
|
||||||
return tab.offsetLeft + tab.offsetWidth >= left
|
return tab.offsetLeft + tab.offsetWidth >= left
|
||||||
})
|
})
|
||||||
|
|
||||||
if (lastVisibleTab) {
|
if (lastVisibleTab) {
|
||||||
const visiblePart = lastVisibleTab.offsetWidth + lastVisibleTab.offsetLeft - left
|
const visiblePart =
|
||||||
const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
|
lastVisibleTab.offsetWidth + lastVisibleTab.offsetLeft - left
|
||||||
const scrollToTab = (isFullyVisible && lastVisibleTab.previousSibling)
|
const isFullyVisible =
|
||||||
|
visiblePart >
|
||||||
|
lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
|
||||||
|
const scrollToTab =
|
||||||
|
isFullyVisible && lastVisibleTab.previousSibling
|
||||||
? lastVisibleTab.previousSibling
|
? lastVisibleTab.previousSibling
|
||||||
: lastVisibleTab
|
: lastVisibleTab
|
||||||
|
|
||||||
@@ -277,14 +315,16 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
const width = this.visibleTabs.offsetWidth
|
const width = this.visibleTabs.offsetWidth
|
||||||
|
|
||||||
const tabs = this.allTabs.querySelectorAll('div')
|
const tabs = this.allTabs.querySelectorAll('div')
|
||||||
const lastVisibleTab = Array.from(tabs).find((tab) => {
|
const lastVisibleTab = Array.from(tabs).find(tab => {
|
||||||
return tab.offsetLeft + tab.offsetWidth >= width + left
|
return tab.offsetLeft + tab.offsetWidth >= width + left
|
||||||
})
|
})
|
||||||
|
|
||||||
if (lastVisibleTab) {
|
if (lastVisibleTab) {
|
||||||
const visiblePart = width + left - lastVisibleTab.offsetLeft
|
const visiblePart = width + left - lastVisibleTab.offsetLeft
|
||||||
const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
|
const isFullyVisible =
|
||||||
const scrollToTab = (isFullyVisible && lastVisibleTab.nextSibling)
|
visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
|
||||||
|
const scrollToTab =
|
||||||
|
isFullyVisible && lastVisibleTab.nextSibling
|
||||||
? lastVisibleTab.nextSibling
|
? lastVisibleTab.nextSibling
|
||||||
: lastVisibleTab
|
: lastVisibleTab
|
||||||
|
|
||||||
@@ -347,7 +387,8 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
const snippets = this.state.note.snippets.slice()
|
const snippets = this.state.note.snippets.slice()
|
||||||
snippets.splice(index, 1)
|
snippets.splice(index, 1)
|
||||||
const note = Object.assign({}, this.state.note, { snippets })
|
const note = Object.assign({}, this.state.note, { snippets })
|
||||||
const snippetIndex = this.state.snippetIndex >= snippets.length
|
const snippetIndex =
|
||||||
|
this.state.snippetIndex >= snippets.length
|
||||||
? snippets.length - 1
|
? snippets.length - 1
|
||||||
: this.state.snippetIndex
|
: this.state.snippetIndex
|
||||||
this.setState({ note, snippetIndex }, () => {
|
this.setState({ note, snippetIndex }, () => {
|
||||||
@@ -358,7 +399,10 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
this.moveTabBarBy(0)
|
this.moveTabBarBy(0)
|
||||||
} else {
|
} else {
|
||||||
const lastTab = this.allTabs.lastChild
|
const lastTab = this.allTabs.lastChild
|
||||||
if (lastTab.offsetLeft + lastTab.offsetWidth < this.visibleTabs.offsetWidth) {
|
if (
|
||||||
|
lastTab.offsetLeft + lastTab.offsetWidth <
|
||||||
|
this.visibleTabs.offsetWidth
|
||||||
|
) {
|
||||||
const width = this.visibleTabs.offsetWidth
|
const width = this.visibleTabs.offsetWidth
|
||||||
const newLeft = lastTab.offsetLeft + lastTab.offsetWidth - width
|
const newLeft = lastTab.offsetLeft + lastTab.offsetWidth - width
|
||||||
this.moveTabBarBy(newLeft > 0 ? -newLeft : 0)
|
this.moveTabBarBy(newLeft > 0 ? -newLeft : 0)
|
||||||
@@ -380,26 +424,36 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
name: mode
|
name: mode
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
|
|
||||||
|
|
||||||
this.setState(state => ({
|
this.setState(state => ({
|
||||||
|
note: Object.assign(state.note, { snippets: snippets })
|
||||||
|
}))
|
||||||
|
|
||||||
|
this.setState(
|
||||||
|
state => ({
|
||||||
note: state.note
|
note: state.note
|
||||||
}), () => {
|
}),
|
||||||
|
() => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleModeOptionClick(index, name) {
|
handleModeOptionClick(index, name) {
|
||||||
return (e) => {
|
return e => {
|
||||||
const snippets = this.state.note.snippets.slice()
|
const snippets = this.state.note.snippets.slice()
|
||||||
snippets[index].mode = name
|
snippets[index].mode = name
|
||||||
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
|
|
||||||
|
|
||||||
this.setState(state => ({
|
this.setState(state => ({
|
||||||
|
note: Object.assign(state.note, { snippets: snippets })
|
||||||
|
}))
|
||||||
|
|
||||||
|
this.setState(
|
||||||
|
state => ({
|
||||||
note: state.note
|
note: state.note
|
||||||
}), () => {
|
}),
|
||||||
|
() => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SELECT_LANG', {
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SELECT_LANG', {
|
||||||
name
|
name
|
||||||
@@ -408,17 +462,22 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleCodeChange(index) {
|
handleCodeChange(index) {
|
||||||
return (e) => {
|
return e => {
|
||||||
const snippets = this.state.note.snippets.slice()
|
const snippets = this.state.note.snippets.slice()
|
||||||
snippets[index].content = this.refs['code-' + index].value
|
snippets[index].content = this.refs['code-' + index].value
|
||||||
snippets[index].linesHighlighted = e.options.linesHighlighted
|
snippets[index].linesHighlighted = e.options.linesHighlighted
|
||||||
|
|
||||||
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
|
|
||||||
this.setState(state => ({
|
this.setState(state => ({
|
||||||
|
note: Object.assign(state.note, { snippets: snippets })
|
||||||
|
}))
|
||||||
|
this.setState(
|
||||||
|
state => ({
|
||||||
note: state.note
|
note: state.note
|
||||||
}), () => {
|
}),
|
||||||
|
() => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,7 +491,11 @@ class SnippetNoteDetail 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()
|
||||||
}
|
}
|
||||||
@@ -440,9 +503,8 @@ class SnippetNoteDetail 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)
|
||||||
@@ -452,9 +514,8 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
// L key
|
// L key
|
||||||
case 76:
|
case 76:
|
||||||
{
|
{
|
||||||
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.focus()
|
this.focus()
|
||||||
@@ -464,9 +525,8 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
// T key
|
// T key
|
||||||
case 84:
|
case 84:
|
||||||
{
|
{
|
||||||
const isSuper = global.process.platform === 'darwin'
|
const isSuper =
|
||||||
? e.metaKey
|
global.process.platform === 'darwin' ? e.metaKey : e.ctrlKey
|
||||||
: e.ctrlKey
|
|
||||||
if (isSuper && !e.shiftKey && !e.altKey) {
|
if (isSuper && !e.shiftKey && !e.altKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.addSnippet()
|
this.addSnippet()
|
||||||
@@ -478,10 +538,14 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
|
|
||||||
handleModeButtonClick(e, index) {
|
handleModeButtonClick(e, index) {
|
||||||
const templetes = []
|
const templetes = []
|
||||||
CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => {
|
CodeMirror.modeInfo
|
||||||
|
.sort(function(a, b) {
|
||||||
|
return a.name.localeCompare(b.name)
|
||||||
|
})
|
||||||
|
.forEach(mode => {
|
||||||
templetes.push({
|
templetes.push({
|
||||||
label: mode.name,
|
label: mode.name,
|
||||||
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
|
click: e => this.handleModeOptionClick(index, mode.name)(e)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
context.popup(templetes)
|
context.popup(templetes)
|
||||||
@@ -491,11 +555,11 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
context.popup([
|
context.popup([
|
||||||
{
|
{
|
||||||
label: 'tab',
|
label: 'tab',
|
||||||
click: (e) => this.handleIndentTypeItemClick(e, 'tab')
|
click: e => this.handleIndentTypeItemClick(e, 'tab')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'space',
|
label: 'space',
|
||||||
click: (e) => this.handleIndentTypeItemClick(e, 'space')
|
click: e => this.handleIndentTypeItemClick(e, 'space')
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@@ -504,15 +568,28 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
context.popup([
|
context.popup([
|
||||||
{
|
{
|
||||||
label: '2',
|
label: '2',
|
||||||
click: (e) => this.handleIndentSizeItemClick(e, 2)
|
click: e => this.handleIndentSizeItemClick(e, 2)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '4',
|
label: '4',
|
||||||
click: (e) => this.handleIndentSizeItemClick(e, 4)
|
click: e => this.handleIndentSizeItemClick(e, 4)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '8',
|
label: '8',
|
||||||
click: (e) => this.handleIndentSizeItemClick(e, 8)
|
click: e => this.handleIndentSizeItemClick(e, 8)
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWrapLineButtonClick(e) {
|
||||||
|
context.popup([
|
||||||
|
{
|
||||||
|
label: 'on',
|
||||||
|
click: e => this.handleWrapLineItemClick(e, true)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'off',
|
||||||
|
click: e => this.handleWrapLineItemClick(e, false)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@@ -549,17 +626,33 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleWrapLineItemClick(e, lineWrapping) {
|
||||||
|
const { config, dispatch } = this.props
|
||||||
|
const editor = Object.assign({}, config.editor, {
|
||||||
|
lineWrapping
|
||||||
|
})
|
||||||
|
ConfigManager.set({
|
||||||
|
editor
|
||||||
|
})
|
||||||
|
dispatch({
|
||||||
|
type: 'SET_CONFIG',
|
||||||
|
config: {
|
||||||
|
editor
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
this.refs.description.focus()
|
this.refs.description.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
moveToTab(tab) {
|
moveToTab(tab) {
|
||||||
const easeOutCubic = t => (--t) * t * t + 1
|
const easeOutCubic = t => --t * t * t + 1
|
||||||
const startScrollPosition = this.visibleTabs.scrollLeft
|
const startScrollPosition = this.visibleTabs.scrollLeft
|
||||||
const animationTiming = 300
|
const animationTiming = 300
|
||||||
const scrollMoreCoeff = 1.4 // introduce coefficient, because we want to scroll a bit further to see next tab
|
const scrollMoreCoeff = 1.4 // introduce coefficient, because we want to scroll a bit further to see next tab
|
||||||
|
|
||||||
let scrollBy = (tab.offsetLeft - startScrollPosition)
|
let scrollBy = tab.offsetLeft - startScrollPosition
|
||||||
|
|
||||||
if (tab.offsetLeft > startScrollPosition) {
|
if (tab.offsetLeft > startScrollPosition) {
|
||||||
// if tab is on the right side and we want to show the whole tab in visible area,
|
// if tab is on the right side and we want to show the whole tab in visible area,
|
||||||
@@ -568,7 +661,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
// |____|_______|________|________|_show_this_|
|
// |____|_______|________|________|_show_this_|
|
||||||
// ↑_____________________↑
|
// ↑_____________________↑
|
||||||
// visible area
|
// visible area
|
||||||
scrollBy += (tab.offsetWidth - this.visibleTabs.offsetWidth)
|
scrollBy += tab.offsetWidth - this.visibleTabs.offsetWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
let startTime = null
|
let startTime = null
|
||||||
@@ -576,7 +669,8 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
startTime = startTime || time
|
startTime = startTime || time
|
||||||
const elapsed = (time - startTime) / animationTiming
|
const elapsed = (time - startTime) / animationTiming
|
||||||
|
|
||||||
this.visibleTabs.scrollLeft = startScrollPosition + easeOutCubic(elapsed) * scrollBy * scrollMoreCoeff
|
this.visibleTabs.scrollLeft =
|
||||||
|
startScrollPosition + easeOutCubic(elapsed) * scrollBy * scrollMoreCoeff
|
||||||
if (elapsed < 1) {
|
if (elapsed < 1) {
|
||||||
window.requestAnimationFrame(scrollAnimation)
|
window.requestAnimationFrame(scrollAnimation)
|
||||||
} else {
|
} else {
|
||||||
@@ -592,28 +686,43 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
const visibleTabs = this.visibleTabs
|
const visibleTabs = this.visibleTabs
|
||||||
|
|
||||||
const showArrows = visibleTabs.offsetWidth < allTabs.scrollWidth
|
const showArrows = visibleTabs.offsetWidth < allTabs.scrollWidth
|
||||||
const enableRightArrow = visibleTabs.scrollLeft !== allTabs.scrollWidth - visibleTabs.offsetWidth
|
const enableRightArrow =
|
||||||
|
visibleTabs.scrollLeft !== allTabs.scrollWidth - visibleTabs.offsetWidth
|
||||||
const enableLeftArrow = visibleTabs.scrollLeft !== 0
|
const enableLeftArrow = visibleTabs.scrollLeft !== 0
|
||||||
|
|
||||||
return { showArrows, enableRightArrow, enableLeftArrow }
|
return { showArrows, enableRightArrow, enableLeftArrow }
|
||||||
}
|
}
|
||||||
|
|
||||||
addSnippet() {
|
addSnippet() {
|
||||||
const { config } = this.props
|
const {
|
||||||
|
config: {
|
||||||
|
editor: { snippetDefaultLanguage }
|
||||||
|
}
|
||||||
|
} = this.props
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
|
|
||||||
note.snippets = note.snippets.concat([{
|
const defaultLanguage =
|
||||||
|
snippetDefaultLanguage === 'Auto Detect' ? null : snippetDefaultLanguage
|
||||||
|
|
||||||
|
note.snippets = note.snippets.concat([
|
||||||
|
{
|
||||||
name: '',
|
name: '',
|
||||||
mode: config.editor.snippetDefaultLanguage || 'text',
|
mode: defaultLanguage,
|
||||||
content: '',
|
content: '',
|
||||||
linesHighlighted: []
|
linesHighlighted: []
|
||||||
}])
|
}
|
||||||
|
])
|
||||||
const snippetIndex = note.snippets.length - 1
|
const snippetIndex = note.snippets.length - 1
|
||||||
|
|
||||||
this.setState(Object.assign({
|
this.setState(
|
||||||
|
Object.assign(
|
||||||
|
{
|
||||||
note,
|
note,
|
||||||
snippetIndex
|
snippetIndex
|
||||||
}, this.getArrowsState()), () => {
|
},
|
||||||
|
this.getArrowsState()
|
||||||
|
),
|
||||||
|
() => {
|
||||||
if (this.state.showArrows) {
|
if (this.state.showArrows) {
|
||||||
const tabs = this.allTabs.querySelectorAll('div')
|
const tabs = this.allTabs.querySelectorAll('div')
|
||||||
if (tabs) {
|
if (tabs) {
|
||||||
@@ -621,23 +730,32 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.refs['tab-' + snippetIndex].startRenaming()
|
this.refs['tab-' + snippetIndex].startRenaming()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
jumpNextTab() {
|
jumpNextTab() {
|
||||||
this.setState(state => ({
|
this.setState(
|
||||||
|
state => ({
|
||||||
snippetIndex: (state.snippetIndex + 1) % state.note.snippets.length
|
snippetIndex: (state.snippetIndex + 1) % state.note.snippets.length
|
||||||
}), () => {
|
}),
|
||||||
|
() => {
|
||||||
this.focusEditor()
|
this.focusEditor()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
jumpPrevTab() {
|
jumpPrevTab() {
|
||||||
this.setState(state => ({
|
this.setState(
|
||||||
snippetIndex: (state.snippetIndex - 1 + state.note.snippets.length) % state.note.snippets.length
|
state => ({
|
||||||
}), () => {
|
snippetIndex:
|
||||||
|
(state.snippetIndex - 1 + state.note.snippets.length) %
|
||||||
|
state.note.snippets.length
|
||||||
|
}),
|
||||||
|
() => {
|
||||||
this.focusEditor()
|
this.focusEditor()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
focusEditor() {
|
focusEditor() {
|
||||||
@@ -646,32 +764,40 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
|
|
||||||
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'
|
||||||
}
|
}
|
||||||
|
|
||||||
showWarning(e, msg) {
|
showWarning(e, msg) {
|
||||||
const warningMessage = (msg) => ({
|
const warningMessage = msg =>
|
||||||
|
({
|
||||||
'export-txt': 'Text export',
|
'export-txt': 'Text export',
|
||||||
'export-md': 'Markdown export',
|
'export-md': 'Markdown export',
|
||||||
'export-html': 'HTML export',
|
'export-html': 'HTML export',
|
||||||
'print': 'Print'
|
'export-pdf': 'PDF export',
|
||||||
})[msg]
|
print: 'Print'
|
||||||
|
}[msg])
|
||||||
|
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: i18n.__('Sorry!'),
|
message: i18n.__('Sorry!'),
|
||||||
detail: i18n.__(warningMessage(msg) + ' is available only in markdown notes.'),
|
detail: i18n.__(
|
||||||
|
warningMessage(msg) + ' is available only in markdown notes.'
|
||||||
|
),
|
||||||
buttons: [i18n.__('OK')]
|
buttons: [i18n.__('OK')]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { data, config, location } = this.props
|
const { data, dispatch, config, location } = this.props
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
|
|
||||||
const storageKey = note.storage
|
const storageKey = note.storage
|
||||||
const folderKey = note.folder
|
const folderKey = note.folder
|
||||||
|
|
||||||
|
const autoDetect = config.editor.snippetDefaultLanguage === 'Auto Detect'
|
||||||
|
|
||||||
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)
|
||||||
@@ -680,42 +806,52 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
const tabList = note.snippets.map((snippet, index) => {
|
const tabList = note.snippets.map((snippet, index) => {
|
||||||
const isActive = this.state.snippetIndex === index
|
const isActive = this.state.snippetIndex === index
|
||||||
|
|
||||||
return <SnippetTab
|
return (
|
||||||
|
<SnippetTab
|
||||||
key={index}
|
key={index}
|
||||||
ref={'tab-' + index}
|
ref={'tab-' + index}
|
||||||
snippet={snippet}
|
snippet={snippet}
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
onClick={(e) => this.handleTabButtonClick(e, index)}
|
onClick={e => this.handleTabButtonClick(e, index)}
|
||||||
onDelete={(e) => this.handleTabDeleteButtonClick(e, index)}
|
onDelete={e => this.handleTabDeleteButtonClick(e, index)}
|
||||||
onRename={(name) => this.renameSnippetByIndex(index, name)}
|
onRename={name => this.renameSnippetByIndex(index, name)}
|
||||||
isDeletable={note.snippets.length > 1}
|
isDeletable={note.snippets.length > 1}
|
||||||
onDragStart={(e) => this.handleTabDragStart(e, index)}
|
onDragStart={e => this.handleTabDragStart(e, index)}
|
||||||
onDrop={(e) => this.handleTabDrop(e, index)}
|
onDrop={e => this.handleTabDrop(e, index)}
|
||||||
/>
|
/>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const viewList = note.snippets.map((snippet, index) => {
|
const viewList = note.snippets.map((snippet, index) => {
|
||||||
const isActive = this.state.snippetIndex === index
|
const isActive = this.state.snippetIndex === index
|
||||||
let syntax = CodeMirror.findModeByName(convertModeName(snippet.mode))
|
return (
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
<div
|
||||||
return <div styleName='tabView'
|
styleName='tabView'
|
||||||
key={index}
|
key={index}
|
||||||
style={{ zIndex: isActive ? 5 : 4 }}
|
style={{ zIndex: isActive ? 5 : 4 }}
|
||||||
>
|
>
|
||||||
{snippet.mode === 'Markdown' || snippet.mode === 'GitHub Flavored Markdown'
|
{snippet.mode === 'Markdown' ||
|
||||||
? <MarkdownEditor styleName='tabView-content'
|
snippet.mode === 'GitHub Flavored Markdown' ? (
|
||||||
|
<MarkdownEditor
|
||||||
|
styleName='tabView-content'
|
||||||
value={snippet.content}
|
value={snippet.content}
|
||||||
config={config}
|
config={config}
|
||||||
linesHighlighted={snippet.linesHighlighted}
|
linesHighlighted={snippet.linesHighlighted}
|
||||||
onChange={(e) => this.handleCodeChange(index)(e)}
|
onChange={e => this.handleCodeChange(index)(e)}
|
||||||
ref={'code-' + index}
|
ref={'code-' + index}
|
||||||
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
|
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
|
||||||
storageKey={storageKey}
|
storageKey={storageKey}
|
||||||
/>
|
/>
|
||||||
: <CodeEditor styleName='tabView-content'
|
) : (
|
||||||
mode={snippet.mode}
|
<CodeEditor
|
||||||
|
styleName='tabView-content'
|
||||||
|
mode={
|
||||||
|
snippet.mode ||
|
||||||
|
(autoDetect ? null : config.editor.snippetDefaultLanguage)
|
||||||
|
}
|
||||||
value={snippet.content}
|
value={snippet.content}
|
||||||
linesHighlighted={snippet.linesHighlighted}
|
linesHighlighted={snippet.linesHighlighted}
|
||||||
|
lineWrapping={config.editor.lineWrapping}
|
||||||
theme={config.editor.theme}
|
theme={config.editor.theme}
|
||||||
fontFamily={config.editor.fontFamily}
|
fontFamily={config.editor.fontFamily}
|
||||||
fontSize={editorFontSize}
|
fontSize={editorFontSize}
|
||||||
@@ -723,64 +859,84 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
displayLineNumbers={config.editor.displayLineNumbers}
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
matchingPairs={config.editor.matchingPairs}
|
matchingPairs={config.editor.matchingPairs}
|
||||||
|
matchingCloseBefore={config.editor.matchingCloseBefore}
|
||||||
matchingTriples={config.editor.matchingTriples}
|
matchingTriples={config.editor.matchingTriples}
|
||||||
explodingPairs={config.editor.explodingPairs}
|
explodingPairs={config.editor.explodingPairs}
|
||||||
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
||||||
|
codeBlockMatchingCloseBefore={
|
||||||
|
config.editor.codeBlockMatchingCloseBefore
|
||||||
|
}
|
||||||
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
||||||
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
||||||
keyMap={config.editor.keyMap}
|
keyMap={config.editor.keyMap}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||||
enableTableEditor={config.editor.enableTableEditor}
|
enableTableEditor={config.editor.enableTableEditor}
|
||||||
onChange={(e) => this.handleCodeChange(index)(e)}
|
onChange={e => this.handleCodeChange(index)(e)}
|
||||||
ref={'code-' + index}
|
ref={'code-' + index}
|
||||||
enableSmartPaste={config.editor.enableSmartPaste}
|
enableSmartPaste={config.editor.enableSmartPaste}
|
||||||
hotkey={config.hotkey}
|
hotkey={config.hotkey}
|
||||||
|
autoDetect={autoDetect}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
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 trashTopBar = <div styleName='info'>
|
const currentOption = _.find(
|
||||||
|
options,
|
||||||
|
option =>
|
||||||
|
option.storage.key === storageKey && option.folder.key === folderKey
|
||||||
|
)
|
||||||
|
|
||||||
|
// currentOption may be undefined
|
||||||
|
const storageName = _.get(currentOption, 'storage.name') || ''
|
||||||
|
const folderName = _.get(currentOption, 'folder.name') || ''
|
||||||
|
|
||||||
|
const trashTopBar = (
|
||||||
|
<div styleName='info'>
|
||||||
<div styleName='info-left'>
|
<div styleName='info-left'>
|
||||||
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
|
<RestoreButton onClick={e => this.handleUndoButtonClick(e)} />
|
||||||
</div>
|
</div>
|
||||||
<div styleName='info-right'>
|
<div styleName='info-right'>
|
||||||
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
<PermanentDeleteButton
|
||||||
<InfoButton
|
onClick={e => this.handleTrashButtonClick(e)}
|
||||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
|
||||||
/>
|
/>
|
||||||
|
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
|
||||||
<InfoPanelTrashed
|
<InfoPanelTrashed
|
||||||
storageName={currentOption.storage.name}
|
storageName={storageName}
|
||||||
folderName={currentOption.folder.name}
|
folderName={folderName}
|
||||||
updatedAt={formatDate(note.updatedAt)}
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
createdAt={formatDate(note.createdAt)}
|
createdAt={formatDate(note.createdAt)}
|
||||||
exportAsMd={this.showWarning}
|
exportAsMd={this.showWarning}
|
||||||
exportAsTxt={this.showWarning}
|
exportAsTxt={this.showWarning}
|
||||||
exportAsHtml={this.showWarning}
|
exportAsHtml={this.showWarning}
|
||||||
|
exportAsPdf={this.showWarning}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
const detailTopBar = <div styleName='info'>
|
const detailTopBar = (
|
||||||
|
<div styleName='info'>
|
||||||
<div styleName='info-left'>
|
<div styleName='info-left'>
|
||||||
<div styleName='info-left-top'>
|
<div>
|
||||||
<FolderSelect styleName='info-left-top-folderSelect'
|
<FolderSelect
|
||||||
|
styleName='info-left-top-folderSelect'
|
||||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||||
ref='folder'
|
ref='folder'
|
||||||
data={data}
|
data={data}
|
||||||
onChange={(e) => this.handleFolderChange(e)}
|
onChange={e => this.handleFolderChange(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -790,47 +946,48 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
||||||
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||||
data={data}
|
data={data}
|
||||||
onChange={(e) => this.handleChange(e)}
|
dispatch={dispatch}
|
||||||
|
onChange={e => this.handleChange(e)}
|
||||||
|
coloredTags={config.coloredTags}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='info-right'>
|
<div styleName='info-right'>
|
||||||
<StarButton
|
<StarButton
|
||||||
onClick={(e) => this.handleStarButtonClick(e)}
|
onClick={e => this.handleStarButtonClick(e)}
|
||||||
isActive={note.isStarred}
|
isActive={note.isStarred}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')}
|
<FullscreenButton onClick={e => this.handleFullScreenButton(e)} />
|
||||||
onMouseDown={(e) => this.handleFullScreenButton(e)}>
|
|
||||||
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
|
|
||||||
<span styleName='tooltip'>{i18n.__('Fullscreen')}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
<TrashButton onClick={e => this.handleTrashButtonClick(e)} />
|
||||||
|
|
||||||
<InfoButton
|
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
|
||||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InfoPanel
|
<InfoPanel
|
||||||
storageName={currentOption.storage.name}
|
storageName={storageName}
|
||||||
folderName={currentOption.folder.name}
|
folderName={folderName}
|
||||||
noteLink={`[${note.title}](:note:${location.query.key})`}
|
noteLink={`[${note.title}](:note:${
|
||||||
|
queryString.parse(location.search).key
|
||||||
|
})`}
|
||||||
updatedAt={formatDate(note.updatedAt)}
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
createdAt={formatDate(note.createdAt)}
|
createdAt={formatDate(note.createdAt)}
|
||||||
exportAsMd={this.showWarning}
|
exportAsMd={this.showWarning}
|
||||||
exportAsTxt={this.showWarning}
|
exportAsTxt={this.showWarning}
|
||||||
exportAsHtml={this.showWarning}
|
exportAsHtml={this.showWarning}
|
||||||
|
exportAsPdf={this.showWarning}
|
||||||
type={note.type}
|
type={note.type}
|
||||||
print={this.showWarning}
|
print={this.showWarning}
|
||||||
/>
|
/>
|
||||||
</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}
|
||||||
|
|
||||||
@@ -844,31 +1001,47 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
ref='description'
|
ref='description'
|
||||||
placeholder={i18n.__('Description...')}
|
placeholder={i18n.__('Description...')}
|
||||||
value={this.state.note.description}
|
value={this.state.note.description}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={e => this.handleChange(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='tabList'>
|
<div styleName='tabList'>
|
||||||
<button styleName='tabButton'
|
<button
|
||||||
|
styleName='tabButton'
|
||||||
hidden={!this.state.showArrows}
|
hidden={!this.state.showArrows}
|
||||||
disabled={!this.state.enableLeftArrow}
|
disabled={!this.state.enableLeftArrow}
|
||||||
onClick={(e) => this.handleTabMoveLeftButtonClick(e)}
|
onClick={e => this.handleTabMoveLeftButtonClick(e)}
|
||||||
>
|
>
|
||||||
<i className='fa fa-chevron-left' />
|
<i className='fa fa-chevron-left' />
|
||||||
</button>
|
</button>
|
||||||
<div styleName='list' onScroll={(e) => { this.setState(this.getArrowsState()) }} ref={(tabs) => { this.visibleTabs = tabs }}>
|
<div
|
||||||
<div styleName='allTabs' ref={(tabs) => { this.allTabs = tabs }}>
|
styleName='list'
|
||||||
|
onScroll={e => {
|
||||||
|
this.setState(this.getArrowsState())
|
||||||
|
}}
|
||||||
|
ref={tabs => {
|
||||||
|
this.visibleTabs = tabs
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
styleName='allTabs'
|
||||||
|
ref={tabs => {
|
||||||
|
this.allTabs = tabs
|
||||||
|
}}
|
||||||
|
>
|
||||||
{tabList}
|
{tabList}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button styleName='tabButton'
|
<button
|
||||||
|
styleName='tabButton'
|
||||||
hidden={!this.state.showArrows}
|
hidden={!this.state.showArrows}
|
||||||
disabled={!this.state.enableRightArrow}
|
disabled={!this.state.enableRightArrow}
|
||||||
onClick={(e) => this.handleTabMoveRightButtonClick(e)}
|
onClick={e => this.handleTabMoveRightButtonClick(e)}
|
||||||
>
|
>
|
||||||
<i className='fa fa-chevron-right' />
|
<i className='fa fa-chevron-right' />
|
||||||
</button>
|
</button>
|
||||||
<button styleName='tabButton'
|
<button
|
||||||
onClick={(e) => this.handleTabPlusButtonClick(e)}
|
styleName='tabButton'
|
||||||
|
onClick={e => this.handleTabPlusButtonClick(e)}
|
||||||
>
|
>
|
||||||
<i className='fa fa-plus' />
|
<i className='fa fa-plus' />
|
||||||
</button>
|
</button>
|
||||||
@@ -878,26 +1051,28 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
|
|
||||||
<div styleName='override'>
|
<div styleName='override'>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => this.handleModeButtonClick(e, this.state.snippetIndex)}
|
onClick={e =>
|
||||||
|
this.handleModeButtonClick(e, this.state.snippetIndex)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{this.state.note.snippets[this.state.snippetIndex].mode == null
|
{this.state.note.snippets[this.state.snippetIndex].mode == null
|
||||||
? i18n.__('Select Syntax...')
|
? i18n.__('Select Syntax...')
|
||||||
: this.state.note.snippets[this.state.snippetIndex].mode
|
: this.state.note.snippets[this.state.snippetIndex].mode}
|
||||||
}
|
|
||||||
<i className='fa fa-caret-down' />
|
<i className='fa fa-caret-down' />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button onClick={e => this.handleIndentTypeButtonClick(e)}>
|
||||||
onClick={(e) => this.handleIndentTypeButtonClick(e)}
|
|
||||||
>
|
|
||||||
Indent: {config.editor.indentType}
|
Indent: {config.editor.indentType}
|
||||||
<i className='fa fa-caret-down' />
|
<i className='fa fa-caret-down' />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button onClick={e => this.handleIndentSizeButtonClick(e)}>
|
||||||
onClick={(e) => this.handleIndentSizeButtonClick(e)}
|
|
||||||
>
|
|
||||||
size: {config.editor.indentSize}
|
size: {config.editor.indentSize}
|
||||||
<i className='fa fa-caret-down' />
|
<i className='fa fa-caret-down' />
|
||||||
</button>
|
</button>
|
||||||
|
<button onClick={e => this.handleWrapLineButtonClick(e)}>
|
||||||
|
Wrap Line: {config.editor.lineWrapping ? 'on' : 'off'}
|
||||||
|
<i className='fa fa-caret-down' />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatusBar
|
<StatusBar
|
||||||
@@ -912,9 +1087,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
SnippetNoteDetail.propTypes = {
|
SnippetNoteDetail.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
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -156,78 +156,35 @@ body[data-theme="dark"]
|
|||||||
.control-fullScreenButton
|
.control-fullScreenButton
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
.root
|
.root
|
||||||
border-left 1px solid $ui-solarized-dark-borderColor
|
border-left 1px solid get-theme-var(theme, 'borderColor')
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
|
|
||||||
.body
|
.body
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
|
|
||||||
.body .description textarea
|
.body .description textarea
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
border 1px solid $ui-solarized-dark-borderColor
|
border 1px solid get-theme-var(theme, 'borderColor')
|
||||||
|
|
||||||
.tabList .tabButton
|
.tabList .tabButton
|
||||||
border-color $ui-solarized-dark-borderColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
|
|
||||||
.tabButton
|
.tabButton
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-solarized-dark-button--active-color
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
|
|
||||||
.tabList
|
.tabList
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
for theme in 'solarized-dark' 'dracula'
|
||||||
.root
|
apply-theme(theme)
|
||||||
border-left 1px solid $ui-monokai-borderColor
|
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.body
|
for theme in $themes
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
apply-theme(theme)
|
||||||
|
|
||||||
.body .description textarea
|
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
border 1px solid $ui-monokai-borderColor
|
|
||||||
|
|
||||||
.tabList .tabButton
|
|
||||||
border-color $ui-monokai-borderColor
|
|
||||||
|
|
||||||
.tabButton
|
|
||||||
&:hover
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.tabList
|
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
|
||||||
.root
|
|
||||||
border-left 1px solid $ui-dracula-borderColor
|
|
||||||
background-color $ui-dracula-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.body
|
|
||||||
background-color $ui-dracula-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.body .description textarea
|
|
||||||
background-color $ui-dracula-noteDetail-backgroundColor
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
border 1px solid $ui-dracula-borderColor
|
|
||||||
|
|
||||||
.tabList .tabButton
|
|
||||||
border-color $ui-dracula-borderColor
|
|
||||||
|
|
||||||
.tabButton
|
|
||||||
&:hover
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
background-color $ui-dracula-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.tabList
|
|
||||||
background-color $ui-dracula-noteDetail-backgroundColor
|
|
||||||
color $ui-dracula-text-color
|
|
||||||
@@ -36,25 +36,29 @@ class StarButton extends React.Component {
|
|||||||
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
|
||||||
|
styleName='icon'
|
||||||
|
src={
|
||||||
|
this.state.isActive || this.props.isActive
|
||||||
? '../resources/icon/icon-starred.svg'
|
? '../resources/icon/icon-starred.svg'
|
||||||
: '../resources/icon/icon-star.svg'
|
: '../resources/icon/icon-star.svg'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<span styleName='tooltip'>{i18n.__('Star')}</span>
|
<span lang={i18n.locale} styleName='tooltip'>
|
||||||
|
{i18n.__('Star')}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,11 @@
|
|||||||
opacity 0
|
opacity 0
|
||||||
transition 0.1s
|
transition 0.1s
|
||||||
|
|
||||||
|
.tooltip:lang(ja)
|
||||||
|
@extend .tooltip
|
||||||
|
right 103px
|
||||||
|
width 70px
|
||||||
|
|
||||||
.root--active
|
.root--active
|
||||||
@extend .root
|
@extend .root
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import invertColor from 'invert-color'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './TagSelect.styl'
|
import styles from './TagSelect.styl'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
@@ -7,6 +8,7 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import Autosuggest from 'react-autosuggest'
|
import Autosuggest from 'react-autosuggest'
|
||||||
|
import { push } from 'connected-react-router'
|
||||||
|
|
||||||
class TagSelect extends React.Component {
|
class TagSelect extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -18,11 +20,16 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.handleAddTag = this.handleAddTag.bind(this)
|
this.handleAddTag = this.handleAddTag.bind(this)
|
||||||
|
this.handleRenameTag = this.handleRenameTag.bind(this)
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,9 +49,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)
|
||||||
@@ -54,24 +59,28 @@ class TagSelect extends React.Component {
|
|||||||
value = _.sortBy(value)
|
value = _.sortBy(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
|
{
|
||||||
newTag: ''
|
newTag: ''
|
||||||
}, () => {
|
},
|
||||||
|
() => {
|
||||||
this.value = value
|
this.value = value
|
||||||
this.props.onChange()
|
this.props.onChange()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildSuggestions() {
|
buildSuggestions() {
|
||||||
this.suggestions = _.sortBy(this.props.data.tagNoteMap.map(
|
this.suggestions = _.sortBy(
|
||||||
(tag, name) => ({
|
this.props.data.tagNoteMap
|
||||||
|
.map((tag, name) => ({
|
||||||
name,
|
name,
|
||||||
nameLC: name.toLowerCase(),
|
nameLC: name.toLowerCase(),
|
||||||
size: tag.size
|
size: tag.size
|
||||||
})
|
}))
|
||||||
).filter(
|
.filter(tag => tag.size > 0),
|
||||||
tag => tag.size > 0
|
['name']
|
||||||
), ['name'])
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -80,6 +89,7 @@ class TagSelect extends React.Component {
|
|||||||
this.buildSuggestions()
|
this.buildSuggestions()
|
||||||
|
|
||||||
ee.on('editor:add-tag', this.handleAddTag)
|
ee.on('editor:add-tag', this.handleAddTag)
|
||||||
|
ee.on('sidebar:rename-tag', this.handleRenameTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
@@ -88,15 +98,29 @@ class TagSelect extends React.Component {
|
|||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ee.off('editor:add-tag', this.handleAddTag)
|
ee.off('editor:add-tag', this.handleAddTag)
|
||||||
|
ee.off('sidebar:rename-tag', this.handleRenameTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddTag() {
|
handleAddTag() {
|
||||||
this.refs.newTag.input.focus()
|
this.refs.newTag.input.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleRenameTag(event, tagChange) {
|
||||||
|
const { value } = this.props
|
||||||
|
const { tag, updatedTag } = tagChange
|
||||||
|
const newTags = value.slice()
|
||||||
|
|
||||||
|
newTags[value.indexOf(tag)] = updatedTag
|
||||||
|
this.value = newTags
|
||||||
|
this.props.onChange()
|
||||||
|
}
|
||||||
|
|
||||||
handleTagLabelClick(tag) {
|
handleTagLabelClick(tag) {
|
||||||
const { router } = this.context
|
const { dispatch } = this.props
|
||||||
router.push(`/tags/${tag}`)
|
|
||||||
|
// Note: `tag` requires encoding later.
|
||||||
|
// E.g. % in tag is a problem (see issue #3170) - encodeURIComponent(tag) is not working.
|
||||||
|
dispatch(push(`/tags/${tag}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTagRemoveButtonClick(tag) {
|
handleTagRemoveButtonClick(tag) {
|
||||||
@@ -141,7 +165,8 @@ class TagSelect extends React.Component {
|
|||||||
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,7 +179,7 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeLastTag() {
|
removeLastTag() {
|
||||||
this.removeTagByCallback((value) => {
|
this.removeTagByCallback(value => {
|
||||||
value.pop()
|
value.pop()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -162,9 +187,7 @@ class TagSelect extends React.Component {
|
|||||||
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)
|
||||||
|
|
||||||
@@ -185,19 +208,43 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { value, className, showTagsAlphabetically } = 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 textStyle = {}
|
||||||
|
const BLACK = '#333333'
|
||||||
|
const WHITE = '#f1f1f1'
|
||||||
|
const color = coloredTags[tag]
|
||||||
|
const invertedColor =
|
||||||
|
color && invertColor(color, { black: BLACK, white: WHITE })
|
||||||
|
let iconRemove = '../resources/icon/icon-x.svg'
|
||||||
|
if (color) {
|
||||||
|
wrapperStyle.backgroundColor = color
|
||||||
|
textStyle.color = invertedColor
|
||||||
|
}
|
||||||
|
if (invertedColor === WHITE) {
|
||||||
|
iconRemove = '../resources/icon/icon-x-light.svg'
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<span styleName='tag'
|
<span styleName='tag' key={tag} style={wrapperStyle}>
|
||||||
key={tag}
|
<span
|
||||||
|
styleName='tag-label'
|
||||||
|
style={textStyle}
|
||||||
|
onClick={e => this.handleTagLabelClick(tag)}
|
||||||
>
|
>
|
||||||
<span styleName='tag-label' onClick={(e) => this.handleTagLabelClick(tag)}>#{tag}</span>
|
#{tag}
|
||||||
<button styleName='tag-removeButton'
|
</span>
|
||||||
onClick={(e) => this.handleTagRemoveButtonClick(tag)}
|
<button
|
||||||
|
styleName='tag-removeButton'
|
||||||
|
onClick={e => this.handleTagRemoveButtonClick(tag)}
|
||||||
>
|
>
|
||||||
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
|
<img
|
||||||
|
className='tag-removeButton-icon'
|
||||||
|
src={iconRemove}
|
||||||
|
width='8px'
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
@@ -207,9 +254,9 @@ class TagSelect extends React.Component {
|
|||||||
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'
|
||||||
>
|
>
|
||||||
@@ -221,11 +268,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,
|
||||||
@@ -239,14 +282,12 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TagSelect.contextTypes = {
|
|
||||||
router: PropTypes.shape({})
|
|
||||||
}
|
|
||||||
|
|
||||||
TagSelect.propTypes = {
|
TagSelect.propTypes = {
|
||||||
|
dispatch: PropTypes.func,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
value: PropTypes.arrayOf(PropTypes.string),
|
value: PropTypes.arrayOf(PropTypes.string),
|
||||||
onChange: PropTypes.func
|
onChange: PropTypes.func,
|
||||||
|
coloredTags: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(TagSelect, styles)
|
export default CSSModules(TagSelect, styles)
|
||||||
|
|||||||
@@ -3,19 +3,18 @@
|
|||||||
align-items center
|
align-items center
|
||||||
user-select none
|
user-select none
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
width 100%
|
width 96%
|
||||||
overflow-x scroll
|
overflow-x auto
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
margin-top 31px
|
top 50px
|
||||||
position absolute
|
position absolute
|
||||||
|
&::-webkit-scrollbar
|
||||||
.root::-webkit-scrollbar
|
height 8px
|
||||||
display none
|
|
||||||
|
|
||||||
.tag
|
.tag
|
||||||
display flex
|
display flex
|
||||||
align-items center
|
align-items center
|
||||||
margin 0px 2px
|
margin 0px 2px 2px
|
||||||
padding 2px 4px
|
padding 2px 4px
|
||||||
background-color alpha($ui-tag-backgroundColor, 3%)
|
background-color alpha($ui-tag-backgroundColor, 3%)
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
@@ -55,35 +54,20 @@ body[data-theme="dark"]
|
|||||||
.tag-label
|
.tag-label
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
.tag
|
.tag
|
||||||
background-color $ui-solarized-dark-tag-backgroundColor
|
background-color get-theme-var(theme, 'tag-backgroundColor')
|
||||||
|
|
||||||
.tag-removeButton
|
.tag-removeButton
|
||||||
border-color $ui-button--focus-borderColor
|
border-color $ui-button--focus-borderColor
|
||||||
background-color transparent
|
background-color transparent
|
||||||
|
|
||||||
.tag-label
|
.tag-label
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
for theme in 'solarized-dark' 'dracula'
|
||||||
.tag
|
apply-theme(theme)
|
||||||
background-color $ui-monokai-tag-backgroundColor
|
|
||||||
|
|
||||||
.tag-removeButton
|
for theme in $themes
|
||||||
border-color $ui-button--focus-borderColor
|
apply-theme(theme)
|
||||||
background-color transparent
|
|
||||||
|
|
||||||
.tag-label
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
|
||||||
.tag
|
|
||||||
background-color $ui-dracula-tag-backgroundColor
|
|
||||||
|
|
||||||
.tag-removeButton
|
|
||||||
border-color $ui-dracula-button--focus-borderColor
|
|
||||||
background-color transparent
|
|
||||||
|
|
||||||
.tag-label
|
|
||||||
color $ui-dracula-borderColor
|
|
||||||
26
browser/main/Detail/ToggleDirectionButton.js
Normal file
26
browser/main/Detail/ToggleDirectionButton.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './ToggleDirectionButton.styl'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
|
const ToggleDirectionButton = ({ onClick, isRTL }) => (
|
||||||
|
<div styleName='control-toggleModeButton'>
|
||||||
|
<div styleName={isRTL ? 'active' : undefined} onClick={() => onClick()}>
|
||||||
|
<img src={!isRTL ? '../resources/icon/icon-left-to-right.svg' : ''} />
|
||||||
|
</div>
|
||||||
|
<div styleName={!isRTL ? 'active' : undefined} onClick={() => onClick()}>
|
||||||
|
<img src={!isRTL ? '' : '../resources/icon/icon-right-to-left.svg'} />
|
||||||
|
</div>
|
||||||
|
<span lang={i18n.locale} styleName='tooltip'>
|
||||||
|
{i18n.__('Toggle Direction')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
ToggleDirectionButton.propTypes = {
|
||||||
|
onClick: PropTypes.func.isRequired,
|
||||||
|
isRTL: PropTypes.bool.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(ToggleDirectionButton, styles)
|
||||||
85
browser/main/Detail/ToggleDirectionButton.styl
Normal file
85
browser/main/Detail/ToggleDirectionButton.styl
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
.control-toggleModeButton
|
||||||
|
height 25px
|
||||||
|
border-radius 50px
|
||||||
|
background-color #F4F4F4
|
||||||
|
width 52px
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
position: relative
|
||||||
|
top 2px
|
||||||
|
margin-left 5px
|
||||||
|
.active
|
||||||
|
background-color #1EC38B
|
||||||
|
width 33px
|
||||||
|
height 24px
|
||||||
|
box-shadow 2px 0px 7px #eee
|
||||||
|
z-index 1
|
||||||
|
|
||||||
|
div
|
||||||
|
width 40px
|
||||||
|
height 100%
|
||||||
|
border-radius 50%
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
justify-content center
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
position absolute
|
||||||
|
pointer-events none
|
||||||
|
top 33px
|
||||||
|
left -10px
|
||||||
|
z-index 200
|
||||||
|
width 80px
|
||||||
|
padding 5px
|
||||||
|
line-height normal
|
||||||
|
border-radius 2px
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
|
||||||
|
.tooltip:lang(ja)
|
||||||
|
@extend .tooltip
|
||||||
|
left -8px
|
||||||
|
width 70px
|
||||||
|
|
||||||
|
.control-toggleModeButton
|
||||||
|
-webkit-user-drag none
|
||||||
|
user-select none
|
||||||
|
> div img
|
||||||
|
-webkit-user-drag none
|
||||||
|
user-select none
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.control-fullScreenButton
|
||||||
|
topBarButtonDark()
|
||||||
|
|
||||||
|
.control-toggleModeButton
|
||||||
|
background-color #3A404C
|
||||||
|
.active
|
||||||
|
background-color #1EC38B
|
||||||
|
box-shadow 2px 0px 7px #444444
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.control-toggleModeButton
|
||||||
|
background-color #002B36
|
||||||
|
.active
|
||||||
|
background-color #1EC38B
|
||||||
|
box-shadow 2px 0px 7px #222222
|
||||||
|
|
||||||
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
|
.control-toggleModeButton
|
||||||
|
background-color get-theme-var(theme, 'borderColor')
|
||||||
|
.active
|
||||||
|
background-color get-theme-var(theme, 'active-color')
|
||||||
|
box-shadow 2px 0px 7px #222222
|
||||||
|
|
||||||
|
for theme in 'dracula'
|
||||||
|
apply-theme(theme)
|
||||||
|
|
||||||
|
for theme in $themes
|
||||||
|
apply-theme(theme)
|
||||||
@@ -4,23 +4,41 @@ 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' : 'non-active'} onClick={() => onClick('SPLIT')}>
|
<div
|
||||||
<img styleName='item-star' 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' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
|
<div
|
||||||
<img styleName='item-star' 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 styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
|
<span lang={i18n.locale} styleName='tooltip'>
|
||||||
|
{i18n.__('Toggle Mode')}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
ToggleModeButton.propTypes = {
|
ToggleModeButton.propTypes = {
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
editorType: PropTypes.string.Required
|
editorType: PropTypes.string.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(ToggleModeButton, styles)
|
export default CSSModules(ToggleModeButton, styles)
|
||||||
|
|||||||
@@ -26,6 +26,13 @@
|
|||||||
&:hover .tooltip
|
&:hover .tooltip
|
||||||
opacity 1
|
opacity 1
|
||||||
|
|
||||||
|
.control-toggleModeButton
|
||||||
|
-webkit-user-drag none
|
||||||
|
user-select none
|
||||||
|
> div img
|
||||||
|
-webkit-user-drag none
|
||||||
|
user-select none
|
||||||
|
|
||||||
.tooltip
|
.tooltip
|
||||||
tooltip()
|
tooltip()
|
||||||
position absolute
|
position absolute
|
||||||
@@ -40,6 +47,11 @@
|
|||||||
opacity 0
|
opacity 0
|
||||||
transition 0.1s
|
transition 0.1s
|
||||||
|
|
||||||
|
.tooltip:lang(ja)
|
||||||
|
@extend .tooltip
|
||||||
|
left -8px
|
||||||
|
width 70px
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.control-fullScreenButton
|
.control-fullScreenButton
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
@@ -57,16 +69,16 @@ body[data-theme="solarized-dark"]
|
|||||||
background-color #1EC38B
|
background-color #1EC38B
|
||||||
box-shadow 2px 0px 7px #222222
|
box-shadow 2px 0px 7px #222222
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
.control-toggleModeButton
|
.control-toggleModeButton
|
||||||
background-color #373831
|
background-color get-theme-var(theme, 'borderColor')
|
||||||
.active
|
.active
|
||||||
background-color #f92672
|
background-color get-theme-var(theme, 'active-color')
|
||||||
box-shadow 2px 0px 7px #222222
|
box-shadow 2px 0px 7px #222222
|
||||||
|
|
||||||
body[data-theme="dracula"]
|
for theme in 'dracula'
|
||||||
.control-toggleModeButton
|
apply-theme(theme)
|
||||||
background-color #44475a
|
|
||||||
.active
|
for theme in $themes
|
||||||
background-color #bd93f9
|
apply-theme(theme)
|
||||||
box-shadow 2px 0px 7px #222222
|
|
||||||
|
|||||||
@@ -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)}>
|
||||||
}) => (
|
<img src='../resources/icon/icon-trash.svg' />
|
||||||
<button styleName='control-trashButton'
|
<span lang={i18n.locale} styleName='tooltip'>
|
||||||
onClick={(e) => onClick(e)}
|
{i18n.__('Trash')}
|
||||||
>
|
</span>
|
||||||
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
|
||||||
<span styleName='tooltip'>{i18n.__('Trash')}</span>
|
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,10 @@
|
|||||||
opacity 0
|
opacity 0
|
||||||
transition 0.1s
|
transition 0.1s
|
||||||
|
|
||||||
|
.tooltip:lang(ja)
|
||||||
|
@extend .tooltip
|
||||||
|
right 46px
|
||||||
|
|
||||||
.control-trashButton--in-trash
|
.control-trashButton--in-trash
|
||||||
top 60px
|
top 60px
|
||||||
topBarButtonRight()
|
topBarButtonRight()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user