mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 17:56:25 +00:00
Compare commits
382 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
789926bc76 | ||
|
|
006795b4d0 | ||
|
|
3e5d78d322 | ||
|
|
58c4a78be1 | ||
|
|
2603dfc1ed | ||
|
|
2df590600b | ||
|
|
ef20a8f3e5 | ||
|
|
3e405e1abf | ||
|
|
553832bdfa | ||
|
|
18d65d999a | ||
|
|
3b5eff582a | ||
|
|
85d09b3b3d | ||
|
|
8958e67fcf | ||
|
|
47b796909a | ||
|
|
67d76abdfa | ||
|
|
d75d68ba72 | ||
|
|
323be6b72d | ||
|
|
031a113338 | ||
|
|
b50c5386a6 | ||
|
|
65777b1d56 | ||
|
|
fe728874ac | ||
|
|
c5b4c327fa | ||
|
|
4c39922ead | ||
|
|
1cdc74a2f0 | ||
|
|
6213a820e6 | ||
|
|
ce81b26d1d | ||
|
|
fae91255f9 | ||
|
|
a82a3efb14 | ||
|
|
9556417447 | ||
|
|
60fbb7db5d | ||
|
|
e504f8e63e | ||
|
|
071ce12a7e | ||
|
|
0decaf187c | ||
|
|
961644747e | ||
|
|
d1a81984fb | ||
|
|
bd9b1306b1 | ||
|
|
0ca18d8ca5 | ||
|
|
540d72696c | ||
|
|
87a530612f | ||
|
|
7f3fdedb5d | ||
|
|
3a80706938 | ||
|
|
f4259bb4d0 | ||
|
|
aa8b589569 | ||
|
|
febc98c101 | ||
|
|
b678c3bd89 | ||
|
|
80b8948433 | ||
|
|
5414fe3384 | ||
|
|
db4016385d | ||
|
|
2cf5f8e966 | ||
|
|
8ede1a4989 | ||
|
|
76da76ae76 | ||
|
|
c02ab033f4 | ||
|
|
1aaba74e24 | ||
|
|
6fe6794796 | ||
|
|
fd3e243855 | ||
|
|
938b075bf6 | ||
|
|
81ac3d1748 | ||
|
|
40d10eae04 | ||
|
|
9b6a61a91c | ||
|
|
7116c305ca | ||
|
|
4fbbb4651d | ||
|
|
2ac38e9644 | ||
|
|
98d4fa0603 | ||
|
|
2ea0514bbe | ||
|
|
137aa692bc | ||
|
|
634fec39c0 | ||
|
|
d269f1e8fd | ||
|
|
4b67026bbf | ||
|
|
8b13ec4f0e | ||
|
|
7fef7660e4 | ||
|
|
01d021cc4c | ||
|
|
c355f81525 | ||
|
|
d138a54dfd | ||
|
|
514d4b9059 | ||
|
|
a7ead67c2d | ||
|
|
2f16784a20 | ||
|
|
8ca3ba21ee | ||
|
|
58ae6419f0 | ||
|
|
b56e0b98e3 | ||
|
|
4def32ab13 | ||
|
|
0de78d12ef | ||
|
|
e756534db4 | ||
|
|
2194965dc4 | ||
|
|
f9e54bcbfc | ||
|
|
667fd3a601 | ||
|
|
461e24bf39 | ||
|
|
ac2cfe5169 | ||
|
|
3f320f4337 | ||
|
|
433ee9ed45 | ||
|
|
6ee92588b1 | ||
|
|
0d797ce8a8 | ||
|
|
4915c545d9 | ||
|
|
e1c95fb1f2 | ||
|
|
5f56d3e0de | ||
|
|
d6b86b902c | ||
|
|
3abc0fec38 | ||
|
|
c0619eb746 | ||
|
|
791ababe1e | ||
|
|
d829216c8d | ||
|
|
1cf6f3b1e2 | ||
|
|
4d5939aaf4 | ||
|
|
2695f62f3e | ||
|
|
ccd0355d0b | ||
|
|
6d6e3a51c0 | ||
|
|
48c8164689 | ||
|
|
38ed5b8541 | ||
|
|
8a6df8bf95 | ||
|
|
050b1563df | ||
|
|
dbbcf385b1 | ||
|
|
d95a3af667 | ||
|
|
87a737babc | ||
|
|
a27ddd7490 | ||
|
|
5693b6d0f5 | ||
|
|
9debe8218d | ||
|
|
9549355ab7 | ||
|
|
88e8d2e009 | ||
|
|
a2ea5dd12e | ||
|
|
9f932a0911 | ||
|
|
71f05b9886 | ||
|
|
2b4e2638dc | ||
|
|
9c3f34fe04 | ||
|
|
d4123eeccd | ||
|
|
d727a6110a | ||
|
|
c42635579c | ||
|
|
fd54a7b85c | ||
|
|
76de78a72e | ||
|
|
5edce1fe6a | ||
|
|
afbe43965e | ||
|
|
d706a5375c | ||
|
|
feb2a878a9 | ||
|
|
e55f1e0308 | ||
|
|
8b2ed8585f | ||
|
|
1d570df129 | ||
|
|
d125bd07f7 | ||
|
|
997ffa620d | ||
|
|
d475146d80 | ||
|
|
1fe59caa19 | ||
|
|
a496a84cb8 | ||
|
|
eae964f7e7 | ||
|
|
e44381f295 | ||
|
|
2c0e0a6e39 | ||
|
|
216f588aa4 | ||
|
|
592aca1539 | ||
|
|
725bf2a691 | ||
|
|
051ce9e208 | ||
|
|
f367e9f08c | ||
|
|
f72fdfe33f | ||
|
|
000a54f5ed | ||
|
|
57a5de97f8 | ||
|
|
262d173c65 | ||
|
|
e558fae4b0 | ||
|
|
a90d801d08 | ||
|
|
636996356f | ||
|
|
8a87c06b97 | ||
|
|
69831571a5 | ||
|
|
24a5c839a7 | ||
|
|
93e09f11dd | ||
|
|
c570fc9873 | ||
|
|
f1d03acbad | ||
|
|
31ffbd98b6 | ||
|
|
87b9766bc0 | ||
|
|
49c9bcac9a | ||
|
|
5105babd14 | ||
|
|
0a361f5f41 | ||
|
|
8218d5eb5a | ||
|
|
7fe6925615 | ||
|
|
1dd71fc923 | ||
|
|
301f03dadd | ||
|
|
53c48d86b7 | ||
|
|
d760259ce6 | ||
|
|
1195c77f7a | ||
|
|
c373c207c0 | ||
|
|
65e83e7017 | ||
|
|
0722c2505a | ||
|
|
3c12e0d119 | ||
|
|
60d6c68e48 | ||
|
|
d8605965a8 | ||
|
|
6d455fc286 | ||
|
|
2882667e94 | ||
|
|
7fa578880e | ||
|
|
3f465df1cd | ||
|
|
c423784cc5 | ||
|
|
ce853a7e3a | ||
|
|
099ebad06b | ||
|
|
75a1347ae1 | ||
|
|
f5779558bb | ||
|
|
b83d3e5c33 | ||
|
|
1a7c719a4e | ||
|
|
b91a76b3b1 | ||
|
|
d78f6b7aba | ||
|
|
7d4d176bf4 | ||
|
|
52ea44ceaa | ||
|
|
132d04326b | ||
|
|
9996b5d686 | ||
|
|
e9d9f49ff3 | ||
|
|
95300546dc | ||
|
|
489fc6578b | ||
|
|
edebba6680 | ||
|
|
72c2a20a74 | ||
|
|
abef6c5fee | ||
|
|
662ae73637 | ||
|
|
3ef485548a | ||
|
|
a90c10ef3e | ||
|
|
d43fe8db75 | ||
|
|
1d84cac922 | ||
|
|
5280b6ed63 | ||
|
|
77833ff980 | ||
|
|
45e75cdfe9 | ||
|
|
2cb4cbe1b6 | ||
|
|
b22b09a93d | ||
|
|
e34485eb83 | ||
|
|
d010c5532d | ||
|
|
f2dc8b8020 | ||
|
|
1798353eac | ||
|
|
772a8b2383 | ||
|
|
fc08d2f8c3 | ||
|
|
5690c8361a | ||
|
|
6d09cf227c | ||
|
|
8736666e91 | ||
|
|
d1fd5cfb45 | ||
|
|
3eabf95fb3 | ||
|
|
8ea920ef91 | ||
|
|
3c0f20f364 | ||
|
|
59f8425c97 | ||
|
|
f181a7e459 | ||
|
|
6b1c595f87 | ||
|
|
0003de8f08 | ||
|
|
5357d8dc04 | ||
|
|
d069722bf9 | ||
|
|
3f4dd49a8f | ||
|
|
be06b3f7e8 | ||
|
|
5044bdda00 | ||
|
|
fbeffb0b5d | ||
|
|
ef0af39aa7 | ||
|
|
0697bc0a74 | ||
|
|
59e361cb37 | ||
|
|
1993a6588d | ||
|
|
218fba1aa1 | ||
|
|
4de6c69f5d | ||
|
|
43d8ebb3c4 | ||
|
|
68175cd71b | ||
|
|
2b3538d3b1 | ||
|
|
b84f1173b7 | ||
|
|
bdfe8c0445 | ||
|
|
f4d87f64ae | ||
|
|
68b3077651 | ||
|
|
1332b337f3 | ||
|
|
e9975d1ea5 | ||
|
|
2c103aca3d | ||
|
|
c0a5eb0d2b | ||
|
|
ff7c4495f0 | ||
|
|
35fe639cd2 | ||
|
|
59add8982e | ||
|
|
8d9c514097 | ||
|
|
6f880d0f02 | ||
|
|
ec47ee8110 | ||
|
|
f64d0b35e1 | ||
|
|
28b8141c6b | ||
|
|
5b0b309c49 | ||
|
|
0b84a372f6 | ||
|
|
8355e1e006 | ||
|
|
c7d33fbd83 | ||
|
|
cf324d93fe | ||
|
|
9a704a2bcb | ||
|
|
1e00651541 | ||
|
|
857e75594d | ||
|
|
2f1dadfc3e | ||
|
|
7d0404657e | ||
|
|
b9dd651fc1 | ||
|
|
25ef456af2 | ||
|
|
084decaa85 | ||
|
|
330a444fc5 | ||
|
|
a47dac2854 | ||
|
|
08070f3e2d | ||
|
|
2352c78cb6 | ||
|
|
6ef9c3865f | ||
|
|
ff9789b5a7 | ||
|
|
f09297f406 | ||
|
|
3921655157 | ||
|
|
e4e10d523f | ||
|
|
404dddcb86 | ||
|
|
ffb2603485 | ||
|
|
2d3c69d178 | ||
|
|
b837653cf1 | ||
|
|
eeca031c86 | ||
|
|
918a8627e9 | ||
|
|
86370edd1e | ||
|
|
1173631255 | ||
|
|
911fd9a004 | ||
|
|
0ad3da5bbc | ||
|
|
89ae2a9516 | ||
|
|
70892cae05 | ||
|
|
de0af153bc | ||
|
|
33161e46e6 | ||
|
|
7e3c662374 | ||
|
|
a39e9c2da6 | ||
|
|
928e0edf4d | ||
|
|
72b8d56245 | ||
|
|
0d36f59036 | ||
|
|
a3f7d2287a | ||
|
|
8edfbd28ed | ||
|
|
606be4304d | ||
|
|
c2a26a8547 | ||
|
|
addf9b920f | ||
|
|
80a63f7404 | ||
|
|
aeb77e5a40 | ||
|
|
1d59d89588 | ||
|
|
bde357f952 | ||
|
|
558c091205 | ||
|
|
f67175e628 | ||
|
|
390f6d545f | ||
|
|
44efb0178c | ||
|
|
37eee26bdf | ||
|
|
6e45ee6a38 | ||
|
|
ed4a670f0a | ||
|
|
fbb9afe34f | ||
|
|
020bc11bd7 | ||
|
|
ae0837e29b | ||
|
|
f0380ef733 | ||
|
|
25bdaf9f00 | ||
|
|
ef1809305c | ||
|
|
ba34458feb | ||
|
|
a2fb50a71c | ||
|
|
b15a4007ee | ||
|
|
090b5c32f0 | ||
|
|
2e380ceb02 | ||
|
|
f26dea2420 | ||
|
|
c5484fbb88 | ||
|
|
0d4b6252e8 | ||
|
|
7529feb4a5 | ||
|
|
5f96e314fd | ||
|
|
a7f802db7c | ||
|
|
db78f1b91e | ||
|
|
04e0523cac | ||
|
|
885b9d2c26 | ||
|
|
51aff20d65 | ||
|
|
8dc5214c9e | ||
|
|
93f0d3c1cf | ||
|
|
8ec7d19f30 | ||
|
|
a0f5a06c73 | ||
|
|
39a98e795f | ||
|
|
57705cf41b | ||
|
|
052c70bb38 | ||
|
|
6dc88262c9 | ||
|
|
1cdac943ba | ||
|
|
9d43e34cfa | ||
|
|
12f9b9342d | ||
|
|
e76bc72667 | ||
|
|
9310e5e86c | ||
|
|
a58b6f1b49 | ||
|
|
fa157f6f76 | ||
|
|
d6a54b8a26 | ||
|
|
9813412c8e | ||
|
|
d76b7235db | ||
|
|
418a789568 | ||
|
|
a19ff6762e | ||
|
|
2d941c3ea3 | ||
|
|
7034e7b620 | ||
|
|
cd53a65c14 | ||
|
|
8b54f5aa69 | ||
|
|
ceed178061 | ||
|
|
33662974bf | ||
|
|
36b97fc6a2 | ||
|
|
540c608cc6 | ||
|
|
0f8c627474 | ||
|
|
f38fef23a0 | ||
|
|
8be0ea64a5 | ||
|
|
1419c71ef5 | ||
|
|
e13742445e | ||
|
|
1d21bb1ea3 | ||
|
|
aa38b1f859 | ||
|
|
e723d4cd59 | ||
|
|
9e770ef357 | ||
|
|
c796b3b30e | ||
|
|
168fe212f5 | ||
|
|
87515dbd3f | ||
|
|
b6b29e02f3 | ||
|
|
2fc37d54f2 | ||
|
|
696c2f29b5 | ||
|
|
18aae8cf7b | ||
|
|
4a9bc69ac2 | ||
|
|
d97e62f864 |
11
.eslintrc
11
.eslintrc
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": ["standard", "standard-jsx", "plugin:react/recommended"],
|
||||
"plugins": ["react"],
|
||||
"extends": ["standard", "standard-jsx", "plugin:react/recommended", "prettier"],
|
||||
"plugins": ["react", "prettier"],
|
||||
"rules": {
|
||||
"no-useless-escape": 0,
|
||||
"prefer-const": ["warn", {
|
||||
@@ -13,12 +13,15 @@
|
||||
"react/no-string-refs": 0,
|
||||
"react/no-find-dom-node": "warn",
|
||||
"react/no-render-return-value": "warn",
|
||||
"react/no-deprecated": "warn"
|
||||
"react/no-deprecated": "warn",
|
||||
"prettier/prettier": ["error"]
|
||||
},
|
||||
"globals": {
|
||||
"FileReader": true,
|
||||
"localStorage": true,
|
||||
"fetch": true
|
||||
"fetch": true,
|
||||
"Image": true,
|
||||
"MutationObserver": true
|
||||
},
|
||||
"env": {
|
||||
"jest": true
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -9,4 +9,6 @@ node_modules/*
|
||||
/secret
|
||||
*.log
|
||||
.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
|
||||
}
|
||||
@@ -3,7 +3,6 @@ node_js:
|
||||
- 8
|
||||
script:
|
||||
- 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'
|
||||
after_success:
|
||||
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
|
||||
|
||||
@@ -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.
|
||||
|
||||
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
|
||||
|
||||
<!--
|
||||
Let us know what you think should happen!
|
||||
Let us know what you think should happen.
|
||||
-->
|
||||
|
||||
# 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.
|
||||
@@ -26,8 +26,8 @@ Please be thorough, issues we can reproduce are easier to fix!
|
||||
|
||||
# Environment
|
||||
|
||||
- Version :
|
||||
- OS Version and name :
|
||||
- Boostnote version: <!-- 0.x.x -->
|
||||
- OS version and name: <!-- Windows 10 / Ubuntu 18.04 / etc -->
|
||||
|
||||
<!--
|
||||
Love Boostnote? Please consider supporting us on IssueHunt:
|
||||
|
||||
@@ -3,13 +3,16 @@ Before submitting this PR, please make sure that:
|
||||
- You have read and understand the contributing.md
|
||||
- You have checked docs/code_style.md for information on code style
|
||||
-->
|
||||
|
||||
## Description
|
||||
|
||||
<!--
|
||||
Tell us what your PR does.
|
||||
Please attach a screenshot/ video/gif image describing your PR if possible.
|
||||
-->
|
||||
|
||||
## Issue fixed
|
||||
|
||||
<!--
|
||||
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
|
||||
-->
|
||||
|
||||
## Type of changes
|
||||
|
||||
- :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: All existing tests have been passed
|
||||
- :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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ import styles from './ColorPicker.styl'
|
||||
const componentHeight = 330
|
||||
|
||||
class ColorPicker extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -18,21 +18,21 @@ class ColorPicker extends React.Component {
|
||||
this.handleConfirm = this.handleConfirm.bind(this)
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.onColorChange(nextProps.color)
|
||||
}
|
||||
|
||||
onColorChange (color) {
|
||||
onColorChange(color) {
|
||||
this.setState({
|
||||
color
|
||||
})
|
||||
}
|
||||
|
||||
handleConfirm () {
|
||||
handleConfirm() {
|
||||
this.props.onConfirm(this.state.color)
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { onReset, onCancel, targetRect } = this.props
|
||||
const { color } = this.state
|
||||
|
||||
@@ -44,13 +44,22 @@ class ColorPicker extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div styleName='colorPicker' style={{top: `${alignY}px`, left: `${alignX}px`}}>
|
||||
<div
|
||||
styleName='colorPicker'
|
||||
style={{ top: `${alignY}px`, left: `${alignX}px` }}
|
||||
>
|
||||
<div styleName='cover' onClick={onCancel} />
|
||||
<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>
|
||||
<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>
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable camelcase */
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
@@ -10,7 +11,7 @@ import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
||||
|
||||
class MarkdownEditor extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
// char codes for ctrl + w
|
||||
@@ -20,142 +21,171 @@ class MarkdownEditor extends React.Component {
|
||||
this.supportMdSelectionBold = [16, 17, 186]
|
||||
|
||||
this.state = {
|
||||
status: props.config.editor.switchPreview === 'RIGHTCLICK' ? props.config.editor.delfaultStatus : 'CODE',
|
||||
status:
|
||||
props.config.editor.switchPreview === 'RIGHTCLICK'
|
||||
? props.config.editor.delfaultStatus
|
||||
: 'CODE',
|
||||
renderValue: props.value,
|
||||
keyPressed: new Set(),
|
||||
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
|
||||
eventEmitter.on('editor:lock', this.lockEditorCode)
|
||||
eventEmitter.on('editor:focus', this.focusEditor.bind(this))
|
||||
eventEmitter.on('editor:focus', this.focusEditor)
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
componentDidUpdate() {
|
||||
this.value = this.refs.code.value
|
||||
}
|
||||
|
||||
componentWillReceiveProps (props) {
|
||||
UNSAFE_componentWillReceiveProps(props) {
|
||||
if (props.value !== this.props.value) {
|
||||
this.queueRendering(props.value)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
this.cancelQueue()
|
||||
eventEmitter.off('editor:lock', this.lockEditorCode)
|
||||
eventEmitter.off('editor:focus', this.focusEditor.bind(this))
|
||||
eventEmitter.off('editor:focus', this.focusEditor)
|
||||
}
|
||||
|
||||
focusEditor () {
|
||||
this.setState({
|
||||
status: 'CODE'
|
||||
}, () => {
|
||||
this.refs.code.focus()
|
||||
})
|
||||
focusEditor() {
|
||||
this.setState(
|
||||
{
|
||||
status: 'CODE'
|
||||
},
|
||||
() => {
|
||||
if (this.refs.code == null) {
|
||||
return
|
||||
}
|
||||
this.refs.code.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
queueRendering (value) {
|
||||
queueRendering(value) {
|
||||
clearTimeout(this.renderTimer)
|
||||
this.renderTimer = setTimeout(() => {
|
||||
this.renderPreview(value)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
cancelQueue () {
|
||||
cancelQueue() {
|
||||
clearTimeout(this.renderTimer)
|
||||
}
|
||||
|
||||
renderPreview (value) {
|
||||
renderPreview(value) {
|
||||
this.setState({
|
||||
renderValue: value
|
||||
})
|
||||
}
|
||||
|
||||
setValue (value) {
|
||||
setValue(value) {
|
||||
this.refs.code.setValue(value)
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
handleChange(e) {
|
||||
this.value = this.refs.code.value
|
||||
this.props.onChange(e)
|
||||
}
|
||||
|
||||
handleContextMenu (e) {
|
||||
handleContextMenu(e) {
|
||||
if (this.state.isLocked) return
|
||||
const { config } = this.props
|
||||
if (config.editor.switchPreview === 'RIGHTCLICK') {
|
||||
const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW'
|
||||
this.setState({
|
||||
status: newStatus
|
||||
}, () => {
|
||||
if (newStatus === 'CODE') {
|
||||
this.refs.code.focus()
|
||||
} else {
|
||||
this.refs.preview.focus()
|
||||
}
|
||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||
this.setState(
|
||||
{
|
||||
status: newStatus
|
||||
},
|
||||
() => {
|
||||
if (newStatus === 'CODE') {
|
||||
this.refs.code.focus()
|
||||
} else {
|
||||
this.previewRef.current.focus()
|
||||
}
|
||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||
|
||||
const newConfig = Object.assign({}, config)
|
||||
newConfig.editor.delfaultStatus = newStatus
|
||||
ConfigManager.set(newConfig)
|
||||
})
|
||||
const newConfig = Object.assign({}, config)
|
||||
newConfig.editor.delfaultStatus = newStatus
|
||||
ConfigManager.set(newConfig)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
handleBlur (e) {
|
||||
handleBlur(e) {
|
||||
if (this.state.isLocked) return
|
||||
this.setState({ keyPressed: new Set() })
|
||||
const { config } = this.props
|
||||
if (config.editor.switchPreview === 'BLUR' ||
|
||||
(config.editor.switchPreview === 'DBL_CLICK' && this.state.status === 'CODE')
|
||||
if (
|
||||
config.editor.switchPreview === 'BLUR' ||
|
||||
(config.editor.switchPreview === 'DBL_CLICK' &&
|
||||
this.state.status === 'CODE')
|
||||
) {
|
||||
const cursorPosition = this.refs.code.editor.getCursor()
|
||||
this.setState({
|
||||
status: 'PREVIEW'
|
||||
}, () => {
|
||||
this.refs.preview.focus()
|
||||
this.refs.preview.scrollTo(cursorPosition.line)
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'PREVIEW'
|
||||
},
|
||||
() => {
|
||||
this.previewRef.current.focus()
|
||||
this.previewRef.current.scrollToLine(cursorPosition.line)
|
||||
}
|
||||
)
|
||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||
}
|
||||
}
|
||||
|
||||
handleDoubleClick (e) {
|
||||
handleDoubleClick(e) {
|
||||
if (this.state.isLocked) return
|
||||
this.setState({keyPressed: new Set()})
|
||||
this.setState({ keyPressed: new Set() })
|
||||
const { config } = this.props
|
||||
if (config.editor.switchPreview === 'DBL_CLICK') {
|
||||
this.setState({
|
||||
status: 'CODE'
|
||||
}, () => {
|
||||
this.refs.code.focus()
|
||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'CODE'
|
||||
},
|
||||
() => {
|
||||
this.refs.code.focus()
|
||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
handlePreviewMouseDown (e) {
|
||||
handlePreviewMouseDown(e) {
|
||||
this.previewMouseDownedAt = new Date()
|
||||
}
|
||||
|
||||
handlePreviewMouseUp (e) {
|
||||
handlePreviewMouseUp(e) {
|
||||
const { config } = this.props
|
||||
if (config.editor.switchPreview === 'BLUR' && new Date() - this.previewMouseDownedAt < 200) {
|
||||
this.setState({
|
||||
status: 'CODE'
|
||||
}, () => {
|
||||
this.refs.code.focus()
|
||||
})
|
||||
if (
|
||||
config.editor.switchPreview === 'BLUR' &&
|
||||
new Date() - this.previewMouseDownedAt < 200
|
||||
) {
|
||||
this.setState(
|
||||
{
|
||||
status: 'CODE'
|
||||
},
|
||||
() => {
|
||||
this.refs.code.focus()
|
||||
}
|
||||
)
|
||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||
}
|
||||
}
|
||||
|
||||
handleCheckboxClick (e) {
|
||||
handleCheckboxClick(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const idMatch = /checkbox-([0-9]+)/
|
||||
@@ -164,61 +194,73 @@ class MarkdownEditor extends React.Component {
|
||||
const checkReplace = /\[x]/i
|
||||
const uncheckReplace = /\[ ]/
|
||||
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||
const lines = this.refs.code.value
|
||||
.split('\n')
|
||||
const lineIndex =
|
||||
parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||
const lines = this.refs.code.value.split('\n')
|
||||
|
||||
const targetLine = lines[lineIndex]
|
||||
let newLine = targetLine
|
||||
|
||||
if (targetLine.match(checkedMatch)) {
|
||||
lines[lineIndex] = targetLine.replace(checkReplace, '[ ]')
|
||||
newLine = targetLine.replace(checkReplace, '[ ]')
|
||||
}
|
||||
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') {
|
||||
this.setState({
|
||||
status: 'CODE'
|
||||
}, () => {
|
||||
this.refs.code.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'CODE'
|
||||
},
|
||||
() => {
|
||||
this.refs.code.focus()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
this.refs.code.focus()
|
||||
}
|
||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||
}
|
||||
|
||||
reload () {
|
||||
reload() {
|
||||
this.refs.code.reload()
|
||||
this.cancelQueue()
|
||||
this.renderPreview(this.props.value)
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
handleKeyDown(e) {
|
||||
const { config } = this.props
|
||||
if (this.state.status !== 'CODE') return false
|
||||
const keyPressed = this.state.keyPressed
|
||||
keyPressed.add(e.keyCode)
|
||||
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
|
||||
if (keyPressed.size === this.escapeFromEditor.length &&
|
||||
!this.state.isLocked && this.state.status === 'CODE' &&
|
||||
this.escapeFromEditor.every(isNoteHandlerKey)) {
|
||||
if (
|
||||
keyPressed.size === this.escapeFromEditor.length &&
|
||||
!this.state.isLocked &&
|
||||
this.state.status === 'CODE' &&
|
||||
this.escapeFromEditor.every(isNoteHandlerKey)
|
||||
) {
|
||||
this.handleContextMenu()
|
||||
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('**')
|
||||
}
|
||||
}
|
||||
|
||||
addMdAroundWord (mdElement) {
|
||||
addMdAroundWord(mdElement) {
|
||||
if (this.refs.code.editor.getSelection()) {
|
||||
return this.addMdAroundSelection(mdElement)
|
||||
}
|
||||
@@ -226,47 +268,64 @@ class MarkdownEditor extends React.Component {
|
||||
const word = this.refs.code.editor.findWordAt(currentCaret)
|
||||
const cmDoc = this.refs.code.editor.getDoc()
|
||||
cmDoc.replaceRange(mdElement, word.anchor)
|
||||
cmDoc.replaceRange(mdElement, { line: word.head.line, ch: word.head.ch + mdElement.length })
|
||||
}
|
||||
|
||||
addMdAroundSelection (mdElement) {
|
||||
this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`)
|
||||
}
|
||||
|
||||
handleDropImage (dropEvent) {
|
||||
dropEvent.preventDefault()
|
||||
const { storageKey, noteKey } = this.props
|
||||
|
||||
this.setState({
|
||||
status: 'CODE'
|
||||
}, () => {
|
||||
this.refs.code.focus()
|
||||
|
||||
this.refs.code.editor.execCommand('goDocEnd')
|
||||
this.refs.code.editor.execCommand('goLineEnd')
|
||||
this.refs.code.editor.execCommand('newlineAndIndent')
|
||||
|
||||
attachmentManagement.handleAttachmentDrop(
|
||||
this.refs.code,
|
||||
storageKey,
|
||||
noteKey,
|
||||
dropEvent
|
||||
)
|
||||
cmDoc.replaceRange(mdElement, {
|
||||
line: word.head.line,
|
||||
ch: word.head.ch + mdElement.length
|
||||
})
|
||||
}
|
||||
|
||||
handleKeyUp (e) {
|
||||
addMdAroundSelection(mdElement) {
|
||||
this.refs.code.editor.replaceSelection(
|
||||
`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`
|
||||
)
|
||||
}
|
||||
|
||||
handleDropImage(dropEvent) {
|
||||
dropEvent.preventDefault()
|
||||
const { storageKey, noteKey } = this.props
|
||||
|
||||
this.setState(
|
||||
{
|
||||
status: 'CODE'
|
||||
},
|
||||
() => {
|
||||
this.refs.code.focus()
|
||||
|
||||
this.refs.code.editor.execCommand('goDocEnd')
|
||||
this.refs.code.editor.execCommand('goLineEnd')
|
||||
this.refs.code.editor.execCommand('newlineAndIndent')
|
||||
|
||||
attachmentManagement.handleAttachmentDrop(
|
||||
this.refs.code,
|
||||
storageKey,
|
||||
noteKey,
|
||||
dropEvent
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
handleKeyUp(e) {
|
||||
const keyPressed = this.state.keyPressed
|
||||
keyPressed.delete(e.keyCode)
|
||||
this.setState({ keyPressed })
|
||||
}
|
||||
|
||||
handleLockEditor () {
|
||||
handleLockEditor() {
|
||||
this.setState({ isLocked: !this.state.isLocked })
|
||||
}
|
||||
|
||||
render () {
|
||||
const {className, value, config, storageKey, noteKey, linesHighlighted} = this.props
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
value,
|
||||
config,
|
||||
storageKey,
|
||||
noteKey,
|
||||
linesHighlighted,
|
||||
getNote,
|
||||
RTL
|
||||
} = this.props
|
||||
|
||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||
@@ -274,23 +333,24 @@ class MarkdownEditor extends React.Component {
|
||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||
|
||||
const previewStyle = {}
|
||||
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
|
||||
if (this.props.ignorePreviewPointerEvents)
|
||||
previewStyle.pointerEvents = 'none'
|
||||
|
||||
const storage = findStorage(storageKey)
|
||||
|
||||
return (
|
||||
<div className={className == null
|
||||
? 'MarkdownEditor'
|
||||
: `MarkdownEditor ${className}`
|
||||
<div
|
||||
className={
|
||||
className == null ? 'MarkdownEditor' : `MarkdownEditor ${className}`
|
||||
}
|
||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||
onContextMenu={e => this.handleContextMenu(e)}
|
||||
tabIndex='-1'
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onKeyUp={(e) => this.handleKeyUp(e)}
|
||||
onKeyDown={e => this.handleKeyDown(e)}
|
||||
onKeyUp={e => this.handleKeyUp(e)}
|
||||
>
|
||||
<CodeEditor styleName={this.state.status === 'CODE'
|
||||
? 'codeEditor'
|
||||
: 'codeEditor--hide'
|
||||
<CodeEditor
|
||||
styleName={
|
||||
this.state.status === 'CODE' ? 'codeEditor' : 'codeEditor--hide'
|
||||
}
|
||||
ref='code'
|
||||
mode='Boost Flavored Markdown'
|
||||
@@ -306,26 +366,38 @@ class MarkdownEditor extends React.Component {
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
lineWrapping
|
||||
matchingPairs={config.editor.matchingPairs}
|
||||
matchingCloseBefore={config.editor.matchingCloseBefore}
|
||||
matchingTriples={config.editor.matchingTriples}
|
||||
explodingPairs={config.editor.explodingPairs}
|
||||
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
||||
codeBlockMatchingCloseBefore={
|
||||
config.editor.codeBlockMatchingCloseBefore
|
||||
}
|
||||
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
||||
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
||||
scrollPastEnd={config.editor.scrollPastEnd}
|
||||
storageKey={storageKey}
|
||||
noteKey={noteKey}
|
||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||
enableTableEditor={config.editor.enableTableEditor}
|
||||
linesHighlighted={linesHighlighted}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
onBlur={(e) => this.handleBlur(e)}
|
||||
onChange={e => this.handleChange(e)}
|
||||
onBlur={e => this.handleBlur(e)}
|
||||
spellCheck={config.editor.spellcheck}
|
||||
enableSmartPaste={config.editor.enableSmartPaste}
|
||||
hotkey={config.hotkey}
|
||||
switchPreview={config.editor.switchPreview}
|
||||
enableMarkdownLint={config.editor.enableMarkdownLint}
|
||||
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
||||
dateFormatISO8601={config.editor.dateFormatISO8601}
|
||||
prettierConfig={config.editor.prettierConfig}
|
||||
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
||||
RTL={RTL}
|
||||
/>
|
||||
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
|
||||
? 'preview'
|
||||
: 'preview--hide'
|
||||
<MarkdownPreview
|
||||
ref={this.previewRef}
|
||||
styleName={
|
||||
this.state.status === 'PREVIEW' ? 'preview' : 'preview--hide'
|
||||
}
|
||||
style={previewStyle}
|
||||
theme={config.ui.theme}
|
||||
@@ -341,21 +413,24 @@ class MarkdownEditor extends React.Component {
|
||||
smartArrows={config.preview.smartArrows}
|
||||
breaks={config.preview.breaks}
|
||||
sanitize={config.preview.sanitize}
|
||||
ref='preview'
|
||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||
onDoubleClick={(e) => this.handleDoubleClick(e)}
|
||||
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
|
||||
onContextMenu={e => this.handleContextMenu(e)}
|
||||
onDoubleClick={e => this.handleDoubleClick(e)}
|
||||
tabIndex='0'
|
||||
value={this.state.renderValue}
|
||||
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
|
||||
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
|
||||
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
||||
onMouseUp={e => this.handlePreviewMouseUp(e)}
|
||||
onMouseDown={e => this.handlePreviewMouseDown(e)}
|
||||
onCheckboxClick={e => this.handleCheckboxClick(e)}
|
||||
showCopyNotification={config.ui.showCopyNotification}
|
||||
storagePath={storage.path}
|
||||
noteKey={noteKey}
|
||||
customCSS={config.preview.customCSS}
|
||||
allowCustomCSS={config.preview.allowCustomCSS}
|
||||
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||
onDrop={(e) => this.handleDropImage(e)}
|
||||
getNote={getNote}
|
||||
export={config.export}
|
||||
onDrop={e => this.handleDropImage(e)}
|
||||
RTL={RTL}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import Markdown from 'browser/lib/markdown'
|
||||
import _ from 'lodash'
|
||||
import CodeMirror from 'codemirror'
|
||||
@@ -11,188 +12,70 @@ import mermaidRender from './render/MermaidRender'
|
||||
import SequenceDiagram from '@rokt33r/js-sequence-diagrams'
|
||||
import Chart from 'chart.js'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import config from 'browser/main/lib/ConfigManager'
|
||||
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
||||
import convertModeName from 'browser/lib/convertModeName'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import mdurl from 'mdurl'
|
||||
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
||||
import { escapeHtmlCharacters } from 'browser/lib/utils'
|
||||
import formatMarkdown from 'browser/main/lib/dataApi/formatMarkdown'
|
||||
import formatHTML, {
|
||||
CSS_FILES,
|
||||
buildStyle,
|
||||
getCodeThemeLink,
|
||||
getStyleParams,
|
||||
escapeHtmlCharactersInCodeTag
|
||||
} from 'browser/main/lib/dataApi/formatHTML'
|
||||
import formatPDF from 'browser/main/lib/dataApi/formatPDF'
|
||||
import yaml from 'js-yaml'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import path from 'path'
|
||||
import { remote, shell } from 'electron'
|
||||
import attachmentManagement from '../main/lib/dataApi/attachmentManagement'
|
||||
import filenamify from 'filenamify'
|
||||
import { render } from 'react-dom'
|
||||
import Carousel from 'react-image-carousel'
|
||||
import { push } from 'connected-react-router'
|
||||
import ConfigManager from '../main/lib/ConfigManager'
|
||||
|
||||
const { remote, shell } = require('electron')
|
||||
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
|
||||
const buildMarkdownPreviewContextMenu = require('browser/lib/contextMenuBuilder').buildMarkdownPreviewContextMenu
|
||||
|
||||
const { app } = remote
|
||||
const path = require('path')
|
||||
const fileUrl = require('file-url')
|
||||
import uiThemes from 'browser/lib/ui-themes'
|
||||
import { buildMarkdownPreviewContextMenu } from 'browser/lib/contextMenuBuilder'
|
||||
|
||||
const dialog = remote.dialog
|
||||
|
||||
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
||||
const appPath = fileUrl(
|
||||
process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve()
|
||||
)
|
||||
const CSS_FILES = [
|
||||
`${appPath}/node_modules/katex/dist/katex.min.css`,
|
||||
`${appPath}/node_modules/codemirror/lib/codemirror.css`,
|
||||
`${appPath}/node_modules/react-image-carousel/lib/css/main.min.css`
|
||||
]
|
||||
const win = global.process.platform === 'win32'
|
||||
|
||||
function buildStyle (
|
||||
fontFamily,
|
||||
fontSize,
|
||||
codeBlockFontFamily,
|
||||
lineNumber,
|
||||
scrollPastEnd,
|
||||
theme,
|
||||
allowCustomCSS,
|
||||
customCSS
|
||||
) {
|
||||
return `
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('${appPath}/resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||
url('${appPath}/resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('${appPath}/resources/fonts/Lato-Regular.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('${appPath}/resources/fonts/Lato-Black.woff2') format('woff2'), /* Modern Browsers */
|
||||
url('${appPath}/resources/fonts/Lato-Black.woff') format('woff'), /* Modern Browsers */
|
||||
url('${appPath}/resources/fonts/Lato-Black.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Material Icons'),
|
||||
local('MaterialIcons-Regular'),
|
||||
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff2') format('woff2'),
|
||||
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
|
||||
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
|
||||
}
|
||||
${markdownStyle}
|
||||
|
||||
body {
|
||||
font-family: '${fontFamily.join("','")}';
|
||||
font-size: ${fontSize}px;
|
||||
${scrollPastEnd && 'padding-bottom: 90vh;'}
|
||||
}
|
||||
@media print {
|
||||
body {
|
||||
padding-bottom: initial;
|
||||
}
|
||||
}
|
||||
code {
|
||||
font-family: '${codeBlockFontFamily.join("','")}';
|
||||
background-color: rgba(0,0,0,0.04);
|
||||
}
|
||||
.lineNumber {
|
||||
${lineNumber && 'display: block !important;'}
|
||||
font-family: '${codeBlockFontFamily.join("','")}';
|
||||
}
|
||||
|
||||
.clipboardButton {
|
||||
color: rgba(147,147,149,0.8);;
|
||||
fill: rgba(147,147,149,1);;
|
||||
border-radius: 50%;
|
||||
margin: 0px 10px;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clipboardButton:hover {
|
||||
transition: 0.2s;
|
||||
color: #939395;
|
||||
fill: #939395;
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
border: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
padding-bottom: 4px;
|
||||
margin: 1em 0 8px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding-bottom: 0.2em;
|
||||
margin: 1em 0 0.37em;
|
||||
}
|
||||
|
||||
body p {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body[data-theme="${theme}"] {
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
}
|
||||
.clipboardButton {
|
||||
display: none
|
||||
}
|
||||
}
|
||||
|
||||
${allowCustomCSS ? customCSS : ''}
|
||||
`
|
||||
}
|
||||
|
||||
const scrollBarStyle = `
|
||||
::-webkit-scrollbar {
|
||||
${config.get().ui.showScrollBar ? '' : 'display: none;'}
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
${config.get().ui.showScrollBar ? '' : 'display: none;'}
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track-piece {
|
||||
background-color: inherit;
|
||||
}
|
||||
`
|
||||
const scrollBarDarkStyle = `
|
||||
::-webkit-scrollbar {
|
||||
${config.get().ui.showScrollBar ? '' : 'display: none;'}
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
${config.get().ui.showScrollBar ? '' : 'display: none;'}
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track-piece {
|
||||
background-color: inherit;
|
||||
}
|
||||
`
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
|
||||
const defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
|
||||
if (!OSX) {
|
||||
defaultFontFamily.unshift('Microsoft YaHei')
|
||||
defaultFontFamily.unshift('meiryo')
|
||||
}
|
||||
const defaultCodeBlockFontFamily = [
|
||||
'Monaco',
|
||||
'Menlo',
|
||||
'Ubuntu Mono',
|
||||
'Consolas',
|
||||
'source-code-pro',
|
||||
'monospace'
|
||||
]
|
||||
|
||||
// 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) {
|
||||
function getSourceLineNumberByElement(element) {
|
||||
let isHasLineNumber = element.dataset.line !== undefined
|
||||
let parent = element
|
||||
while (!isHasLineNumber && parent.parentElement !== null) {
|
||||
@@ -202,8 +85,8 @@ function getSourceLineNumberByElement (element) {
|
||||
return parent.dataset.line !== undefined ? parseInt(parent.dataset.line) : -1
|
||||
}
|
||||
|
||||
export default class MarkdownPreview extends React.Component {
|
||||
constructor (props) {
|
||||
class MarkdownPreview extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.contextMenuHandler = e => this.handleContextMenu(e)
|
||||
@@ -220,13 +103,14 @@ export default class MarkdownPreview extends React.Component {
|
||||
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
|
||||
this.saveAsPdfHandler = () => this.handleSaveAsPdf()
|
||||
this.printHandler = () => this.handlePrint()
|
||||
this.resizeHandler = _.throttle(this.handleResize.bind(this), 100)
|
||||
|
||||
this.linkClickHandler = this.handleLinkClick.bind(this)
|
||||
this.initMarkdown = this.initMarkdown.bind(this)
|
||||
this.initMarkdown()
|
||||
}
|
||||
|
||||
initMarkdown () {
|
||||
initMarkdown() {
|
||||
const { smartQuotes, sanitize, breaks } = this.props
|
||||
this.markdown = new Markdown({
|
||||
typographer: smartQuotes,
|
||||
@@ -235,34 +119,41 @@ export default class MarkdownPreview extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleCheckboxClick (e) {
|
||||
handleCheckboxClick(e) {
|
||||
this.props.onCheckboxClick(e)
|
||||
}
|
||||
|
||||
handleScroll (e) {
|
||||
handleScroll(e) {
|
||||
if (this.props.onScroll) {
|
||||
this.props.onScroll(e)
|
||||
}
|
||||
}
|
||||
|
||||
handleContextMenu (event) {
|
||||
handleContextMenu(event) {
|
||||
const menu = buildMarkdownPreviewContextMenu(this, event)
|
||||
if (menu != null) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
handleDoubleClick (e) {
|
||||
handleDoubleClick(e) {
|
||||
if (this.props.onDoubleClick != null) this.props.onDoubleClick(e)
|
||||
}
|
||||
|
||||
handleMouseDown (e) {
|
||||
handleMouseDown(e) {
|
||||
const config = ConfigManager.get()
|
||||
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') {
|
||||
if (
|
||||
config.editor.switchPreview === 'RIGHTCLICK' &&
|
||||
e.buttons === 2 &&
|
||||
config.editor.type === 'SPLIT'
|
||||
) {
|
||||
eventEmitter.emit('topbar:togglemodebutton', 'CODE')
|
||||
}
|
||||
if (e.ctrlKey) {
|
||||
@@ -278,10 +169,11 @@ export default class MarkdownPreview extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.onMouseDown != null && targetTag === 'BODY') this.props.onMouseDown(e)
|
||||
if (this.props.onMouseDown != null && targetTag === 'BODY')
|
||||
this.props.onMouseDown(e)
|
||||
}
|
||||
|
||||
handleMouseUp (e) {
|
||||
handleMouseUp(e) {
|
||||
if (!this.props.onMouseUp) return
|
||||
if (e.target != null && e.target.tagName === 'A') {
|
||||
return null
|
||||
@@ -289,113 +181,47 @@ export default class MarkdownPreview extends React.Component {
|
||||
if (this.props.onMouseUp != null) this.props.onMouseUp(e)
|
||||
}
|
||||
|
||||
handleSaveAsText () {
|
||||
handleSaveAsText() {
|
||||
this.exportAsDocument('txt')
|
||||
}
|
||||
|
||||
handleSaveAsMd () {
|
||||
this.exportAsDocument('md')
|
||||
handleSaveAsMd() {
|
||||
this.exportAsDocument('md', formatMarkdown(this.props))
|
||||
}
|
||||
|
||||
htmlContentFormatter (noteContent, exportTasks, targetDir) {
|
||||
const {
|
||||
fontFamily,
|
||||
fontSize,
|
||||
codeBlockFontFamily,
|
||||
lineNumber,
|
||||
codeBlockTheme,
|
||||
scrollPastEnd,
|
||||
theme,
|
||||
allowCustomCSS,
|
||||
customCSS
|
||||
} = this.getStyleParams()
|
||||
|
||||
const inlineStyles = buildStyle(
|
||||
fontFamily,
|
||||
fontSize,
|
||||
codeBlockFontFamily,
|
||||
lineNumber,
|
||||
scrollPastEnd,
|
||||
theme,
|
||||
allowCustomCSS,
|
||||
customCSS
|
||||
)
|
||||
let body = this.markdown.render(noteContent)
|
||||
body = attachmentManagement.fixLocalURLS(
|
||||
body,
|
||||
this.props.storagePath
|
||||
)
|
||||
const files = [this.getCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||
files.forEach(file => {
|
||||
if (global.process.platform === 'win32') {
|
||||
file = file.replace('file:///', '')
|
||||
} else {
|
||||
file = file.replace('file://', '')
|
||||
}
|
||||
exportTasks.push({
|
||||
src: file,
|
||||
dst: 'css'
|
||||
})
|
||||
})
|
||||
|
||||
let styles = ''
|
||||
files.forEach(file => {
|
||||
styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
|
||||
})
|
||||
|
||||
return `<html>
|
||||
<head>
|
||||
<base href="file://${targetDir}/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
|
||||
<style id="style">${inlineStyles}</style>
|
||||
${styles}
|
||||
</head>
|
||||
<body>${body}</body>
|
||||
</html>`
|
||||
handleSaveAsHtml() {
|
||||
this.exportAsDocument('html', formatHTML(this.props))
|
||||
}
|
||||
|
||||
handleSaveAsHtml () {
|
||||
this.exportAsDocument('html', (noteContent, exportTasks, targetDir) => Promise.resolve(this.htmlContentFormatter(noteContent, exportTasks, targetDir)))
|
||||
handleSaveAsPdf() {
|
||||
this.exportAsDocument('pdf', formatPDF(this.props))
|
||||
}
|
||||
|
||||
handleSaveAsPdf () {
|
||||
this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => {
|
||||
const printout = new remote.BrowserWindow({show: false, webPreferences: {webSecurity: 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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
handlePrint () {
|
||||
handlePrint() {
|
||||
this.refs.root.contentWindow.print()
|
||||
}
|
||||
|
||||
exportAsDocument (fileType, contentFormatter) {
|
||||
exportAsDocument(fileType, contentFormatter) {
|
||||
const note = this.props.getNote()
|
||||
|
||||
const options = {
|
||||
defaultPath: filenamify(note.title, {
|
||||
replacement: '_'
|
||||
}),
|
||||
filters: [{ name: 'Documents', extensions: [fileType] }],
|
||||
properties: ['openFile', 'createDirectory']
|
||||
}
|
||||
|
||||
dialog.showSaveDialog(remote.getCurrentWindow(), options, filename => {
|
||||
if (filename) {
|
||||
const content = this.props.value
|
||||
const storage = this.props.storagePath
|
||||
const nodeKey = this.props.noteKey
|
||||
const storagePath = this.props.storagePath
|
||||
|
||||
exportNote(nodeKey, storage, content, filename, contentFormatter)
|
||||
exportNote(storagePath, note, filename, contentFormatter)
|
||||
.then(res => {
|
||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'info',
|
||||
message: `Exported to ${filename}`
|
||||
message: `Exported to ${filename}`,
|
||||
buttons: [i18n.__('Ok')]
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
@@ -409,7 +235,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
fixDecodedURI (node) {
|
||||
fixDecodedURI(node) {
|
||||
if (
|
||||
node &&
|
||||
node.children.length === 1 &&
|
||||
@@ -421,46 +247,15 @@ 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
|
||||
|
||||
switch (theme) {
|
||||
case 'dark':
|
||||
case 'solarized-dark':
|
||||
case 'monokai':
|
||||
case 'dracula':
|
||||
return scrollBarDarkStyle
|
||||
default:
|
||||
return scrollBarStyle
|
||||
}
|
||||
return uiThemes.some(item => item.name === theme && item.isDark)
|
||||
? scrollBarDarkStyle
|
||||
: scrollBarStyle
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
const { onDrop } = this.props
|
||||
|
||||
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
||||
@@ -510,6 +305,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
'scroll',
|
||||
this.scrollHandler
|
||||
)
|
||||
this.refs.root.contentWindow.addEventListener('resize', this.resizeHandler)
|
||||
eventEmitter.on('export:save-text', this.saveAsTextHandler)
|
||||
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
||||
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
|
||||
@@ -517,7 +313,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
eventEmitter.on('print', this.printHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
const { onDrop } = this.props
|
||||
|
||||
this.refs.root.contentWindow.document.body.removeEventListener(
|
||||
@@ -548,6 +344,10 @@ export default class MarkdownPreview extends React.Component {
|
||||
'scroll',
|
||||
this.scrollHandler
|
||||
)
|
||||
this.refs.root.contentWindow.removeEventListener(
|
||||
'resize',
|
||||
this.resizeHandler
|
||||
)
|
||||
eventEmitter.off('export:save-text', this.saveAsTextHandler)
|
||||
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
||||
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
|
||||
@@ -555,17 +355,20 @@ export default class MarkdownPreview extends React.Component {
|
||||
eventEmitter.off('print', this.printHandler)
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
||||
componentDidUpdate(prevProps) {
|
||||
// actual rewriteIframe function should be called only once
|
||||
let needsRewriteIframe = false
|
||||
if (prevProps.value !== this.props.value) needsRewriteIframe = true
|
||||
if (
|
||||
prevProps.smartQuotes !== this.props.smartQuotes ||
|
||||
prevProps.sanitize !== this.props.sanitize ||
|
||||
prevProps.mermaidHTMLLabel !== this.props.mermaidHTMLLabel ||
|
||||
prevProps.smartArrows !== this.props.smartArrows ||
|
||||
prevProps.breaks !== this.props.breaks ||
|
||||
prevProps.lineThroughCheckbox !== this.props.lineThroughCheckbox
|
||||
) {
|
||||
this.initMarkdown()
|
||||
this.rewriteIframe()
|
||||
needsRewriteIframe = true
|
||||
}
|
||||
if (
|
||||
prevProps.fontFamily !== this.props.fontFamily ||
|
||||
@@ -577,52 +380,24 @@ export default class MarkdownPreview extends React.Component {
|
||||
prevProps.theme !== this.props.theme ||
|
||||
prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
|
||||
prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
|
||||
prevProps.customCSS !== this.props.customCSS
|
||||
prevProps.customCSS !== this.props.customCSS ||
|
||||
prevProps.RTL !== this.props.RTL
|
||||
) {
|
||||
this.applyStyle()
|
||||
needsRewriteIframe = true
|
||||
}
|
||||
|
||||
if (needsRewriteIframe) {
|
||||
this.rewriteIframe()
|
||||
}
|
||||
}
|
||||
|
||||
getStyleParams () {
|
||||
const {
|
||||
fontSize,
|
||||
lineNumber,
|
||||
codeBlockTheme,
|
||||
scrollPastEnd,
|
||||
theme,
|
||||
allowCustomCSS,
|
||||
customCSS
|
||||
} = this.props
|
||||
let { fontFamily, codeBlockFontFamily } = this.props
|
||||
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
||||
? fontFamily
|
||||
.split(',')
|
||||
.map(fontName => fontName.trim())
|
||||
.concat(defaultFontFamily)
|
||||
: defaultFontFamily
|
||||
codeBlockFontFamily = _.isString(codeBlockFontFamily) &&
|
||||
codeBlockFontFamily.trim().length > 0
|
||||
? codeBlockFontFamily
|
||||
.split(',')
|
||||
.map(fontName => fontName.trim())
|
||||
.concat(defaultCodeBlockFontFamily)
|
||||
: defaultCodeBlockFontFamily
|
||||
|
||||
return {
|
||||
fontFamily,
|
||||
fontSize,
|
||||
codeBlockFontFamily,
|
||||
lineNumber,
|
||||
codeBlockTheme,
|
||||
scrollPastEnd,
|
||||
theme,
|
||||
allowCustomCSS,
|
||||
customCSS
|
||||
// Should scroll to top after selecting another note
|
||||
if (prevProps.noteKey !== this.props.noteKey) {
|
||||
this.scrollTo(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
applyStyle () {
|
||||
applyStyle() {
|
||||
const {
|
||||
fontFamily,
|
||||
fontSize,
|
||||
@@ -632,12 +407,14 @@ export default class MarkdownPreview extends React.Component {
|
||||
scrollPastEnd,
|
||||
theme,
|
||||
allowCustomCSS,
|
||||
customCSS
|
||||
} = this.getStyleParams()
|
||||
customCSS,
|
||||
RTL
|
||||
} = getStyleParams(this.props)
|
||||
|
||||
this.getWindow().document.getElementById(
|
||||
'codeTheme'
|
||||
).href = this.getCodeThemeLink(codeBlockTheme)
|
||||
).href = getCodeThemeLink(codeBlockTheme)
|
||||
|
||||
this.getWindow().document.getElementById('style').innerHTML = buildStyle(
|
||||
fontFamily,
|
||||
fontSize,
|
||||
@@ -646,19 +423,12 @@ export default class MarkdownPreview extends React.Component {
|
||||
scrollPastEnd,
|
||||
theme,
|
||||
allowCustomCSS,
|
||||
customCSS
|
||||
customCSS,
|
||||
RTL
|
||||
)
|
||||
}
|
||||
|
||||
getCodeThemeLink (name) {
|
||||
const theme = consts.THEMES.find(theme => theme.name === name)
|
||||
|
||||
return theme != null
|
||||
? theme.path
|
||||
: `${appPath}/node_modules/codemirror/theme/elegant.css`
|
||||
}
|
||||
|
||||
rewriteIframe () {
|
||||
rewriteIframe() {
|
||||
_.forEach(
|
||||
this.refs.root.contentWindow.document.querySelectorAll(
|
||||
'input[type="checkbox"]'
|
||||
@@ -681,14 +451,15 @@ export default class MarkdownPreview extends React.Component {
|
||||
showCopyNotification,
|
||||
storagePath,
|
||||
noteKey,
|
||||
sanitize
|
||||
sanitize,
|
||||
mermaidHTMLLabel
|
||||
} = this.props
|
||||
let { value, codeBlockTheme } = this.props
|
||||
|
||||
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
||||
if (sanitize === 'NONE') {
|
||||
const splitWithCodeTag = value.split('```')
|
||||
value = this.escapeHtmlCharactersInCodeTag(splitWithCodeTag)
|
||||
value = escapeHtmlCharactersInCodeTag(splitWithCodeTag)
|
||||
}
|
||||
const renderedHTML = this.markdown.render(value)
|
||||
attachmentManagement.migrateAttachments(value, storagePath, noteKey)
|
||||
@@ -715,7 +486,9 @@ export default class MarkdownPreview extends React.Component {
|
||||
|
||||
codeBlockTheme = consts.THEMES.find(theme => theme.name === codeBlockTheme)
|
||||
|
||||
const codeBlockThemeClassName = codeBlockTheme ? codeBlockTheme.className : 'cm-s-default'
|
||||
const codeBlockThemeClassName = codeBlockTheme
|
||||
? codeBlockTheme.className
|
||||
: 'cm-s-default'
|
||||
|
||||
_.forEach(
|
||||
this.refs.root.contentWindow.document.querySelectorAll('.code code'),
|
||||
@@ -749,13 +522,9 @@ export default class MarkdownPreview extends React.Component {
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const opts = {}
|
||||
// if (this.props.theme === 'dark') {
|
||||
// opts['font-color'] = '#DDD'
|
||||
// opts['line-color'] = '#DDD'
|
||||
// opts['element-color'] = '#DDD'
|
||||
// opts['fill'] = '#3A404C'
|
||||
// }
|
||||
|
||||
_.forEach(
|
||||
this.refs.root.contentWindow.document.querySelectorAll('.flowchart'),
|
||||
el => {
|
||||
@@ -801,7 +570,10 @@ export default class MarkdownPreview extends React.Component {
|
||||
el => {
|
||||
try {
|
||||
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 = ''
|
||||
|
||||
const canvas = document.createElement('canvas')
|
||||
@@ -813,6 +585,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
canvas.height = height.value + 'vh'
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(canvas, chartConfig)
|
||||
} catch (e) {
|
||||
el.className = 'chart-error'
|
||||
@@ -823,7 +596,12 @@ export default class MarkdownPreview extends React.Component {
|
||||
_.forEach(
|
||||
this.refs.root.contentWindow.document.querySelectorAll('.mermaid'),
|
||||
el => {
|
||||
mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme)
|
||||
mermaidRender(
|
||||
el,
|
||||
htmlTextHelper.decodeEntities(el.innerHTML),
|
||||
theme,
|
||||
mermaidHTMLLabel
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -845,20 +623,14 @@ export default class MarkdownPreview extends React.Component {
|
||||
autoplay = 0
|
||||
}
|
||||
|
||||
render(
|
||||
<Carousel
|
||||
images={images}
|
||||
autoplay={autoplay}
|
||||
/>,
|
||||
el
|
||||
)
|
||||
render(<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) => {
|
||||
const imgObserver = new MutationObserver(mutationList => {
|
||||
for (const mu of mutationList) {
|
||||
if (mu.target.className === 'carouselContent-enter-done') {
|
||||
this.setImgOnClickEventHelper(mu.target, rect)
|
||||
@@ -867,26 +639,32 @@ export default class MarkdownPreview extends React.Component {
|
||||
}
|
||||
})
|
||||
|
||||
const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('img')
|
||||
const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll(
|
||||
'img'
|
||||
)
|
||||
for (const img of imgList) {
|
||||
const parentEl = img.parentElement
|
||||
this.setImgOnClickEventHelper(img, rect)
|
||||
imgObserver.observe(parentEl, config)
|
||||
}
|
||||
|
||||
const aList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('a')
|
||||
const aList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll(
|
||||
'a'
|
||||
)
|
||||
for (const a of aList) {
|
||||
a.removeEventListener('click', this.linkClickHandler)
|
||||
a.addEventListener('click', this.linkClickHandler)
|
||||
}
|
||||
}
|
||||
|
||||
setImgOnClickEventHelper (img, rect) {
|
||||
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 magnification = baseOnWidth
|
||||
? widthMagnification
|
||||
: heightMagnification
|
||||
|
||||
const zoomImgWidth = img.width * magnification
|
||||
const zoomImgHeight = img.height * magnification
|
||||
@@ -917,10 +695,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
width: ${zoomImgWidth};
|
||||
height: ${zoomImgHeight}px;
|
||||
`
|
||||
zoomImg.animate([
|
||||
originalImgRect,
|
||||
zoomInImgRect
|
||||
], animationSpeed)
|
||||
zoomImg.animate([originalImgRect, zoomInImgRect], animationSpeed)
|
||||
|
||||
const overlay = document.createElement('div')
|
||||
overlay.style = `
|
||||
@@ -941,50 +716,71 @@ export default class MarkdownPreview extends React.Component {
|
||||
width: ${img.width}px;
|
||||
height: ${img.height}px;
|
||||
`
|
||||
const zoomOutImgAnimation = zoomImg.animate([
|
||||
zoomInImgRect,
|
||||
originalImgRect
|
||||
], animationSpeed)
|
||||
const zoomOutImgAnimation = zoomImg.animate(
|
||||
[zoomInImgRect, originalImgRect],
|
||||
animationSpeed
|
||||
)
|
||||
zoomOutImgAnimation.onfinish = () => overlay.remove()
|
||||
}
|
||||
|
||||
overlay.appendChild(zoomImg)
|
||||
document.body.appendChild(overlay)
|
||||
}
|
||||
|
||||
this.getWindow().scrollTo(0, 0)
|
||||
}
|
||||
|
||||
focus () {
|
||||
handleResize() {
|
||||
_.forEach(
|
||||
this.refs.root.contentWindow.document.querySelectorAll('svg[ratio]'),
|
||||
el => {
|
||||
el.setAttribute('height', el.clientWidth / el.getAttribute('ratio'))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.refs.root.focus()
|
||||
}
|
||||
|
||||
getWindow () {
|
||||
getWindow() {
|
||||
return this.refs.root.contentWindow
|
||||
}
|
||||
|
||||
scrollTo (targetRow) {
|
||||
/**
|
||||
* @public
|
||||
* @param {Number} targetLine
|
||||
*/
|
||||
scrollToLine(targetLine) {
|
||||
const blocks = this.getWindow().document.querySelectorAll(
|
||||
'body>[data-line]'
|
||||
'body [data-line]'
|
||||
)
|
||||
|
||||
for (let index = 0; index < blocks.length; index++) {
|
||||
let block = blocks[index]
|
||||
const row = parseInt(block.getAttribute('data-line'))
|
||||
if (row > targetRow || index === blocks.length - 1) {
|
||||
const line = parseInt(block.getAttribute('data-line'))
|
||||
|
||||
if (line > targetLine || index === blocks.length - 1) {
|
||||
block = blocks[index - 1]
|
||||
block != null && this.getWindow().scrollTo(0, block.offsetTop)
|
||||
block != null && this.scrollTo(0, block.offsetTop)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preventImageDroppedHandler (e) {
|
||||
/**
|
||||
* `document.body.scrollTo`
|
||||
* @param {Number} x
|
||||
* @param {Number} y
|
||||
*/
|
||||
scrollTo(x, y) {
|
||||
this.getWindow().document.body.scrollTo(x, y)
|
||||
}
|
||||
|
||||
preventImageDroppedHandler(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
notify (title, options) {
|
||||
notify(title, options) {
|
||||
if (global.process.platform === 'win32') {
|
||||
options.icon = path.join(
|
||||
'file://',
|
||||
@@ -995,30 +791,38 @@ export default class MarkdownPreview extends React.Component {
|
||||
return new window.Notification(title, options)
|
||||
}
|
||||
|
||||
handleLinkClick (e) {
|
||||
handleLinkClick(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const rawHref = e.target.getAttribute('href')
|
||||
const parser = document.createElement('a')
|
||||
parser.href = e.target.getAttribute('href')
|
||||
const { href, hash } = parser
|
||||
const linkHash = hash === '' ? rawHref : hash // needed because we're having special link formats that are removed by parser e.g. :line:10
|
||||
const el = e.target.closest('a[href]')
|
||||
if (!el) return
|
||||
|
||||
const rawHref = el.getAttribute('href')
|
||||
const { dispatch } = this.props
|
||||
if (!rawHref) return // not checked href because parser will create file://... string for [empty link]()
|
||||
|
||||
const extractId = /(main.html)?#/
|
||||
const regexNoteInternalLink = new RegExp(`${extractId.source}(.+)`)
|
||||
if (regexNoteInternalLink.test(linkHash)) {
|
||||
const targetId = mdurl.encode(linkHash.replace(extractId, ''))
|
||||
const targetElement = this.refs.root.contentWindow.document.getElementById(
|
||||
targetId
|
||||
)
|
||||
const parser = document.createElement('a')
|
||||
parser.href = rawHref
|
||||
const isStartWithHash = rawHref[0] === '#'
|
||||
const { href, hash } = parser
|
||||
|
||||
if (targetElement != null) {
|
||||
this.getWindow().scrollTo(0, targetElement.offsetTop)
|
||||
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) {
|
||||
this.scrollTo(0, targetElement.offsetTop)
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// this will match the new uuid v4 hash and the old hash
|
||||
@@ -1049,11 +853,29 @@ export default class MarkdownPreview extends React.Component {
|
||||
return
|
||||
}
|
||||
|
||||
const regexIsTagLink = /^:tag:([\w]+)$/
|
||||
if (regexIsTagLink.test(rawHref)) {
|
||||
const tag = rawHref.match(regexIsTagLink)[1]
|
||||
dispatch(push(`/tags/${encodeURIComponent(tag)}`))
|
||||
return
|
||||
}
|
||||
|
||||
// other case
|
||||
shell.openExternal(href)
|
||||
this.openExternal(href)
|
||||
}
|
||||
|
||||
render () {
|
||||
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() {
|
||||
const { className, style, tabIndex } = this.props
|
||||
return (
|
||||
<iframe
|
||||
@@ -1082,3 +904,10 @@ MarkdownPreview.propTypes = {
|
||||
smartArrows: PropTypes.bool,
|
||||
breaks: PropTypes.bool
|
||||
}
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{ forwardRef: true }
|
||||
)(MarkdownPreview)
|
||||
|
||||
@@ -8,73 +8,217 @@ import styles from './MarkdownSplitEditor.styl'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
|
||||
class MarkdownSplitEditor extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.value = props.value
|
||||
this.focus = () => this.refs.code.focus()
|
||||
this.reload = () => this.refs.code.reload()
|
||||
this.userScroll = true
|
||||
this.userScroll = props.config.preview.scrollSync
|
||||
this.state = {
|
||||
isSliderFocused: false,
|
||||
codeEditorWidthInPercent: 50
|
||||
codeEditorWidthInPercent: 50,
|
||||
codeEditorHeightInPercent: 50
|
||||
}
|
||||
}
|
||||
|
||||
setValue (value) {
|
||||
componentDidUpdate(prevProps) {
|
||||
if (
|
||||
this.props.config.preview.scrollSync !==
|
||||
prevProps.config.preview.scrollSync
|
||||
) {
|
||||
this.userScroll = this.props.config.preview.scrollSync
|
||||
}
|
||||
}
|
||||
|
||||
handleCursorActivity(editor) {
|
||||
if (this.userScroll) {
|
||||
const previewDoc = _.get(
|
||||
this,
|
||||
'refs.preview.refs.root.contentWindow.document'
|
||||
)
|
||||
const previewTop = _.get(previewDoc, 'body.scrollTop')
|
||||
|
||||
const line = editor.doc.getCursor().line
|
||||
let top
|
||||
if (line === 0) {
|
||||
top = 0
|
||||
} else {
|
||||
const blockElements = previewDoc.querySelectorAll('body [data-line]')
|
||||
const blocks = []
|
||||
for (const block of blockElements) {
|
||||
const l = parseInt(block.getAttribute('data-line'))
|
||||
|
||||
blocks.push({
|
||||
line: l,
|
||||
top: block.offsetTop
|
||||
})
|
||||
|
||||
if (l > line) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (blocks.length === 1) {
|
||||
const block = blockElements[blockElements.length - 1]
|
||||
|
||||
blocks.push({
|
||||
line: editor.doc.size,
|
||||
top: block.offsetTop + block.offsetHeight
|
||||
})
|
||||
}
|
||||
|
||||
const i = blocks.length - 1
|
||||
|
||||
const ratio =
|
||||
(blocks[i].top - blocks[i - 1].top) /
|
||||
(blocks[i].line - blocks[i - 1].line)
|
||||
|
||||
const delta = Math.floor(_.get(previewDoc, 'body.clientHeight') / 3)
|
||||
|
||||
top =
|
||||
blocks[i - 1].top +
|
||||
Math.floor((line - blocks[i - 1].line) * ratio) -
|
||||
delta
|
||||
}
|
||||
|
||||
this.scrollTo(previewTop, top, y =>
|
||||
_.set(previewDoc, 'body.scrollTop', y)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
setValue(value) {
|
||||
this.refs.code.setValue(value)
|
||||
}
|
||||
|
||||
handleOnChange (e) {
|
||||
handleOnChange(e) {
|
||||
this.value = this.refs.code.value
|
||||
this.props.onChange(e)
|
||||
}
|
||||
|
||||
handleScroll (e) {
|
||||
if (!this.props.config.preview.scrollSync) return
|
||||
|
||||
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
|
||||
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
||||
let srcTop, srcHeight, targetTop, targetHeight
|
||||
|
||||
handleEditorScroll(e) {
|
||||
if (this.userScroll) {
|
||||
if (e.doc) {
|
||||
srcTop = _.get(e, 'doc.scrollTop')
|
||||
srcHeight = _.get(e, 'doc.height')
|
||||
targetTop = _.get(previewDoc, 'body.scrollTop')
|
||||
targetHeight = _.get(previewDoc, 'body.scrollHeight')
|
||||
const previewDoc = _.get(
|
||||
this,
|
||||
'refs.preview.refs.root.contentWindow.document'
|
||||
)
|
||||
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
||||
|
||||
const from = codeDoc.cm.coordsChar({ left: 0, top: 0 }).line
|
||||
const to = codeDoc.cm.coordsChar({
|
||||
left: 0,
|
||||
top: codeDoc.cm.display.lastWrapHeight * 1.125
|
||||
}).line
|
||||
const previewTop = _.get(previewDoc, 'body.scrollTop')
|
||||
|
||||
let top
|
||||
if (from === 0) {
|
||||
top = 0
|
||||
} else if (to === codeDoc.lastLine()) {
|
||||
top =
|
||||
_.get(previewDoc, 'body.scrollHeight') -
|
||||
_.get(previewDoc, 'body.clientHeight')
|
||||
} else {
|
||||
srcTop = _.get(previewDoc, 'body.scrollTop')
|
||||
srcHeight = _.get(previewDoc, 'body.scrollHeight')
|
||||
targetTop = _.get(codeDoc, 'scrollTop')
|
||||
targetHeight = _.get(codeDoc, 'height')
|
||||
const line = from + Math.floor((to - from) / 3)
|
||||
|
||||
const blockElements = previewDoc.querySelectorAll('body [data-line]')
|
||||
const blocks = []
|
||||
for (const block of blockElements) {
|
||||
const l = parseInt(block.getAttribute('data-line'))
|
||||
|
||||
blocks.push({
|
||||
line: l,
|
||||
top: block.offsetTop
|
||||
})
|
||||
|
||||
if (l > line) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (blocks.length === 1) {
|
||||
const block = blockElements[blockElements.length - 1]
|
||||
|
||||
blocks.push({
|
||||
line: codeDoc.size,
|
||||
top: block.offsetTop + block.offsetHeight
|
||||
})
|
||||
}
|
||||
|
||||
const i = blocks.length - 1
|
||||
|
||||
const ratio =
|
||||
(blocks[i].top - blocks[i - 1].top) /
|
||||
(blocks[i].line - blocks[i - 1].line)
|
||||
|
||||
top =
|
||||
blocks[i - 1].top + Math.floor((line - blocks[i - 1].line) * ratio)
|
||||
}
|
||||
|
||||
const distance = (targetHeight * srcTop / srcHeight) - targetTop
|
||||
const framerate = 1000 / 60
|
||||
const frames = 20
|
||||
const refractory = frames * framerate
|
||||
|
||||
this.userScroll = false
|
||||
|
||||
let frame = 0
|
||||
let scrollPos, time
|
||||
const timer = setInterval(() => {
|
||||
time = frame / frames
|
||||
scrollPos = time < 0.5
|
||||
? 2 * time * time // ease in
|
||||
: -1 + (4 - 2 * time) * time // ease out
|
||||
if (e.doc) _.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
|
||||
else _.get(this, 'refs.code.editor').scrollTo(0, targetTop + scrollPos * distance)
|
||||
if (frame >= frames) {
|
||||
clearInterval(timer)
|
||||
setTimeout(() => { this.userScroll = true }, refractory)
|
||||
}
|
||||
frame++
|
||||
}, framerate)
|
||||
this.scrollTo(previewTop, top, y =>
|
||||
_.set(previewDoc, 'body.scrollTop', y)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
handleCheckboxClick (e) {
|
||||
handlePreviewScroll(e) {
|
||||
if (this.userScroll) {
|
||||
const previewDoc = _.get(
|
||||
this,
|
||||
'refs.preview.refs.root.contentWindow.document'
|
||||
)
|
||||
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
||||
|
||||
const srcTop = _.get(previewDoc, 'body.scrollTop')
|
||||
const editorTop = _.get(codeDoc, 'scrollTop')
|
||||
|
||||
let top
|
||||
if (srcTop === 0) {
|
||||
top = 0
|
||||
} else {
|
||||
const delta = Math.floor(_.get(previewDoc, 'body.clientHeight') / 3)
|
||||
const previewTop = srcTop + delta
|
||||
|
||||
const blockElements = previewDoc.querySelectorAll('body [data-line]')
|
||||
const blocks = []
|
||||
for (const block of blockElements) {
|
||||
const top = block.offsetTop
|
||||
|
||||
blocks.push({
|
||||
line: parseInt(block.getAttribute('data-line')),
|
||||
top
|
||||
})
|
||||
|
||||
if (top > previewTop) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (blocks.length === 1) {
|
||||
const block = blockElements[blockElements.length - 1]
|
||||
|
||||
blocks.push({
|
||||
line: codeDoc.size,
|
||||
top: block.offsetTop + block.offsetHeight
|
||||
})
|
||||
}
|
||||
|
||||
const i = blocks.length - 1
|
||||
|
||||
const from = codeDoc.cm.heightAtLine(blocks[i - 1].line, 'local')
|
||||
const to = codeDoc.cm.heightAtLine(blocks[i].line, 'local')
|
||||
|
||||
const ratio =
|
||||
(previewTop - blocks[i - 1].top) / (blocks[i].top - blocks[i - 1].top)
|
||||
|
||||
top = from + Math.floor((to - from) * ratio) - delta
|
||||
}
|
||||
|
||||
this.scrollTo(editorTop, top, y => codeDoc.cm.scrollTo(0, y))
|
||||
}
|
||||
}
|
||||
|
||||
handleCheckboxClick(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const idMatch = /checkbox-([0-9]+)/
|
||||
@@ -83,88 +227,212 @@ class MarkdownSplitEditor extends React.Component {
|
||||
const checkReplace = /\[x]/i
|
||||
const uncheckReplace = /\[ ]/
|
||||
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||
const lines = this.refs.code.value
|
||||
.split('\n')
|
||||
const lineIndex =
|
||||
parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||
const lines = this.refs.code.value.split('\n')
|
||||
|
||||
const targetLine = lines[lineIndex]
|
||||
let newLine = targetLine
|
||||
|
||||
if (targetLine.match(checkedMatch)) {
|
||||
lines[lineIndex] = targetLine.replace(checkReplace, '[ ]')
|
||||
newLine = targetLine.replace(checkReplace, '[ ]')
|
||||
}
|
||||
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) {
|
||||
const rootRect = this.refs.root.getBoundingClientRect()
|
||||
const rootWidth = rootRect.width
|
||||
const offset = rootRect.left
|
||||
let newCodeEditorWidthInPercent = (e.pageX - offset) / rootWidth * 100
|
||||
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 (newCodeEditorWidthInPercent <= 10) {
|
||||
newCodeEditorWidthInPercent = 10
|
||||
// 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 offset = rootRect.left
|
||||
let newCodeEditorWidthInPercent = ((e.pageX - offset) / rootWidth) * 100
|
||||
|
||||
// limit minSize to 10%, maxSize to 90%
|
||||
if (newCodeEditorWidthInPercent <= 10) {
|
||||
newCodeEditorWidthInPercent = 10
|
||||
}
|
||||
|
||||
if (newCodeEditorWidthInPercent >= 90) {
|
||||
newCodeEditorWidthInPercent = 90
|
||||
}
|
||||
|
||||
this.setState({
|
||||
codeEditorWidthInPercent: newCodeEditorWidthInPercent
|
||||
})
|
||||
}
|
||||
|
||||
if (newCodeEditorWidthInPercent >= 90) {
|
||||
newCodeEditorWidthInPercent = 90
|
||||
}
|
||||
|
||||
this.setState({
|
||||
codeEditorWidthInPercent: newCodeEditorWidthInPercent
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseUp (e) {
|
||||
handleMouseUp(e) {
|
||||
e.preventDefault()
|
||||
this.setState({
|
||||
isSliderFocused: false
|
||||
})
|
||||
}
|
||||
|
||||
handleMouseDown (e) {
|
||||
handleMouseDown(e) {
|
||||
e.preventDefault()
|
||||
this.setState({
|
||||
isSliderFocused: true
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
const {config, value, storageKey, noteKey, linesHighlighted} = this.props
|
||||
const storage = findStorage(storageKey)
|
||||
scrollTo(from, to, scroller) {
|
||||
const distance = to - from
|
||||
const framerate = 1000 / 60
|
||||
const frames = 20
|
||||
const refractory = frames * framerate
|
||||
|
||||
this.userScroll = false
|
||||
|
||||
let frame = 0
|
||||
let scrollPos, time
|
||||
const timer = setInterval(() => {
|
||||
time = frame / frames
|
||||
scrollPos =
|
||||
time < 0.5
|
||||
? 2 * time * time // ease in
|
||||
: -1 + (4 - 2 * time) * time // ease out
|
||||
|
||||
scroller(from + scrollPos * distance)
|
||||
|
||||
if (frame >= frames) {
|
||||
clearInterval(timer)
|
||||
setTimeout(() => {
|
||||
this.userScroll = true
|
||||
}, refractory)
|
||||
}
|
||||
frame++
|
||||
}, framerate)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
config,
|
||||
value,
|
||||
storageKey,
|
||||
noteKey,
|
||||
linesHighlighted,
|
||||
getNote,
|
||||
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)
|
||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||
editorStyle.fontSize = editorFontSize
|
||||
|
||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||
const previewStyle = {}
|
||||
previewStyle.width = (100 - this.state.codeEditorWidthInPercent) + '%'
|
||||
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused) previewStyle.pointerEvents = 'none'
|
||||
if (!(editorStyle.fontSize > 0 && editorStyle.fontSize < 132))
|
||||
editorIndentSize = 4
|
||||
editorStyle.indentSize = editorIndentSize
|
||||
|
||||
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 (
|
||||
<div styleName='root' ref='root'
|
||||
<div
|
||||
styleName='root'
|
||||
ref='root'
|
||||
onMouseMove={e => this.handleMouseMove(e)}
|
||||
onMouseUp={e => this.handleMouseUp(e)}>
|
||||
onMouseUp={e => this.handleMouseUp(e)}
|
||||
>
|
||||
<CodeEditor
|
||||
ref='code'
|
||||
width={this.state.codeEditorWidthInPercent + '%'}
|
||||
width={editorStyle.width}
|
||||
height={editorStyle.height}
|
||||
mode='Boost Flavored Markdown'
|
||||
value={value}
|
||||
theme={config.editor.theme}
|
||||
keyMap={config.editor.keyMap}
|
||||
fontFamily={config.editor.fontFamily}
|
||||
fontSize={editorFontSize}
|
||||
fontSize={editorStyle.fontSize}
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
lineWrapping
|
||||
matchingPairs={config.editor.matchingPairs}
|
||||
matchingCloseBefore={config.editor.matchingCloseBefore}
|
||||
matchingTriples={config.editor.matchingTriples}
|
||||
explodingPairs={config.editor.explodingPairs}
|
||||
codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
|
||||
codeBlockMatchingCloseBefore={
|
||||
config.editor.codeBlockMatchingCloseBefore
|
||||
}
|
||||
codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
|
||||
codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
|
||||
indentType={config.editor.indentType}
|
||||
indentSize={editorIndentSize}
|
||||
indentSize={editorStyle.indentSize}
|
||||
enableRulers={config.editor.enableRulers}
|
||||
rulers={config.editor.rulers}
|
||||
scrollPastEnd={config.editor.scrollPastEnd}
|
||||
@@ -173,19 +441,28 @@ class MarkdownSplitEditor extends React.Component {
|
||||
storageKey={storageKey}
|
||||
noteKey={noteKey}
|
||||
linesHighlighted={linesHighlighted}
|
||||
onChange={(e) => this.handleOnChange(e)}
|
||||
onScroll={this.handleScroll.bind(this)}
|
||||
onChange={e => this.handleOnChange(e)}
|
||||
onScroll={e => this.handleEditorScroll(e)}
|
||||
onCursorActivity={e => this.handleCursorActivity(e)}
|
||||
spellCheck={config.editor.spellcheck}
|
||||
enableSmartPaste={config.editor.enableSmartPaste}
|
||||
hotkey={config.hotkey}
|
||||
switchPreview={config.editor.switchPreview}
|
||||
enableMarkdownLint={config.editor.enableMarkdownLint}
|
||||
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
||||
/>
|
||||
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
||||
dateFormatISO8601={config.editor.dateFormatISO8601}
|
||||
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
||||
RTL={RTL}
|
||||
/>
|
||||
<div
|
||||
styleName={isStacking ? 'slider-hoz' : 'slider'}
|
||||
style={{ left: sliderStyle.left, top: sliderStyle.top }}
|
||||
onMouseDown={e => this.handleMouseDown(e)}
|
||||
>
|
||||
<div styleName='slider-hitbox' />
|
||||
</div>
|
||||
<MarkdownPreview
|
||||
ref='preview'
|
||||
style={previewStyle}
|
||||
theme={config.ui.theme}
|
||||
keyMap={config.editor.keyMap}
|
||||
@@ -194,23 +471,27 @@ class MarkdownSplitEditor extends React.Component {
|
||||
codeBlockTheme={config.preview.codeBlockTheme}
|
||||
codeBlockFontFamily={config.editor.fontFamily}
|
||||
lineNumber={config.preview.lineNumber}
|
||||
indentSize={editorIndentSize}
|
||||
scrollPastEnd={config.preview.scrollPastEnd}
|
||||
smartQuotes={config.preview.smartQuotes}
|
||||
smartArrows={config.preview.smartArrows}
|
||||
breaks={config.preview.breaks}
|
||||
sanitize={config.preview.sanitize}
|
||||
ref='preview'
|
||||
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
|
||||
tabInde='0'
|
||||
value={value}
|
||||
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
||||
onScroll={this.handleScroll.bind(this)}
|
||||
onCheckboxClick={e => this.handleCheckboxClick(e)}
|
||||
onScroll={e => this.handlePreviewScroll(e)}
|
||||
showCopyNotification={config.ui.showCopyNotification}
|
||||
storagePath={storage.path}
|
||||
noteKey={noteKey}
|
||||
customCSS={config.preview.customCSS}
|
||||
allowCustomCSS={config.preview.allowCustomCSS}
|
||||
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||
/>
|
||||
getNote={getNote}
|
||||
export={config.export}
|
||||
RTL={RTL}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
height 100%
|
||||
font-size 30px
|
||||
display flex
|
||||
flex-wrap wrap
|
||||
.slider
|
||||
absolute top bottom
|
||||
top -2px
|
||||
@@ -15,23 +16,23 @@
|
||||
left -3px
|
||||
z-index 10
|
||||
cursor col-resize
|
||||
.slider-hoz
|
||||
absolute left right
|
||||
.slider-hitbox
|
||||
absolute left right
|
||||
width: 100%
|
||||
height 7px
|
||||
cursor row-resize
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
.slider
|
||||
border-left 1px solid $ui-dark-borderColor
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
.slider
|
||||
border-left 1px solid $ui-solarized-dark-borderColor
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.root
|
||||
.slider
|
||||
border-left 1px solid get-theme-var(theme, 'borderColor')
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.root
|
||||
.slider
|
||||
border-left 1px solid $ui-monokai-borderColor
|
||||
for theme in 'dark' 'dracula' 'solarized-dark'
|
||||
apply-theme(theme)
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
.slider
|
||||
border-left 1px solid $ui-dracula-borderColor
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
@@ -3,9 +3,7 @@ import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ModalEscButton.styl'
|
||||
|
||||
const ModalEscButton = ({
|
||||
handleEscButtonClick
|
||||
}) => (
|
||||
const ModalEscButton = ({ handleEscButtonClick }) => (
|
||||
<button styleName='escButton' onClick={handleEscButtonClick}>
|
||||
<div styleName='esc-mark'>×</div>
|
||||
<div>esc</div>
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
/**
|
||||
* @fileoverview Micro component for toggle SideNav
|
||||
*/
|
||||
* @fileoverview Micro component for toggle SideNav
|
||||
*/
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import styles from './NavToggleButton.styl'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
|
||||
/**
|
||||
* @param {boolean} isFolded
|
||||
* @param {Function} handleToggleButtonClick
|
||||
*/
|
||||
* @param {boolean} isFolded
|
||||
* @param {Function} handleToggleButtonClick
|
||||
*/
|
||||
|
||||
const NavToggleButton = ({isFolded, handleToggleButtonClick}) => (
|
||||
<button styleName='navToggle'
|
||||
onClick={(e) => handleToggleButtonClick(e)}
|
||||
>
|
||||
{isFolded
|
||||
? <i className='fa fa-angle-double-right fa-2x' />
|
||||
: <i className='fa fa-angle-double-left fa-2x' />
|
||||
}
|
||||
const NavToggleButton = ({ isFolded, handleToggleButtonClick }) => (
|
||||
<button styleName='navToggle' onClick={e => handleToggleButtonClick(e)}>
|
||||
{isFolded ? (
|
||||
<i className='fa fa-angle-double-right fa-2x' />
|
||||
) : (
|
||||
<i className='fa fa-angle-double-left fa-2x' />
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
@@ -17,10 +17,16 @@
|
||||
body[data-theme="white"]
|
||||
navWhiteButtonColor()
|
||||
|
||||
body[data-theme="dark"]
|
||||
.navToggle
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.navToggle:hover
|
||||
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 20%)
|
||||
border 1px solid get-theme-var(theme, 'button--active-backgroundColor')
|
||||
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,8 +3,9 @@
|
||||
*/
|
||||
import PropTypes from 'prop-types'
|
||||
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 { getTodoStatus } from 'browser/lib/getTodoStatus'
|
||||
import styles from './NoteItem.styl'
|
||||
@@ -21,7 +22,11 @@ const TagElement = ({ tagName, color }) => {
|
||||
const style = {}
|
||||
if (color) {
|
||||
style.backgroundColor = color
|
||||
style.color = invertColor(color, { black: '#222', white: '#f1f1f1', threshold: 0.3 })
|
||||
style.color = invertColor(color, {
|
||||
black: '#222',
|
||||
white: '#f1f1f1',
|
||||
threshold: 0.3
|
||||
})
|
||||
}
|
||||
return (
|
||||
<span styleName='item-bottom-tagList-item' key={tagName} style={style}>
|
||||
@@ -43,9 +48,13 @@ const TagElementList = (tags, showTagsAlphabetically, coloredTags) => {
|
||||
}
|
||||
|
||||
if (showTagsAlphabetically) {
|
||||
return _.sortBy(tags).map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
|
||||
return sortBy(tags).map(tag =>
|
||||
TagElement({ tagName: tag, color: coloredTags[tag] })
|
||||
)
|
||||
} else {
|
||||
return tags.map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
|
||||
return tags.map(tag =>
|
||||
TagElement({ tagName: tag, color: coloredTags[tag] })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,13 +91,17 @@ const NoteItem = ({
|
||||
draggable='true'
|
||||
>
|
||||
<div styleName='item-wrapper'>
|
||||
{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-file-text-o' />}
|
||||
{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-file-text-o' />
|
||||
)}
|
||||
<div styleName='item-title'>
|
||||
{note.title.trim().length > 0
|
||||
? note.title
|
||||
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>}
|
||||
{note.title.trim().length > 0 ? (
|
||||
<Emoji text={note.title} />
|
||||
) : (
|
||||
<span styleName='item-title-empty'>{i18n.__('Empty note')}</span>
|
||||
)}
|
||||
</div>
|
||||
<div styleName='item-middle'>
|
||||
<div styleName='item-middle-time'>{dateDisplay}</div>
|
||||
@@ -97,7 +110,9 @@ const NoteItem = ({
|
||||
title={
|
||||
viewType === 'ALL'
|
||||
? storageName
|
||||
: viewType === 'STORAGE' ? folderName : null
|
||||
: viewType === 'STORAGE'
|
||||
? folderName
|
||||
: null
|
||||
}
|
||||
styleName='item-middle-app-meta-label'
|
||||
>
|
||||
@@ -108,28 +123,36 @@ const NoteItem = ({
|
||||
</div>
|
||||
<div styleName='item-bottom'>
|
||||
<div styleName='item-bottom-tagList'>
|
||||
{note.tags.length > 0
|
||||
? TagElementList(note.tags, showTagsAlphabetically, coloredTags)
|
||||
: <span
|
||||
{note.tags.length > 0 ? (
|
||||
TagElementList(note.tags, showTagsAlphabetically, coloredTags)
|
||||
) : (
|
||||
<span
|
||||
style={{ fontStyle: 'italic', opacity: 0.5 }}
|
||||
styleName='item-bottom-tagList-empty'
|
||||
>
|
||||
>
|
||||
{i18n.__('No tags')}
|
||||
</span>}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
{note.isStarred
|
||||
? <img
|
||||
{note.isStarred ? (
|
||||
<img
|
||||
styleName='item-star'
|
||||
src='../resources/icon/icon-starred.svg'
|
||||
/>
|
||||
: ''}
|
||||
{note.isPinned && !pathname.match(/\/starred|\/trash/)
|
||||
? <i styleName='item-pin' className='fa fa-thumb-tack' />
|
||||
: ''}
|
||||
{note.type === 'MARKDOWN_NOTE'
|
||||
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
|
||||
: ''}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{note.isPinned && !pathname.match(/\/starred|\/trash/) ? (
|
||||
<i styleName='item-pin' className='fa fa-thumb-tack' />
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{note.type === 'MARKDOWN_NOTE' ? (
|
||||
<TodoProcess todoStatus={getTodoStatus(note.content)} />
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -194,7 +194,7 @@ body[data-theme="dark"]
|
||||
color $ui-dark-text-color
|
||||
.item-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha(#fff, 20%)
|
||||
background-color alpha($ui-dark-tagList-backgroundColor, 20%)
|
||||
color $ui-dark-text-color
|
||||
&:active
|
||||
transition 0.15s
|
||||
@@ -207,7 +207,7 @@ body[data-theme="dark"]
|
||||
color $ui-dark-text-color
|
||||
.item-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha(white, 10%)
|
||||
background-color alpha($ui-dark-tagList-backgroundColor, 10%)
|
||||
color $ui-dark-text-color
|
||||
|
||||
.item-wrapper
|
||||
@@ -223,13 +223,13 @@ body[data-theme="dark"]
|
||||
.item-bottom-time
|
||||
color $ui-dark-text-color
|
||||
.item-bottom-tagList-item
|
||||
background-color alpha(white, 10%)
|
||||
background-color alpha($ui-dark-tagList-backgroundColor, 10%)
|
||||
color $ui-dark-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||
color #c0392b
|
||||
color $ui-dark-button--hover-color
|
||||
.item-bottom-tagList-item
|
||||
background-color alpha(#fff, 20%)
|
||||
background-color alpha($ui-dark-tagList-backgroundColor, 20%)
|
||||
|
||||
.item-title
|
||||
color $ui-inactive-text-color
|
||||
@@ -322,148 +322,82 @@ body[data-theme="solarized-dark"]
|
||||
color $ui-inactive-text-color
|
||||
vertical-align middle
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.root
|
||||
border-color $ui-monokai-borderColor
|
||||
background-color $ui-monokai-noteList-backgroundColor
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.root
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||
|
||||
.item
|
||||
border-color $ui-monokai-borderColor
|
||||
background-color $ui-monokai-noteList-backgroundColor
|
||||
&:hover
|
||||
transition 0.15s
|
||||
// background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
|
||||
color $ui-monokai-text-color
|
||||
.item-title
|
||||
.item-title-icon
|
||||
.item-bottom-time
|
||||
.item
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||
&:hover
|
||||
transition 0.15s
|
||||
color $ui-monokai-text-color
|
||||
.item-bottom-tagList-item
|
||||
// background-color alpha(get-theme-var(theme, 'noteList-backgroundColor'), 20%)
|
||||
color get-theme-var(theme, 'text-color')
|
||||
.item-title
|
||||
.item-title-icon
|
||||
.item-bottom-time
|
||||
transition 0.15s
|
||||
color get-theme-var(theme, 'text-color')
|
||||
.item-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha(get-theme-var(theme, 'noteList-backgroundColor'), 20%)
|
||||
color get-theme-var(theme, 'text-color')
|
||||
&:active
|
||||
transition 0.15s
|
||||
background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
|
||||
color $ui-monokai-text-color
|
||||
&:active
|
||||
transition 0.15s
|
||||
background-color $ui-monokai-noteList-backgroundColor
|
||||
color $ui-monokai-text-color
|
||||
.item-title
|
||||
.item-title-icon
|
||||
.item-bottom-time
|
||||
transition 0.15s
|
||||
color $ui-monokai-text-color
|
||||
.item-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha($ui-monokai-noteList-backgroundColor, 10%)
|
||||
color $ui-monokai-text-color
|
||||
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||
color get-theme-var(theme, 'text-color')
|
||||
.item-title
|
||||
.item-title-icon
|
||||
.item-bottom-time
|
||||
transition 0.15s
|
||||
color get-theme-var(theme, 'text-color')
|
||||
.item-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha(get-theme-var(theme, 'noteList-backgroundColor'), 10%)
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
.item-wrapper
|
||||
border-color alpha($ui-monokai-button-backgroundColor, 60%)
|
||||
|
||||
.item--active
|
||||
border-color $ui-monokai-borderColor
|
||||
background-color $ui-monokai-button-backgroundColor
|
||||
.item-wrapper
|
||||
border-color transparent
|
||||
.item-title
|
||||
.item-title-icon
|
||||
.item-bottom-time
|
||||
color $ui-monokai-active-color
|
||||
.item-bottom-tagList-item
|
||||
background-color alpha(white, 10%)
|
||||
color $ui-monokai-text-color
|
||||
&:hover
|
||||
// background-color alpha($ui-monokai-button--active-backgroundColor, 60%)
|
||||
color #f92672
|
||||
.item-bottom-tagList-item
|
||||
background-color alpha(#fff, 20%)
|
||||
border-color alpha(get-theme-var(theme, 'button-backgroundColor'), 60%)
|
||||
|
||||
.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
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
border-color $ui-dracula-borderColor
|
||||
background-color $ui-dracula-noteList-backgroundColor
|
||||
|
||||
.item
|
||||
border-color $ui-dracula-borderColor
|
||||
background-color $ui-dracula-noteList-backgroundColor
|
||||
&:hover
|
||||
transition 0.15s
|
||||
// background-color alpha($ui-dracula-noteList-backgroundColor, 20%)
|
||||
color $ui-dracula-text-color
|
||||
.item--active
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
.item-wrapper
|
||||
border-color transparent
|
||||
.item-title
|
||||
.item-title-icon
|
||||
.item-bottom-time
|
||||
transition 0.15s
|
||||
color $ui-dracula-text-color
|
||||
color get-theme-var(theme, 'active-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
|
||||
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 10%)
|
||||
color get-theme-var(theme, 'text-color')
|
||||
&:hover
|
||||
// background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 60%)
|
||||
color get-theme-var(theme, 'button--hover-color')
|
||||
.item-bottom-tagList-item
|
||||
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 20%)
|
||||
|
||||
.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
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.item-title-icon
|
||||
.item-bottom-time
|
||||
color $ui-dracula-active-color
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.item-title-empty
|
||||
color $ui-inactive-text-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%)
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.item-title
|
||||
color $ui-inactive-text-color
|
||||
.item-bottom-tagList-empty
|
||||
color $ui-inactive-text-color
|
||||
vertical-align middle
|
||||
|
||||
.item-title-icon
|
||||
color $ui-inactive-text-color
|
||||
for theme in 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
.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
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
@@ -25,10 +25,8 @@ const NoteItemSimple = ({
|
||||
pathname,
|
||||
storage
|
||||
}) => (
|
||||
<div styleName={isActive
|
||||
? 'item-simple--active'
|
||||
: 'item-simple'
|
||||
}
|
||||
<div
|
||||
styleName={isActive ? 'item-simple--active' : 'item-simple'}
|
||||
key={note.key}
|
||||
onClick={e => handleNoteClick(e, note.key)}
|
||||
onContextMenu={e => handleNoteContextMenu(e, note.key)}
|
||||
@@ -36,23 +34,29 @@ const NoteItemSimple = ({
|
||||
draggable='true'
|
||||
>
|
||||
<div styleName='item-simple-title'>
|
||||
{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-file-text-o' />
|
||||
}
|
||||
{note.isPinned && !pathname.match(/\/starred|\/trash/)
|
||||
? <i styleName='item-pin' className='fa fa-thumb-tack' />
|
||||
: ''
|
||||
}
|
||||
{note.title.trim().length > 0
|
||||
? note.title
|
||||
: <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>}
|
||||
{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-file-text-o'
|
||||
/>
|
||||
)}
|
||||
{note.isPinned && !pathname.match(/\/starred|\/trash/) ? (
|
||||
<i styleName='item-pin' className='fa fa-thumb-tack' />
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{note.title.trim().length > 0 ? (
|
||||
note.title
|
||||
) : (
|
||||
<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>
|
||||
)
|
||||
|
||||
@@ -223,130 +223,73 @@ body[data-theme="solarized-dark"]
|
||||
padding-left 4px
|
||||
opacity 0.4
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.root
|
||||
border-color $ui-monokai-borderColor
|
||||
background-color $ui-monokai-noteList-backgroundColor
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.root
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||
|
||||
.item-simple
|
||||
border-color $ui-monokai-borderColor
|
||||
background-color $ui-monokai-noteList-backgroundColor
|
||||
&:hover
|
||||
transition 0.15s
|
||||
background-color alpha($ui-monokai-button-backgroundColor, 60%)
|
||||
color $ui-monokai-text-color
|
||||
.item-simple
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||
&:hover
|
||||
transition 0.15s
|
||||
background-color alpha(get-theme-var(theme, 'button-backgroundColor'), 60%)
|
||||
color get-theme-var(theme, 'text-color')
|
||||
.item-simple-title
|
||||
.item-simple-title-empty
|
||||
.item-simple-title-icon
|
||||
.item-simple-bottom-time
|
||||
transition 0.15s
|
||||
color get-theme-var(theme, 'text-color')
|
||||
.item-simple-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 20%)
|
||||
color get-theme-var(theme, 'text-color')
|
||||
&:active
|
||||
transition 0.15s
|
||||
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||
color get-theme-var(theme, 'text-color')
|
||||
.item-simple-title
|
||||
.item-simple-title-empty
|
||||
.item-simple-title-icon
|
||||
.item-simple-bottom-time
|
||||
transition 0.15s
|
||||
color get-theme-var(theme, 'text-color')
|
||||
.item-simple-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 10%)
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
.item-simple--active
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||
.item-simple-wrapper
|
||||
border-color transparent
|
||||
.item-simple-title
|
||||
.item-simple-title-empty
|
||||
.item-simple-title-icon
|
||||
.item-simple-bottom-time
|
||||
transition 0.15s
|
||||
color $ui-monokai-text-color
|
||||
color get-theme-var(theme, 'text-color')
|
||||
.item-simple-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha(#fff, 20%)
|
||||
color $ui-monokai-text-color
|
||||
&:active
|
||||
transition 0.15s
|
||||
background-color $ui-monokai-button--active-backgroundColor
|
||||
color $ui-monokai-text-color
|
||||
.item-simple-title
|
||||
.item-simple-title-empty
|
||||
.item-simple-title-icon
|
||||
.item-simple-bottom-time
|
||||
transition 0.15s
|
||||
color $ui-monokai-text-color
|
||||
.item-simple-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha(white, 10%)
|
||||
color $ui-monokai-text-color
|
||||
|
||||
.item-simple--active
|
||||
border-color $ui-monokai-borderColor
|
||||
background-color $ui-monokai-button--active-backgroundColor
|
||||
.item-simple-wrapper
|
||||
border-color transparent
|
||||
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 10%)
|
||||
color get-theme-var(theme, 'text-color')
|
||||
&:hover
|
||||
// background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 60%)
|
||||
color #c0392b
|
||||
.item-simple-bottom-tagList-item
|
||||
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 20%)
|
||||
.item-simple-title
|
||||
.item-simple-title-empty
|
||||
.item-simple-title-icon
|
||||
.item-simple-bottom-time
|
||||
color $ui-monokai-text-color
|
||||
.item-simple-bottom-tagList-item
|
||||
background-color alpha(white, 10%)
|
||||
color $ui-monokai-text-color
|
||||
&:hover
|
||||
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||
color #c0392b
|
||||
.item-simple-bottom-tagList-item
|
||||
background-color alpha(#fff, 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
|
||||
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
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
border-color $ui-dracula-borderColor
|
||||
background-color $ui-dracula-noteList-backgroundColor
|
||||
for theme in 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
.item-simple
|
||||
border-color $ui-dracula-borderColor
|
||||
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
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
@@ -6,7 +6,7 @@ const electron = require('electron')
|
||||
const { shell } = electron
|
||||
|
||||
class RealtimeNotification extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -14,38 +14,46 @@ class RealtimeNotification extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.fetchNotifications()
|
||||
}
|
||||
|
||||
fetchNotifications () {
|
||||
const notificationsUrl = 'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
|
||||
fetchNotifications() {
|
||||
const notificationsUrl =
|
||||
'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
|
||||
fetch(notificationsUrl)
|
||||
.then(response => {
|
||||
return response.json()
|
||||
})
|
||||
.then(json => {
|
||||
this.setState({notifications: json.notifications})
|
||||
this.setState({ notifications: json.notifications })
|
||||
})
|
||||
}
|
||||
|
||||
handleLinkClick (e) {
|
||||
handleLinkClick(e) {
|
||||
shell.openExternal(e.currentTarget.href)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { notifications } = this.state
|
||||
const link = notifications.length > 0
|
||||
? <a styleName='notification-link' href={notifications[0].linkUrl}
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>
|
||||
Info: {notifications[0].text}
|
||||
</a>
|
||||
: ''
|
||||
const link =
|
||||
notifications.length > 0 ? (
|
||||
<a
|
||||
styleName='notification-link'
|
||||
href={notifications[0].linkUrl}
|
||||
onClick={e => this.handleLinkClick(e)}
|
||||
>
|
||||
Info: {notifications[0].text}
|
||||
</a>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
|
||||
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
|
||||
color #5CB85C
|
||||
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.notification-area
|
||||
background-color none
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.notification-area
|
||||
background-color none
|
||||
.notification-link
|
||||
color get-theme-var(theme, 'text-color')
|
||||
border none
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
&:hover
|
||||
color get-theme-var(theme, 'button--hover-color')
|
||||
|
||||
.notification-link
|
||||
color $ui-solarized-dark-text-color
|
||||
border none
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
&:hover
|
||||
color #5CB85C
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.notification-area
|
||||
background-color none
|
||||
|
||||
.notification-link
|
||||
color $ui-monokai-text-color
|
||||
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
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
@@ -16,54 +16,70 @@ import i18n from 'browser/lib/i18n'
|
||||
* @return {React.Component}
|
||||
*/
|
||||
const SideNavFilter = ({
|
||||
isFolded, isHomeActive, handleAllNotesButtonClick,
|
||||
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote,
|
||||
counterTotalNote, counterStarredNote, handleFilterButtonContextMenu
|
||||
isFolded,
|
||||
isHomeActive,
|
||||
handleAllNotesButtonClick,
|
||||
isStarredActive,
|
||||
handleStarredButtonClick,
|
||||
isTrashedActive,
|
||||
handleTrashedButtonClick,
|
||||
counterDelNote,
|
||||
counterTotalNote,
|
||||
counterStarredNote,
|
||||
handleFilterButtonContextMenu
|
||||
}) => (
|
||||
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
|
||||
|
||||
<button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
|
||||
<button
|
||||
styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
|
||||
onClick={handleAllNotesButtonClick}
|
||||
>
|
||||
<div styleName='iconWrap'>
|
||||
<img src={isHomeActive
|
||||
? '../resources/icon/icon-all-active.svg'
|
||||
: '../resources/icon/icon-all.svg'
|
||||
}
|
||||
<img
|
||||
src={
|
||||
isHomeActive
|
||||
? '../resources/icon/icon-all-active.svg'
|
||||
: '../resources/icon/icon-all.svg'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<span styleName='menu-button-label'>{i18n.__('All Notes')}</span>
|
||||
<span styleName='counters'>{counterTotalNote}</span>
|
||||
</button>
|
||||
|
||||
<button styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
|
||||
<button
|
||||
styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
|
||||
onClick={handleStarredButtonClick}
|
||||
>
|
||||
<div styleName='iconWrap'>
|
||||
<img src={isStarredActive
|
||||
? '../resources/icon/icon-star-active.svg'
|
||||
: '../resources/icon/icon-star-sidenav.svg'
|
||||
}
|
||||
<img
|
||||
src={
|
||||
isStarredActive
|
||||
? '../resources/icon/icon-star-active.svg'
|
||||
: '../resources/icon/icon-star-sidenav.svg'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<span styleName='menu-button-label'>{i18n.__('Starred')}</span>
|
||||
<span styleName='counters'>{counterStarredNote}</span>
|
||||
</button>
|
||||
|
||||
<button styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
|
||||
onClick={handleTrashedButtonClick} onContextMenu={handleFilterButtonContextMenu}
|
||||
<button
|
||||
styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
|
||||
onClick={handleTrashedButtonClick}
|
||||
onContextMenu={handleFilterButtonContextMenu}
|
||||
>
|
||||
<div styleName='iconWrap'>
|
||||
<img src={isTrashedActive
|
||||
? '../resources/icon/icon-trash-active.svg'
|
||||
: '../resources/icon/icon-trash-sidenav.svg'
|
||||
}
|
||||
<img
|
||||
src={
|
||||
isTrashedActive
|
||||
? '../resources/icon/icon-trash-active.svg'
|
||||
: '../resources/icon/icon-trash-sidenav.svg'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<span styleName='menu-button-label'>{i18n.__('Trash')}</span>
|
||||
<span styleName='counters'>{counterDelNote}</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.menu
|
||||
margin-bottom 30px
|
||||
margin-bottom 20px
|
||||
|
||||
.menu-button
|
||||
navButtonColor()
|
||||
@@ -180,129 +180,51 @@ body[data-theme="dark"]
|
||||
.menu-button-label
|
||||
color $ui-dark-text-color
|
||||
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.menu-button
|
||||
&:active
|
||||
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||
color get-theme-var(theme, 'text-color')
|
||||
&:hover
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.menu-button
|
||||
&:active
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
&:hover
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.menu-button--active
|
||||
color $ui-solarized-dark-text-color
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
.menu-button-label
|
||||
color $ui-solarized-dark-text-color
|
||||
&:hover
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
.menu-button--active
|
||||
color get-theme-var(theme, 'text-color')
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
.menu-button-label
|
||||
color $ui-solarized-dark-text-color
|
||||
color get-theme-var(theme, 'text-color')
|
||||
&:hover
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
color get-theme-var(theme, 'text-color')
|
||||
.menu-button-label
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
.menu-button-star--active
|
||||
color $ui-solarized-dark-text-color
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
.menu-button-label
|
||||
color $ui-solarized-dark-text-color
|
||||
&:hover
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
.menu-button-star--active
|
||||
color get-theme-var(theme, 'text-color')
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
.menu-button-label
|
||||
color $ui-solarized-dark-text-color
|
||||
color get-theme-var(theme, 'text-color')
|
||||
&:hover
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
color get-theme-var(theme, 'text-color')
|
||||
.menu-button-label
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
.menu-button-trash--active
|
||||
color $ui-solarized-dark-text-color
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
.menu-button-label
|
||||
color $ui-solarized-dark-text-color
|
||||
&:hover
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
.menu-button-trash--active
|
||||
color get-theme-var(theme, 'text-color')
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
.menu-button-label
|
||||
color $ui-solarized-dark-text-color
|
||||
color get-theme-var(theme, 'text-color')
|
||||
&:hover
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
color get-theme-var(theme, 'text-color')
|
||||
.menu-button-label
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.menu-button
|
||||
&:active
|
||||
background-color $ui-monokai-noteList-backgroundColor
|
||||
color $ui-monokai-text-color
|
||||
&:hover
|
||||
background-color $ui-monokai-button-backgroundColor
|
||||
color $ui-monokai-text-color
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
.menu-button--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-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
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
@@ -5,7 +5,7 @@ import context from 'browser/lib/context'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class SnippetTab extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -14,7 +14,7 @@ class SnippetTab extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUpdate (nextProps) {
|
||||
componentWillUpdate(nextProps) {
|
||||
if (nextProps.snippet.name !== this.props.snippet.name) {
|
||||
this.setState({
|
||||
name: nextProps.snippet.name
|
||||
@@ -22,34 +22,34 @@ class SnippetTab extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleClick (e) {
|
||||
handleClick(e) {
|
||||
this.props.onClick(e)
|
||||
}
|
||||
|
||||
handleContextMenu (e) {
|
||||
handleContextMenu(e) {
|
||||
context.popup([
|
||||
{
|
||||
label: i18n.__('Rename'),
|
||||
click: (e) => this.handleRenameClick(e)
|
||||
click: e => this.handleRenameClick(e)
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
handleRenameClick (e) {
|
||||
handleRenameClick(e) {
|
||||
this.startRenaming()
|
||||
}
|
||||
|
||||
handleNameInputBlur (e) {
|
||||
handleNameInputBlur(e) {
|
||||
this.handleRename()
|
||||
}
|
||||
|
||||
handleNameInputChange (e) {
|
||||
handleNameInputChange(e) {
|
||||
this.setState({
|
||||
name: e.target.value
|
||||
})
|
||||
}
|
||||
|
||||
handleNameInputKeyDown (e) {
|
||||
handleNameInputKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
case 13:
|
||||
this.handleRename()
|
||||
@@ -63,84 +63,87 @@ class SnippetTab extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleRename () {
|
||||
this.setState({
|
||||
isRenaming: false
|
||||
}, () => {
|
||||
if (this.props.snippet.name !== this.state.name) {
|
||||
this.props.onRename(this.state.name)
|
||||
handleRename() {
|
||||
this.setState(
|
||||
{
|
||||
isRenaming: false
|
||||
},
|
||||
() => {
|
||||
if (this.props.snippet.name !== this.state.name) {
|
||||
this.props.onRename(this.state.name)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
handleDeleteButtonClick (e) {
|
||||
handleDeleteButtonClick(e) {
|
||||
this.props.onDelete(e)
|
||||
}
|
||||
|
||||
startRenaming () {
|
||||
this.setState({
|
||||
isRenaming: true
|
||||
}, () => {
|
||||
this.refs.name.focus()
|
||||
this.refs.name.select()
|
||||
})
|
||||
startRenaming() {
|
||||
this.setState(
|
||||
{
|
||||
isRenaming: true
|
||||
},
|
||||
() => {
|
||||
this.refs.name.focus()
|
||||
this.refs.name.select()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
handleDragStart (e) {
|
||||
handleDragStart(e) {
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
this.props.onDragStart(e)
|
||||
}
|
||||
|
||||
handleDrop (e) {
|
||||
handleDrop(e) {
|
||||
this.props.onDrop(e)
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { isActive, snippet, isDeletable } = this.props
|
||||
return (
|
||||
<div styleName={isActive
|
||||
? 'root--active'
|
||||
: 'root'
|
||||
}
|
||||
>
|
||||
{!this.state.isRenaming
|
||||
? <button styleName='button'
|
||||
onClick={(e) => this.handleClick(e)}
|
||||
onDoubleClick={(e) => this.handleRenameClick(e)}
|
||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||
onDragStart={(e) => this.handleDragStart(e)}
|
||||
onDrop={(e) => this.handleDrop(e)}
|
||||
<div styleName={isActive ? 'root--active' : 'root'}>
|
||||
{!this.state.isRenaming ? (
|
||||
<button
|
||||
styleName='button'
|
||||
onClick={e => this.handleClick(e)}
|
||||
onDoubleClick={e => this.handleRenameClick(e)}
|
||||
onContextMenu={e => this.handleContextMenu(e)}
|
||||
onDragStart={e => this.handleDragStart(e)}
|
||||
onDrop={e => this.handleDrop(e)}
|
||||
draggable='true'
|
||||
>
|
||||
{snippet.name.trim().length > 0
|
||||
? snippet.name
|
||||
: <span>
|
||||
{i18n.__('Unnamed')}
|
||||
</span>
|
||||
}
|
||||
{snippet.name.trim().length > 0 ? (
|
||||
snippet.name
|
||||
) : (
|
||||
<span>{i18n.__('Unnamed')}</span>
|
||||
)}
|
||||
</button>
|
||||
: <input styleName='input'
|
||||
) : (
|
||||
<input
|
||||
styleName='input'
|
||||
ref='name'
|
||||
value={this.state.name}
|
||||
onChange={(e) => this.handleNameInputChange(e)}
|
||||
onBlur={(e) => this.handleNameInputBlur(e)}
|
||||
onKeyDown={(e) => this.handleNameInputKeyDown(e)}
|
||||
onChange={e => this.handleNameInputChange(e)}
|
||||
onBlur={e => this.handleNameInputBlur(e)}
|
||||
onKeyDown={e => this.handleNameInputKeyDown(e)}
|
||||
/>
|
||||
}
|
||||
{isDeletable &&
|
||||
<button styleName='deleteButton'
|
||||
onClick={(e) => this.handleDeleteButtonClick(e)}
|
||||
)}
|
||||
{isDeletable && (
|
||||
<button
|
||||
styleName='deleteButton'
|
||||
onClick={e => this.handleDeleteButtonClick(e)}
|
||||
>
|
||||
<i className='fa fa-times' />
|
||||
</button>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SnippetTab.propTypes = {
|
||||
|
||||
}
|
||||
SnippetTab.propTypes = {}
|
||||
|
||||
export default CSSModules(SnippetTab, styles)
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
width 100%
|
||||
outline none
|
||||
|
||||
body[data-theme="default"], body[data-theme="white"]
|
||||
body[data-theme="default"], body[data-theme="white"]
|
||||
.root--active
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||
@@ -100,103 +100,43 @@ body[data-theme="dark"]
|
||||
color $ui-dark-text-color
|
||||
transition 0.15s
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
&:hover
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
transition 0.15s
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.root
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
&:hover
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
transition 0.15s
|
||||
.deleteButton
|
||||
color get-theme-var(theme, 'text-color')
|
||||
transition 0.15s
|
||||
.button
|
||||
color get-theme-var(theme, 'text-color')
|
||||
transition 0.15s
|
||||
|
||||
.root--active
|
||||
color get-theme-var(theme, 'active-color')
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
.deleteButton
|
||||
color $ui-solarized-dark-button--active-color
|
||||
transition 0.15s
|
||||
color get-theme-var(theme, 'text-color')
|
||||
.button
|
||||
color $ui-solarized-dark-button--active-color
|
||||
transition 0.15s
|
||||
color get-theme-var(theme, 'active-color')
|
||||
|
||||
.root--active
|
||||
color $ui-solarized-dark-button--active-color
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
.deleteButton
|
||||
color $ui-solarized-dark-button--active-color
|
||||
.button
|
||||
color $ui-solarized-dark-button--active-color
|
||||
border none
|
||||
color $ui-inactive-text-color
|
||||
background-color transparent
|
||||
transition color background-color 0.15s
|
||||
border-left 4px solid transparent
|
||||
|
||||
.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
|
||||
.input
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
color get-theme-var(theme, 'text-color')
|
||||
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
|
||||
border none
|
||||
color $ui-inactive-text-color
|
||||
background-color transparent
|
||||
transition color background-color 0.15s
|
||||
border-left 4px solid transparent
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
.input
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
color $ui-monokai-text-color
|
||||
transition 0.15s
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
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
|
||||
color $ui-dracula-text-color
|
||||
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
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
@@ -21,7 +21,9 @@ const FolderIcon = ({ className, color, isActive }) => {
|
||||
|
||||
/**
|
||||
* @param {boolean} isActive
|
||||
* @param {object} tooltipRef,
|
||||
* @param {Function} handleButtonClick
|
||||
* @param {Function} handleMouseEnter
|
||||
* @param {Function} handleContextMenu
|
||||
* @param {string} folderName
|
||||
* @param {string} folderColor
|
||||
@@ -35,7 +37,9 @@ const FolderIcon = ({ className, color, isActive }) => {
|
||||
const StorageItem = ({
|
||||
styles,
|
||||
isActive,
|
||||
tooltipRef,
|
||||
handleButtonClick,
|
||||
handleMouseEnter,
|
||||
handleContextMenu,
|
||||
folderName,
|
||||
folderColor,
|
||||
@@ -49,13 +53,15 @@ const StorageItem = ({
|
||||
<button
|
||||
styleName={isActive ? 'folderList-item--active' : 'folderList-item'}
|
||||
onClick={handleButtonClick}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onContextMenu={handleContextMenu}
|
||||
onDrop={handleDrop}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragLeave={handleDragLeave}
|
||||
>
|
||||
{!isFolded &&
|
||||
<DraggableIcon className={styles['folderList-item-reorder']} />}
|
||||
{!isFolded && (
|
||||
<DraggableIcon className={styles['folderList-item-reorder']} />
|
||||
)}
|
||||
<span
|
||||
styleName={
|
||||
isFolded ? 'folderList-item-name--folded' : 'folderList-item-name'
|
||||
@@ -70,18 +76,23 @@ const StorageItem = ({
|
||||
? _.truncate(folderName, { length: 1, omission: '' })
|
||||
: folderName}
|
||||
</span>
|
||||
{!isFolded &&
|
||||
_.isNumber(noteCount) &&
|
||||
<span styleName='folderList-item-noteCount'>{noteCount}</span>}
|
||||
{isFolded &&
|
||||
<span styleName='folderList-item-tooltip'>{folderName}</span>}
|
||||
{!isFolded && _.isNumber(noteCount) && (
|
||||
<span styleName='folderList-item-noteCount'>{noteCount}</span>
|
||||
)}
|
||||
{isFolded && (
|
||||
<span styleName='folderList-item-tooltip' ref={tooltipRef}>
|
||||
{folderName}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
StorageItem.propTypes = {
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
tooltipRef: PropTypes.object,
|
||||
handleButtonClick: PropTypes.func,
|
||||
handleMouseEnter: PropTypes.func,
|
||||
handleContextMenu: PropTypes.func,
|
||||
folderName: PropTypes.string.isRequired,
|
||||
folderColor: PropTypes.string,
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
border-bottom-right-radius 2px
|
||||
height 34px
|
||||
line-height 32px
|
||||
transition-property opacity
|
||||
|
||||
.folderList-item:hover, .folderList-item--active:hover
|
||||
.folderList-item-tooltip
|
||||
@@ -120,59 +121,28 @@ body[data-theme="dark"]
|
||||
color $ui-dark-text-color
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.folderList-item
|
||||
&:hover
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
&:active
|
||||
color $ui-solarized-dark-text-color
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.folderList-item
|
||||
&:hover
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
color get-theme-var(theme, 'text-color')
|
||||
&:active
|
||||
color get-theme-var(theme, 'text-color')
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
|
||||
.folderList-item--active
|
||||
@extend .folderList-item
|
||||
color $ui-solarized-dark-text-color
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
&:active
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
&:hover
|
||||
color $ui-solarized-dark-text-color
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
.folderList-item--active
|
||||
@extend .folderList-item
|
||||
color get-theme-var(theme, 'text-color')
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
&:active
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
&:hover
|
||||
color get-theme-var(theme, 'text-color')
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.folderList-item
|
||||
&:hover
|
||||
background-color $ui-monokai-button-backgroundColor
|
||||
color $ui-monokai-text-color
|
||||
&:active
|
||||
color $ui-monokai-text-color
|
||||
background-color $ui-monokai-button-backgroundColor
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
.folderList-item--active
|
||||
@extend .folderList-item
|
||||
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
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
@@ -1,18 +1,20 @@
|
||||
/**
|
||||
* @fileoverview Micro component for showing StorageList
|
||||
*/
|
||||
* @fileoverview Micro component for showing StorageList
|
||||
*/
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import styles from './StorageList.styl'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
|
||||
/**
|
||||
* @param {Array} storageList
|
||||
*/
|
||||
* @param {Array} storageList
|
||||
*/
|
||||
|
||||
const StorageList = ({storageList, isFolded}) => (
|
||||
const StorageList = ({ storageList, isFolded }) => (
|
||||
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
|
||||
{storageList.length > 0 ? storageList : (
|
||||
{storageList.length > 0 ? (
|
||||
storageList
|
||||
) : (
|
||||
<div styleName='storageList-empty'>No storage mount.</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,30 +1,58 @@
|
||||
/**
|
||||
* @fileoverview Micro component for showing TagList.
|
||||
*/
|
||||
* @fileoverview Micro component for showing TagList.
|
||||
*/
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import styles from './TagListItem.styl'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {Function} handleClickTagListItem
|
||||
* @param {Function} handleClickNarrowToTag
|
||||
* @param {boolean} isActive
|
||||
* @param {boolean} isRelated
|
||||
* @param {string} bgColor tab backgroundColor
|
||||
*/
|
||||
* @param {string} name
|
||||
* @param {Function} handleClickTagListItem
|
||||
* @param {Function} handleClickNarrowToTag
|
||||
* @param {boolean} isActive
|
||||
* @param {boolean} isRelated
|
||||
* @param {string} bgColor tab backgroundColor
|
||||
*/
|
||||
|
||||
const TagListItem = ({name, handleClickTagListItem, 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)}>
|
||||
const TagListItem = ({
|
||||
name,
|
||||
handleClickTagListItem,
|
||||
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'} />
|
||||
</button>
|
||||
: <div styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} />
|
||||
}
|
||||
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
|
||||
<span styleName='tagList-item-color' style={{backgroundColor: color || 'transparent'}} />
|
||||
) : (
|
||||
<div
|
||||
styleName={
|
||||
isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
styleName={isActive ? 'tagList-item-active' : 'tagList-item'}
|
||||
onClick={() => handleClickTagListItem(name)}
|
||||
>
|
||||
<span
|
||||
styleName='tagList-item-color'
|
||||
style={{ backgroundColor: color || 'transparent' }}
|
||||
/>
|
||||
<span styleName='tagList-item-name'>
|
||||
{`# ${name}`}
|
||||
<span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span>
|
||||
|
||||
@@ -94,23 +94,30 @@ body[data-theme="white"]
|
||||
.tagList-item-count
|
||||
color $ui-text-color
|
||||
|
||||
body[data-theme="dark"]
|
||||
.tagList-item
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
color $ui-dark-text-color
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
&:active
|
||||
color $ui-dark-text-color
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.tagList-item
|
||||
color get-theme-var(theme, 'inactive-text-color')
|
||||
&:hover
|
||||
color get-theme-var(theme, 'text-color')
|
||||
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 20%)
|
||||
&:active
|
||||
color get-theme-var(theme, 'text-color')
|
||||
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||
|
||||
.tagList-item-active
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
color $ui-dark-text-color
|
||||
&:active
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||
&:hover
|
||||
color $ui-dark-text-color
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||
.tagList-item-count
|
||||
color $ui-dark-button--active-color
|
||||
.tagList-item-active
|
||||
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||
color get-theme-var(theme, 'text-color')
|
||||
&:active
|
||||
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 50%)
|
||||
&:hover
|
||||
color get-theme-var(theme, 'text-color')
|
||||
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 50%)
|
||||
.tagList-item-count
|
||||
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
|
||||
*/
|
||||
|
||||
const TodoListPercentage = ({
|
||||
percentageOfTodo, onClearCheckboxClick
|
||||
}) => (
|
||||
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
|
||||
<div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}>
|
||||
const TodoListPercentage = ({ percentageOfTodo, onClearCheckboxClick }) => (
|
||||
<div
|
||||
styleName='percentageBar'
|
||||
style={{ display: isNaN(percentageOfTodo) ? 'none' : '' }}
|
||||
>
|
||||
<div styleName='progressBar' style={{ width: `${percentageOfTodo}%` }}>
|
||||
<div styleName='progressBarInner'>
|
||||
<p styleName='percentageText'>{percentageOfTodo}%</p>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='todoClear'>
|
||||
<p styleName='todoClearText' onClick={(e) => onClearCheckboxClick(e)}>clear</p>
|
||||
<p styleName='todoClearText' onClick={e => onClearCheckboxClick(e)}>
|
||||
clear
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -54,7 +54,7 @@ body[data-theme="dark"]
|
||||
|
||||
.percentageText
|
||||
color $ui-dark-text-color
|
||||
|
||||
|
||||
.todoClearText
|
||||
color $ui-dark-text-color
|
||||
|
||||
@@ -71,25 +71,19 @@ body[data-theme="solarized-dark"]
|
||||
.todoClearText
|
||||
color #fdf6e3
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.percentageBar
|
||||
background-color: $ui-monokai-borderColor
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.percentageBar
|
||||
background-color: get-theme-var(theme, 'borderColor')
|
||||
|
||||
.progressBar
|
||||
background-color $ui-monokai-active-color
|
||||
.progressBar
|
||||
background-color get-theme-var(theme, 'active-color')
|
||||
|
||||
.percentageText
|
||||
color $ui-monokai-text-color
|
||||
.percentageText
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.percentageBar
|
||||
background-color $ui-dracula-borderColor
|
||||
for theme in 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
.progressBar
|
||||
background-color: $ui-dracula-active-color
|
||||
|
||||
.percentageText
|
||||
color $ui-dracula-text-color
|
||||
|
||||
.percentageText
|
||||
color $ui-dracula-text-color
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
@@ -8,18 +8,21 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './TodoProcess.styl'
|
||||
|
||||
const TodoProcess = ({
|
||||
todoStatus: {
|
||||
total: totalTodo,
|
||||
completed: completedTodo
|
||||
}
|
||||
todoStatus: { 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'>
|
||||
<i className='fa fa-fw fa-check-square-o' />
|
||||
{completedTodo} of {totalTodo}
|
||||
</div>
|
||||
<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>
|
||||
)
|
||||
|
||||
@@ -124,40 +124,34 @@ hr
|
||||
border-bottom solid 1px borderColor
|
||||
margin 15px 0
|
||||
h1, h2, h3, h4, h5, h6
|
||||
margin 1em 0 1.5em
|
||||
line-height 1.4
|
||||
font-weight bold
|
||||
word-wrap break-word
|
||||
padding .2em 0 .2em
|
||||
h1
|
||||
font-size 2.55em
|
||||
padding-bottom 0.3em
|
||||
line-height 1.2em
|
||||
line-height 1.2
|
||||
border-bottom solid 1px borderColor
|
||||
margin 1em 0 0.44em
|
||||
&:first-child
|
||||
margin-top 0
|
||||
h2
|
||||
font-size 1.75em
|
||||
padding-bottom 0.3em
|
||||
line-height 1.225em
|
||||
line-height 1.225
|
||||
border-bottom solid 1px borderColor
|
||||
margin 1em 0 0.57em
|
||||
&:first-child
|
||||
margin-top 0
|
||||
h3
|
||||
font-size 1.5em
|
||||
line-height 1.43em
|
||||
margin 1em 0 0.66em
|
||||
line-height 1.43
|
||||
h4
|
||||
font-size 1.25em
|
||||
line-height 1.4em
|
||||
margin 1em 0 0.8em
|
||||
line-height 1.4
|
||||
h5
|
||||
font-size 1em
|
||||
line-height 1.4em
|
||||
margin 1em 0 1em
|
||||
line-height 1.1
|
||||
h6
|
||||
font-size 1em
|
||||
line-height 1.4em
|
||||
margin 1em 0 1em
|
||||
color #777
|
||||
p
|
||||
line-height 1.6em
|
||||
@@ -363,7 +357,10 @@ admonition_types = {
|
||||
danger: {color: #c2185b, icon: "block"},
|
||||
caution: {color: #ffa726, icon: "warning"},
|
||||
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
|
||||
@@ -424,6 +421,9 @@ pre.fence
|
||||
canvas, svg
|
||||
max-width 100% !important
|
||||
|
||||
svg[ratio]
|
||||
width 100%
|
||||
|
||||
.gallery
|
||||
width 100%
|
||||
height 50vh
|
||||
@@ -444,6 +444,44 @@ pre.fence
|
||||
color $ui-text-color
|
||||
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%)
|
||||
themeDarkText = #f9f9f9
|
||||
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
||||
@@ -511,137 +549,63 @@ body[data-theme="dark"]
|
||||
color $ui-dark-text-color
|
||||
background-color $ui-dark-tag-backgroundColor
|
||||
|
||||
themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor
|
||||
themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%)
|
||||
themeSolarizedDarkTableHead = themeSolarizedDarkTableEven
|
||||
themeSolarizedDarkTableBorder = themeDarkBorder
|
||||
.markdownIt-TOC-wrapper
|
||||
&,
|
||||
&:before
|
||||
background-color darken(themeDarkBackground, 5%)
|
||||
color themeDarkText
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
color $ui-solarized-dark-text-color
|
||||
border-color themeDarkBorder
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
table
|
||||
thead
|
||||
tr
|
||||
background-color themeSolarizedDarkTableHead
|
||||
th
|
||||
border-color themeSolarizedDarkTableBorder
|
||||
&:last-child
|
||||
border-right solid 1px themeSolarizedDarkTableBorder
|
||||
tbody
|
||||
tr:nth-child(2n + 1)
|
||||
background-color themeSolarizedDarkTableOdd
|
||||
tr:nth-child(2n)
|
||||
background-color themeSolarizedDarkTableEven
|
||||
td
|
||||
border-color themeSolarizedDarkTableBorder
|
||||
&:last-child
|
||||
border-right solid 1px themeSolarizedDarkTableBorder
|
||||
dl
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
color get-theme-var(theme, 'text-color')
|
||||
border-color themeDarkBorder
|
||||
background-color themeSolarizedDarkTableHead
|
||||
dt
|
||||
border-color themeDarkBorder
|
||||
dd
|
||||
border-color themeDarkBorder
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
table
|
||||
thead
|
||||
tr
|
||||
background-color get-theme-var(theme, 'table-head-backgroundColor')
|
||||
th
|
||||
border-color get-theme-var(theme, 'table-borderColor')
|
||||
&:last-child
|
||||
border-right solid 1px get-theme-var(theme, 'table-borderColor')
|
||||
tbody
|
||||
tr:nth-child(2n + 1)
|
||||
background-color get-theme-var(theme, 'table-odd-backgroundColor')
|
||||
tr:nth-child(2n)
|
||||
background-color get-theme-var(theme, 'table-even-backgroundColor')
|
||||
td
|
||||
border-color get-theme-var(theme, 'table-borderColor')
|
||||
&:last-child
|
||||
border-right solid 1px get-theme-var(theme, 'table-borderColor')
|
||||
kbd
|
||||
background-color get-theme-var(theme, 'kbd-backgroundColor')
|
||||
color get-theme-var(theme, 'kbd-color')
|
||||
|
||||
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
|
||||
dl
|
||||
border-color themeDarkBorder
|
||||
background-color get-theme-var(theme, 'table-head-backgroundColor')
|
||||
dt
|
||||
border-color themeDarkBorder
|
||||
dd
|
||||
border-color themeDarkBorder
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
|
||||
themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
|
||||
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
|
||||
themeMonokaiTableHead = themeMonokaiTableEven
|
||||
themeMonokaiTableBorder = themeDarkBorder
|
||||
pre.fence
|
||||
.gallery
|
||||
.carousel-main, .carousel-footer
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
.prev, .next
|
||||
color get-theme-var(theme, 'button--active-color')
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
|
||||
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
|
||||
background-color themeDarkBackground
|
||||
.markdownIt-TOC-wrapper
|
||||
&,
|
||||
&:before
|
||||
background-color darken(get-theme-var(theme, 'noteDetail-backgroundColor'), 15%)
|
||||
color themeDarkText
|
||||
|
||||
dl
|
||||
border-color themeDarkBorder
|
||||
background-color themeMonokaiTableHead
|
||||
dt
|
||||
border-color themeDarkBorder
|
||||
dd
|
||||
border-color themeDarkBorder
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
pre.fence
|
||||
.gallery
|
||||
.carousel-main, .carousel-footer
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
.prev, .next
|
||||
color $ui-monokai-button--active-color
|
||||
background-color $ui-monokai-button-backgroundColor
|
||||
|
||||
themeDraculaTableOdd = $ui-dracula-noteDetail-backgroundColor
|
||||
themeDraculaTableEven = darken($ui-dracula-noteDetail-backgroundColor, 10%)
|
||||
themeDraculaTableHead = themeDraculaTableEven
|
||||
themeDraculaTableBorder = themeDarkBorder
|
||||
|
||||
body[data-theme="dracula"]
|
||||
color $ui-dracula-text-color
|
||||
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
|
||||
border-color themeDarkBorder
|
||||
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
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import mermaidAPI from 'mermaid'
|
||||
import mermaidAPI from 'mermaid/dist/mermaid.min.js'
|
||||
import uiThemes from 'browser/lib/ui-themes'
|
||||
|
||||
// fixes bad styling in the mermaid dark theme
|
||||
const darkThemeStyling = `
|
||||
@@ -6,11 +7,11 @@ const darkThemeStyling = `
|
||||
fill: white;
|
||||
}`
|
||||
|
||||
function getRandomInt (min, max) {
|
||||
function getRandomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min)) + min
|
||||
}
|
||||
|
||||
function getId () {
|
||||
function getId() {
|
||||
const pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
let id = 'm-'
|
||||
for (let i = 0; i < 7; i++) {
|
||||
@@ -19,20 +20,48 @@ function getId () {
|
||||
return id
|
||||
}
|
||||
|
||||
function render (element, content, theme) {
|
||||
function render(element, content, theme, enableHTMLLabel) {
|
||||
try {
|
||||
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'
|
||||
}
|
||||
const isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai' || theme === 'dracula'
|
||||
|
||||
const isDarkTheme = uiThemes.some(
|
||||
item => item.name === theme && item.isDark
|
||||
)
|
||||
|
||||
mermaidAPI.initialize({
|
||||
theme: isDarkTheme ? 'dark' : 'default',
|
||||
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
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
element.className = 'mermaid-error'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import CSSModules from 'react-css-modules'
|
||||
|
||||
export default function (component, styles) {
|
||||
return CSSModules(component, styles, {handleNotFoundStyleName: 'log'})
|
||||
export default function(component, styles) {
|
||||
return CSSModules(component, styles, { handleNotFoundStyleName: 'log' })
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ const languages = [
|
||||
name: 'Chinese (zh-TW)',
|
||||
locale: 'zh-TW'
|
||||
},
|
||||
{
|
||||
name: 'Czech',
|
||||
locale: 'cs'
|
||||
},
|
||||
{
|
||||
name: 'Danish',
|
||||
locale: 'da'
|
||||
@@ -74,13 +78,13 @@ const languages = [
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
getLocales () {
|
||||
return languages.reduce(function (localeList, locale) {
|
||||
getLocales() {
|
||||
return languages.reduce(function(localeList, locale) {
|
||||
localeList.push(locale.locale)
|
||||
return localeList
|
||||
}, [])
|
||||
},
|
||||
getLanguages () {
|
||||
getLanguages() {
|
||||
return languages
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
class MutableMap {
|
||||
constructor (iterable) {
|
||||
constructor(iterable) {
|
||||
this._map = new Map(iterable)
|
||||
Object.defineProperty(this, 'size', {
|
||||
get: () => this._map.size,
|
||||
set: function (value) {
|
||||
set: function(value) {
|
||||
this['size'] = value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
get (...args) {
|
||||
get(...args) {
|
||||
return this._map.get(...args)
|
||||
}
|
||||
|
||||
set (...args) {
|
||||
set(...args) {
|
||||
return this._map.set(...args)
|
||||
}
|
||||
|
||||
delete (...args) {
|
||||
delete(...args) {
|
||||
return this._map.delete(...args)
|
||||
}
|
||||
|
||||
has (...args) {
|
||||
has(...args) {
|
||||
return this._map.has(...args)
|
||||
}
|
||||
|
||||
clear (...args) {
|
||||
clear(...args) {
|
||||
return this._map.clear(...args)
|
||||
}
|
||||
|
||||
forEach (...args) {
|
||||
forEach(...args) {
|
||||
return this._map.forEach(...args)
|
||||
}
|
||||
|
||||
[Symbol.iterator] () {
|
||||
[Symbol.iterator]() {
|
||||
return this._map[Symbol.iterator]()
|
||||
}
|
||||
|
||||
map (cb) {
|
||||
map(cb) {
|
||||
const result = []
|
||||
for (const [key, value] of this._map) {
|
||||
result.push(cb(value, key))
|
||||
@@ -45,7 +45,7 @@ class MutableMap {
|
||||
return result
|
||||
}
|
||||
|
||||
toJS () {
|
||||
toJS() {
|
||||
const result = {}
|
||||
for (let [key, value] of this._map) {
|
||||
if (value instanceof MutableSet || value instanceof MutableMap) {
|
||||
@@ -58,42 +58,42 @@ class MutableMap {
|
||||
}
|
||||
|
||||
class MutableSet {
|
||||
constructor (iterable) {
|
||||
constructor(iterable) {
|
||||
this._set = new Set(iterable)
|
||||
Object.defineProperty(this, 'size', {
|
||||
get: () => this._set.size,
|
||||
set: function (value) {
|
||||
set: function(value) {
|
||||
this['size'] = value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
add (...args) {
|
||||
add(...args) {
|
||||
return this._set.add(...args)
|
||||
}
|
||||
|
||||
delete (...args) {
|
||||
delete(...args) {
|
||||
return this._set.delete(...args)
|
||||
}
|
||||
|
||||
forEach (...args) {
|
||||
forEach(...args) {
|
||||
return this._set.forEach(...args)
|
||||
}
|
||||
|
||||
[Symbol.iterator] () {
|
||||
[Symbol.iterator]() {
|
||||
return this._set[Symbol.iterator]()
|
||||
}
|
||||
|
||||
map (cb) {
|
||||
map(cb) {
|
||||
const result = []
|
||||
this._set.forEach(function (value, key) {
|
||||
this._set.forEach(function(value, key) {
|
||||
result.push(cb(value, key))
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
toJS () {
|
||||
toJS() {
|
||||
return Array.from(this._set)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@ const BOOSTNOTERC = '.boostnoterc'
|
||||
const homePath = global.process.env.HOME || global.process.env.USERPROFILE
|
||||
const _boostnotercPath = path.join(homePath, BOOSTNOTERC)
|
||||
|
||||
export function parse (boostnotercPath = _boostnotercPath) {
|
||||
export function parse(boostnotercPath = _boostnotercPath) {
|
||||
if (!sander.existsSync(boostnotercPath)) return {}
|
||||
try {
|
||||
return JSON.parse(sander.readFileSync(boostnotercPath).toString())
|
||||
} catch (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 {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ import fs from 'fs'
|
||||
import consts from './consts'
|
||||
|
||||
class SnippetManager {
|
||||
constructor () {
|
||||
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.'
|
||||
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 = []
|
||||
@@ -18,7 +19,7 @@ class SnippetManager {
|
||||
this.assignSnippets = this.assignSnippets.bind(this)
|
||||
}
|
||||
|
||||
init () {
|
||||
init() {
|
||||
if (fs.existsSync(consts.SNIPPET_FILE)) {
|
||||
try {
|
||||
this.snippets = JSON.parse(
|
||||
@@ -37,11 +38,11 @@ class SnippetManager {
|
||||
this.snippets = this.defaultSnippet
|
||||
}
|
||||
|
||||
assignSnippets (snippets) {
|
||||
assignSnippets(snippets) {
|
||||
this.snippets = snippets
|
||||
}
|
||||
|
||||
expandSnippet (wordBeforeCursor, cursor, cm) {
|
||||
expandSnippet(wordBeforeCursor, cursor, cm) {
|
||||
const templateCursorString = ':{}'
|
||||
for (let i = 0; i < this.snippets.length; i++) {
|
||||
if (this.snippets[i].prefix.indexOf(wordBeforeCursor.text) === -1) {
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
import { Point } from '@susisu/mte-kernel'
|
||||
|
||||
export default class TextEditorInterface {
|
||||
constructor (editor) {
|
||||
constructor(editor) {
|
||||
this.editor = editor
|
||||
this.doc = editor.getDoc()
|
||||
this.transaction = false
|
||||
}
|
||||
|
||||
getCursorPosition () {
|
||||
getCursorPosition() {
|
||||
const { line, ch } = this.doc.getCursor()
|
||||
return new Point(line, ch)
|
||||
}
|
||||
|
||||
setCursorPosition (pos) {
|
||||
setCursorPosition(pos) {
|
||||
this.doc.setCursor({
|
||||
line: pos.row,
|
||||
ch: pos.column
|
||||
})
|
||||
}
|
||||
|
||||
setSelectionRange (range) {
|
||||
setSelectionRange(range) {
|
||||
this.doc.setSelection(
|
||||
{ line: range.start.row, ch: range.start.column },
|
||||
{ line: range.end.row, ch: range.end.column }
|
||||
)
|
||||
}
|
||||
|
||||
getLastRow () {
|
||||
getLastRow() {
|
||||
return this.doc.lineCount() - 1
|
||||
}
|
||||
|
||||
acceptsTableEdit () {
|
||||
acceptsTableEdit() {
|
||||
return true
|
||||
}
|
||||
|
||||
getLine (row) {
|
||||
getLine(row) {
|
||||
return this.doc.getLine(row)
|
||||
}
|
||||
|
||||
insertLine (row, line) {
|
||||
insertLine(row, line) {
|
||||
const lastRow = this.getLastRow()
|
||||
if (row > lastRow) {
|
||||
const lastLine = this.getLine(lastRow)
|
||||
@@ -56,7 +56,7 @@ export default class TextEditorInterface {
|
||||
}
|
||||
}
|
||||
|
||||
deleteLine (row) {
|
||||
deleteLine(row) {
|
||||
const lastRow = this.getLastRow()
|
||||
if (row >= lastRow) {
|
||||
if (lastRow > 0) {
|
||||
@@ -76,15 +76,11 @@ export default class TextEditorInterface {
|
||||
)
|
||||
}
|
||||
} else {
|
||||
this.doc.replaceRange(
|
||||
'',
|
||||
{ line: row, ch: 0 },
|
||||
{ line: row + 1, ch: 0 }
|
||||
)
|
||||
this.doc.replaceRange('', { line: row, ch: 0 }, { line: row + 1, ch: 0 })
|
||||
}
|
||||
}
|
||||
|
||||
replaceLines (startRow, endRow, lines) {
|
||||
replaceLines(startRow, endRow, lines) {
|
||||
const lastRow = this.getLastRow()
|
||||
if (endRow > lastRow) {
|
||||
const lastLine = this.getLine(lastRow)
|
||||
@@ -102,7 +98,7 @@ export default class TextEditorInterface {
|
||||
}
|
||||
}
|
||||
|
||||
transact (func) {
|
||||
transact(func) {
|
||||
this.transaction = true
|
||||
func()
|
||||
this.transaction = false
|
||||
|
||||
@@ -3,17 +3,18 @@ import i18n from 'browser/lib/i18n'
|
||||
const { remote } = electron
|
||||
const { dialog } = remote
|
||||
|
||||
export function confirmDeleteNote (confirmDeletion, permanent) {
|
||||
export function confirmDeleteNote(confirmDeletion, permanent) {
|
||||
if (confirmDeletion || permanent) {
|
||||
const alertConfig = {
|
||||
ype: 'warning',
|
||||
type: 'warning',
|
||||
message: i18n.__('Confirm note deletion'),
|
||||
detail: i18n.__('This will permanently remove this note.'),
|
||||
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||
}
|
||||
|
||||
const dialogButtonIndex = dialog.showMessageBox(
|
||||
remote.getCurrentWindow(), alertConfig
|
||||
remote.getCurrentWindow(),
|
||||
alertConfig
|
||||
)
|
||||
|
||||
return dialogButtonIndex === 0
|
||||
|
||||
@@ -9,41 +9,53 @@ const CODEMIRROR_EXTRA_THEME_PATH = 'extra_scripts/codemirror/theme'
|
||||
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)
|
||||
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('.'))
|
||||
.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}`
|
||||
}
|
||||
}))
|
||||
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(
|
||||
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')
|
||||
: '' // return nothing as we specified different path to snippets.json in test
|
||||
const snippetFile =
|
||||
process.env.NODE_ENV !== 'test'
|
||||
? path.join(app.getPath('userData'), 'snippets.json')
|
||||
: '' // return nothing as we specified different path to snippets.json in test
|
||||
|
||||
const consts = {
|
||||
FOLDER_COLORS: [
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const { remote } = require('electron')
|
||||
const { Menu, MenuItem } = remote
|
||||
|
||||
function popup (templates) {
|
||||
function popup(templates) {
|
||||
const menu = new Menu()
|
||||
templates.forEach((item) => {
|
||||
templates.forEach(item => {
|
||||
menu.append(new MenuItem(item))
|
||||
})
|
||||
menu.popup(remote.getCurrentWindow())
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import fs from 'fs'
|
||||
|
||||
const {remote} = require('electron')
|
||||
const {Menu} = remote.require('electron')
|
||||
const {clipboard} = remote.require('electron')
|
||||
const {shell} = remote.require('electron')
|
||||
const { remote } = require('electron')
|
||||
const { Menu } = remote.require('electron')
|
||||
const { clipboard } = remote.require('electron')
|
||||
const { shell } = remote.require('electron')
|
||||
const spellcheck = require('./spellcheck')
|
||||
const uri2path = require('file-uri-to-path')
|
||||
|
||||
@@ -16,11 +16,16 @@ const uri2path = require('file-uri-to-path')
|
||||
* @param {MouseEvent} event that has triggered the creation of the context menu
|
||||
* @returns {Electron.Menu} The created electron context menu
|
||||
*/
|
||||
const buildEditorContextMenu = function (editor, event) {
|
||||
if (editor == null || event == null || event.pageX == null || event.pageY == null) {
|
||||
const buildEditorContextMenu = function(editor, event) {
|
||||
if (
|
||||
editor == null ||
|
||||
event == null ||
|
||||
event.pageX == null ||
|
||||
event.pageY == null
|
||||
) {
|
||||
return null
|
||||
}
|
||||
const cursor = editor.coordsChar({left: event.pageX, top: event.pageY})
|
||||
const cursor = editor.coordsChar({ left: event.pageX, top: event.pageY })
|
||||
const wordRange = editor.findWordAt(cursor)
|
||||
const word = editor.getRange(wordRange.anchor, wordRange.head)
|
||||
const existingMarks = editor.findMarks(wordRange.anchor, wordRange.head) || []
|
||||
@@ -40,30 +45,44 @@ const buildEditorContextMenu = function (editor, event) {
|
||||
isMisspelled: isMisspelled,
|
||||
spellingSuggestions: suggestion
|
||||
}
|
||||
const template = [{
|
||||
role: 'cut'
|
||||
}, {
|
||||
role: 'copy'
|
||||
}, {
|
||||
role: 'paste'
|
||||
}, {
|
||||
role: 'selectall'
|
||||
}]
|
||||
const template = [
|
||||
{
|
||||
role: 'cut'
|
||||
},
|
||||
{
|
||||
role: 'copy'
|
||||
},
|
||||
{
|
||||
role: 'paste'
|
||||
},
|
||||
{
|
||||
role: 'selectall'
|
||||
}
|
||||
]
|
||||
|
||||
if (selection.isMisspelled) {
|
||||
const suggestions = selection.spellingSuggestions
|
||||
template.unshift.apply(template, suggestions.map(function (suggestion) {
|
||||
return {
|
||||
label: suggestion,
|
||||
click: function (suggestion) {
|
||||
if (editor != null) {
|
||||
editor.replaceRange(suggestion.label, wordRange.anchor, wordRange.head)
|
||||
template.unshift.apply(
|
||||
template,
|
||||
suggestions
|
||||
.map(function(suggestion) {
|
||||
return {
|
||||
label: suggestion,
|
||||
click: function(suggestion) {
|
||||
if (editor != null) {
|
||||
editor.replaceRange(
|
||||
suggestion.label,
|
||||
wordRange.anchor,
|
||||
wordRange.head
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).concat({
|
||||
type: 'separator'
|
||||
}))
|
||||
})
|
||||
.concat({
|
||||
type: 'separator'
|
||||
})
|
||||
)
|
||||
}
|
||||
return Menu.buildFromTemplate(template)
|
||||
}
|
||||
@@ -74,19 +93,30 @@ const buildEditorContextMenu = function (editor, event) {
|
||||
* @param {MouseEvent} event that has triggered the creation of the context menu
|
||||
* @returns {Electron.Menu} The created electron context menu
|
||||
*/
|
||||
const buildMarkdownPreviewContextMenu = function (markdownPreview, event) {
|
||||
if (markdownPreview == null || event == null || event.pageX == null || event.pageY == null) {
|
||||
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'
|
||||
}]
|
||||
const template = [
|
||||
{
|
||||
role: 'copy'
|
||||
},
|
||||
{
|
||||
role: 'selectall'
|
||||
}
|
||||
]
|
||||
|
||||
if (event.target.tagName.toLowerCase() === 'a' && event.target.getAttribute('href')) {
|
||||
if (
|
||||
event.target.tagName.toLowerCase() === 'a' &&
|
||||
event.target.getAttribute('href')
|
||||
) {
|
||||
// Link opener for files on the local system pointed to by href
|
||||
const href = event.target.href
|
||||
const isLocalFile = href.startsWith('file:')
|
||||
@@ -94,31 +124,29 @@ const buildMarkdownPreviewContextMenu = function (markdownPreview, event) {
|
||||
const absPath = uri2path(href)
|
||||
try {
|
||||
if (fs.lstatSync(absPath).isFile()) {
|
||||
template.push(
|
||||
{
|
||||
label: i18n.__('Show in explorer'),
|
||||
click: (e) => shell.showItemInFolder(absPath)
|
||||
}
|
||||
)
|
||||
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)
|
||||
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)
|
||||
}
|
||||
)
|
||||
template.push({
|
||||
label: i18n.__('Copy Url'),
|
||||
click: e => clipboard.writeText(href)
|
||||
})
|
||||
}
|
||||
return Menu.buildFromTemplate(template)
|
||||
}
|
||||
|
||||
module.exports =
|
||||
{
|
||||
module.exports = {
|
||||
buildEditorContextMenu: buildEditorContextMenu,
|
||||
buildMarkdownPreviewContextMenu: buildMarkdownPreviewContextMenu
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default function convertModeName (name) {
|
||||
export default function convertModeName(name) {
|
||||
switch (name) {
|
||||
case 'ejs':
|
||||
return 'Embedded Javascript'
|
||||
|
||||
@@ -3,8 +3,19 @@ import 'codemirror-mode-elixir'
|
||||
|
||||
const stylusCodeInfo = CodeMirror.modeInfo.find(info => info.name === 'Stylus')
|
||||
if (stylusCodeInfo == null) {
|
||||
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
|
||||
CodeMirror.modeInfo.push({
|
||||
name: 'Stylus',
|
||||
mime: 'text/x-styl',
|
||||
mode: 'stylus',
|
||||
ext: ['styl'],
|
||||
alias: ['styl']
|
||||
})
|
||||
} else {
|
||||
stylusCodeInfo.alias = ['styl']
|
||||
}
|
||||
CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']})
|
||||
CodeMirror.modeInfo.push({
|
||||
name: 'Elixir',
|
||||
mime: 'text/x-elixir',
|
||||
mode: 'elixir',
|
||||
ext: ['ex']
|
||||
})
|
||||
|
||||
@@ -8,7 +8,7 @@ import moment from 'moment'
|
||||
* @param {mixed}
|
||||
* @return {string}
|
||||
*/
|
||||
export function formatDate (date) {
|
||||
export function formatDate(date) {
|
||||
const m = moment(date)
|
||||
if (!m.isValid()) {
|
||||
throw Error('Invalid argument.')
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleField = 'title') {
|
||||
export function findNoteTitle(
|
||||
value,
|
||||
enableFrontMatterTitle,
|
||||
frontMatterTitleField = 'title'
|
||||
) {
|
||||
const splitted = value.split('\n')
|
||||
let title = null
|
||||
let isInsideCodeBlock = false
|
||||
@@ -6,8 +10,13 @@ export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleFi
|
||||
if (splitted[0] === '---') {
|
||||
let line = 0
|
||||
while (++line < splitted.length) {
|
||||
if (enableFrontMatterTitle && splitted[line].startsWith(frontMatterTitleField + ':')) {
|
||||
title = splitted[line].substring(frontMatterTitleField.length + 1).trim()
|
||||
if (
|
||||
enableFrontMatterTitle &&
|
||||
splitted[line].startsWith(frontMatterTitleField + ':')
|
||||
) {
|
||||
title = splitted[line]
|
||||
.substring(frontMatterTitleField.length + 1)
|
||||
.trim()
|
||||
|
||||
break
|
||||
}
|
||||
@@ -22,11 +31,15 @@ export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleFi
|
||||
if (title === null) {
|
||||
splitted.some((line, index) => {
|
||||
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('```')) {
|
||||
isInsideCodeBlock = !isInsideCodeBlock
|
||||
}
|
||||
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
|
||||
if (
|
||||
isInsideCodeBlock === false &&
|
||||
(trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))
|
||||
) {
|
||||
title = trimmedLine
|
||||
return true
|
||||
}
|
||||
@@ -35,7 +48,7 @@ export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleFi
|
||||
|
||||
if (title === null) {
|
||||
title = ''
|
||||
splitted.some((line) => {
|
||||
splitted.some(line => {
|
||||
if (line.trim().length > 0) {
|
||||
title = line.trim()
|
||||
return true
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
const _ = require('lodash')
|
||||
|
||||
export function findStorage (storageKey) {
|
||||
export function findStorage(storageKey) {
|
||||
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
||||
const storage = _.find(cachedStorageList, {key: storageKey})
|
||||
if (storage === undefined) throw new Error('Target storage doesn\'t exist.')
|
||||
if (!_.isArray(cachedStorageList))
|
||||
throw new Error("Target storage doesn't exist.")
|
||||
const storage = _.find(cachedStorageList, { key: storageKey })
|
||||
if (storage === undefined) throw new Error("Target storage doesn't exist.")
|
||||
|
||||
return storage
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export function getTodoStatus (content) {
|
||||
export function getTodoStatus(content) {
|
||||
const splitted = content.split('\n')
|
||||
let numberOfTodo = 0
|
||||
let numberOfCompletedTodo = 0
|
||||
|
||||
splitted.forEach((line) => {
|
||||
splitted.forEach(line => {
|
||||
const trimmedLine = line.trim().replace(/^(>\s*)*/, '')
|
||||
if (trimmedLine.match(/^[+\-*] \[(\s|x)] ./i)) {
|
||||
numberOfTodo++
|
||||
@@ -19,7 +19,7 @@ export function getTodoStatus (content) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getTodoPercentageOfCompleted (content) {
|
||||
export function getTodoPercentageOfCompleted(content) {
|
||||
const state = getTodoStatus(content)
|
||||
return Math.floor(state.completed / state.total * 100)
|
||||
return Math.floor((state.completed / state.total) * 100)
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
* @return {string}
|
||||
*/
|
||||
|
||||
export function decodeEntities (text) {
|
||||
export function decodeEntities(text) {
|
||||
var entities = [
|
||||
['apos', '\''],
|
||||
['apos', "'"],
|
||||
['amp', '&'],
|
||||
['lt', '<'],
|
||||
['gt', '>'],
|
||||
@@ -24,16 +24,16 @@ export function decodeEntities (text) {
|
||||
return text
|
||||
}
|
||||
|
||||
export function encodeEntities (text) {
|
||||
export function encodeEntities(text) {
|
||||
const entities = [
|
||||
['\'', 'apos'],
|
||||
["'", 'apos'],
|
||||
['<', 'lt'],
|
||||
['>', 'gt'],
|
||||
['\\?', '#63'],
|
||||
['\\$', '#36']
|
||||
]
|
||||
|
||||
entities.forEach((entity) => {
|
||||
entities.forEach(entity => {
|
||||
text = text.replace(new RegExp(entity[0], 'g'), `&${entity[1]};`)
|
||||
})
|
||||
return text
|
||||
|
||||
@@ -8,9 +8,10 @@ const i18n = new (require('i18n-2'))({
|
||||
// setup some locales - other locales default to the first locale
|
||||
locales: getLocales(),
|
||||
extension: '.json',
|
||||
directory: process.env.NODE_ENV === 'production'
|
||||
? path.join(app.getAppPath(), './locales')
|
||||
: path.resolve('./locales'),
|
||||
directory:
|
||||
process.env.NODE_ENV === 'production'
|
||||
? path.join(app.getAppPath(), './locales')
|
||||
: path.resolve('./locales'),
|
||||
devMode: false
|
||||
})
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
const crypto = require('crypto')
|
||||
const _ = require('lodash')
|
||||
const uuidv4 = require('uuid/v4')
|
||||
|
||||
module.exports = function (uuid) {
|
||||
module.exports = function(uuid) {
|
||||
if (typeof uuid === typeof true && uuid) {
|
||||
return uuidv4()
|
||||
}
|
||||
|
||||
@@ -1,35 +1,44 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function definitionListPlugin (md) {
|
||||
module.exports = function definitionListPlugin(md) {
|
||||
var isSpace = md.utils.isSpace
|
||||
|
||||
// Search `[:~][\n ]`, returns next pos after marker on success
|
||||
// or -1 on fail.
|
||||
function skipMarker (state, line) {
|
||||
function skipMarker(state, line) {
|
||||
let start = state.bMarks[line] + state.tShift[line]
|
||||
const max = state.eMarks[line]
|
||||
|
||||
if (start >= max) { return -1 }
|
||||
if (start >= max) {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Check bullet
|
||||
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)
|
||||
|
||||
// require space after ":"
|
||||
if (start === pos) { return -1 }
|
||||
if (start === pos) {
|
||||
return -1
|
||||
}
|
||||
|
||||
return start
|
||||
}
|
||||
|
||||
function markTightParagraphs (state, idx) {
|
||||
function markTightParagraphs(state, idx) {
|
||||
const level = state.level + 2
|
||||
|
||||
let i
|
||||
let l
|
||||
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].hidden = true
|
||||
i += 2
|
||||
@@ -37,7 +46,7 @@ module.exports = function definitionListPlugin (md) {
|
||||
}
|
||||
}
|
||||
|
||||
function deflist (state, startLine, endLine, silent) {
|
||||
function deflist(state, startLine, endLine, silent) {
|
||||
var ch,
|
||||
contentStart,
|
||||
ddLine,
|
||||
@@ -63,28 +72,38 @@ module.exports = function definitionListPlugin (md) {
|
||||
|
||||
if (silent) {
|
||||
// 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
|
||||
}
|
||||
|
||||
nextLine = startLine + 1
|
||||
if (nextLine >= endLine) { return false }
|
||||
if (nextLine >= endLine) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (state.isEmpty(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)
|
||||
if (contentStart < 0) { return false }
|
||||
if (contentStart < 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Start list
|
||||
listTokIdx = state.tokens.length
|
||||
tight = true
|
||||
|
||||
token = state.push('dl_open', 'dl', 1)
|
||||
token.map = listLines = [ startLine, 0 ]
|
||||
token.map = listLines = [startLine, 0]
|
||||
|
||||
//
|
||||
// Iterate list items
|
||||
@@ -100,34 +119,38 @@ module.exports = function definitionListPlugin (md) {
|
||||
// needed to break out of the second one
|
||||
//
|
||||
/* eslint no-labels:0,block-scoped-var:0 */
|
||||
OUTER:
|
||||
for (;;) {
|
||||
OUTER: for (;;) {
|
||||
prevEmptyEnd = false
|
||||
|
||||
token = state.push('dt_open', 'dt', 1)
|
||||
token.map = [ dtLine, dtLine ]
|
||||
token.map = [dtLine, dtLine]
|
||||
|
||||
token = state.push('inline', '', 0)
|
||||
token.map = [ dtLine, dtLine ]
|
||||
token.content = state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim()
|
||||
token.map = [dtLine, dtLine]
|
||||
token.content = state
|
||||
.getLines(dtLine, dtLine + 1, state.blkIndent, false)
|
||||
.trim()
|
||||
token.children = []
|
||||
|
||||
token = state.push('dt_close', 'dt', -1)
|
||||
|
||||
for (;;) {
|
||||
token = state.push('dd_open', 'dd', 1)
|
||||
token.map = itemLines = [ ddLine, 0 ]
|
||||
token.map = itemLines = [ddLine, 0]
|
||||
|
||||
pos = contentStart
|
||||
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) {
|
||||
ch = state.src.charCodeAt(pos)
|
||||
|
||||
if (isSpace(ch)) {
|
||||
if (ch === 0x09) {
|
||||
offset += 4 - offset % 4
|
||||
offset += 4 - (offset % 4)
|
||||
} else {
|
||||
offset++
|
||||
}
|
||||
@@ -153,8 +176,11 @@ module.exports = function definitionListPlugin (md) {
|
||||
state.parentType = 'deflist'
|
||||
|
||||
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
|
||||
state.lineMax = newEndLine
|
||||
@@ -169,7 +195,7 @@ module.exports = function definitionListPlugin (md) {
|
||||
}
|
||||
// Item become loose if finish with empty line,
|
||||
// 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.sCount[ddLine] = oldSCount
|
||||
@@ -182,11 +208,17 @@ module.exports = function definitionListPlugin (md) {
|
||||
|
||||
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)
|
||||
if (contentStart < 0) { break }
|
||||
if (contentStart < 0) {
|
||||
break
|
||||
}
|
||||
|
||||
ddLine = nextLine
|
||||
|
||||
@@ -194,20 +226,36 @@ module.exports = function definitionListPlugin (md) {
|
||||
// insert DD tag and repeat checking
|
||||
}
|
||||
|
||||
if (nextLine >= endLine) { break }
|
||||
if (nextLine >= endLine) {
|
||||
break
|
||||
}
|
||||
dtLine = nextLine
|
||||
|
||||
if (state.isEmpty(dtLine)) { break }
|
||||
if (state.sCount[dtLine] < state.blkIndent) { break }
|
||||
if (state.isEmpty(dtLine)) {
|
||||
break
|
||||
}
|
||||
if (state.sCount[dtLine] < state.blkIndent) {
|
||||
break
|
||||
}
|
||||
|
||||
ddLine = dtLine + 1
|
||||
if (ddLine >= endLine) { break }
|
||||
if (state.isEmpty(ddLine)) { ddLine++ }
|
||||
if (ddLine >= endLine) { 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)
|
||||
if (contentStart < 0) { break }
|
||||
if (contentStart < 0) {
|
||||
break
|
||||
}
|
||||
|
||||
// go to the next loop iteration:
|
||||
// insert DT and DD tags and repeat checking
|
||||
@@ -228,5 +276,7 @@ module.exports = function definitionListPlugin (md) {
|
||||
return true
|
||||
}
|
||||
|
||||
md.block.ruler.before('paragraph', 'deflist', deflist, { alt: [ 'paragraph', 'reference' ] })
|
||||
md.block.ruler.before('paragraph', 'deflist', deflist, {
|
||||
alt: ['paragraph', 'reference']
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function (md, renderers, defaultRenderer) {
|
||||
module.exports = function(md, renderers, defaultRenderer) {
|
||||
const paramsRE = /^[ \t]*([\w+#-]+)?(?:\(((?:\s*\w[-\w]*(?:=(?:'(?:.*?[^\\])?'|"(?:.*?[^\\])?"|(?:[^'"][^\s]*)))?)*)\))?(?::([^:]*)(?::(\d+))?)?\s*$/
|
||||
|
||||
function fence (state, startLine, endLine, silent) {
|
||||
function fence(state, startLine, endLine, silent) {
|
||||
let pos = state.bMarks[startLine] + state.tShift[startLine]
|
||||
let max = state.eMarks[startLine]
|
||||
|
||||
@@ -12,7 +12,7 @@ module.exports = function (md, renderers, defaultRenderer) {
|
||||
}
|
||||
|
||||
const marker = state.src.charCodeAt(pos)
|
||||
if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) {
|
||||
if (marker !== 0x7e /* ~ */ && marker !== 0x60 /* ` */) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -46,7 +46,10 @@ module.exports = function (md, renderers, defaultRenderer) {
|
||||
if (pos < max && state.sCount[nextLine] < state.blkIndent) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -127,10 +130,12 @@ module.exports = function (md, renderers, defaultRenderer) {
|
||||
})
|
||||
|
||||
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) {
|
||||
md.renderer.rules['_fence'] = (tokens, index) => defaultRenderer(tokens[index])
|
||||
md.renderer.rules['_fence'] = (tokens, index) =>
|
||||
defaultRenderer(tokens[index])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function frontMatterPlugin (md) {
|
||||
function frontmatter (state, startLine, endLine, silent) {
|
||||
if (startLine !== 0 || state.src.substr(startLine, state.eMarks[0]) !== '---') {
|
||||
module.exports = function frontMatterPlugin(md) {
|
||||
function frontmatter(state, startLine, endLine, silent) {
|
||||
if (
|
||||
startLine !== 0 ||
|
||||
state.src.substr(startLine, state.eMarks[0]) !== '---'
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
let line = 0
|
||||
while (++line < state.lineMax) {
|
||||
if (state.src.substring(state.bMarks[line], state.eMarks[line]) === '---') {
|
||||
if (
|
||||
state.src.substring(state.bMarks[line], state.eMarks[line]) === '---'
|
||||
) {
|
||||
state.line = line + 1
|
||||
|
||||
return true
|
||||
@@ -19,6 +24,6 @@ module.exports = function frontMatterPlugin (md) {
|
||||
}
|
||||
|
||||
md.block.ruler.before('table', 'frontmatter', frontmatter, {
|
||||
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
|
||||
alt: ['paragraph', 'reference', 'blockquote', 'list']
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import sanitizeHtml from 'sanitize-html'
|
||||
import { escapeHtmlCharacters } from './utils'
|
||||
import url from 'url'
|
||||
|
||||
module.exports = function sanitizePlugin (md, options) {
|
||||
module.exports = function sanitizePlugin(md, options) {
|
||||
options = options || {}
|
||||
|
||||
md.core.ruler.after('linkify', 'sanitize_inline', state => {
|
||||
@@ -15,7 +15,7 @@ module.exports = function sanitizePlugin (md, options) {
|
||||
options
|
||||
)
|
||||
}
|
||||
if (state.tokens[tokenIdx].type === '_fence') {
|
||||
if (state.tokens[tokenIdx].type.match(/.*_fence$/)) {
|
||||
// escapeHtmlCharacters has better performance
|
||||
state.tokens[tokenIdx].content = escapeHtmlCharacters(
|
||||
state.tokens[tokenIdx].content,
|
||||
@@ -38,15 +38,20 @@ module.exports = function sanitizePlugin (md, options) {
|
||||
}
|
||||
|
||||
const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:=("|')(?:[^\3]+?)\3)?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i
|
||||
const 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)
|
||||
if (!match) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const { allowedTags, allowedAttributes, selfClosing, allowedSchemesAppliedToAttributes } = options
|
||||
const {
|
||||
allowedTags,
|
||||
allowedAttributes,
|
||||
selfClosing,
|
||||
allowedSchemesAppliedToAttributes
|
||||
} = options
|
||||
|
||||
if (match[1] !== undefined) {
|
||||
// opening tag
|
||||
@@ -65,9 +70,17 @@ function sanitizeInline (html, options) {
|
||||
name = match[1].toLowerCase()
|
||||
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 (naughtyHRef(value, options) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value, options))) {
|
||||
if (
|
||||
naughtyHRef(value, options) ||
|
||||
(tag === 'iframe' &&
|
||||
name === 'src' &&
|
||||
naughtyIFrame(value, options))
|
||||
) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -94,8 +107,12 @@ function sanitizeInline (html, options) {
|
||||
}
|
||||
}
|
||||
|
||||
function naughtyHRef (href, options) {
|
||||
function naughtyHRef(href, options) {
|
||||
// href = href.replace(/[\x00-\x20]+/g, '')
|
||||
if (!href) {
|
||||
// No href
|
||||
return false
|
||||
}
|
||||
href = href.replace(/<\!\-\-.*?\-\-\>/g, '')
|
||||
|
||||
const matches = href.match(/^([a-zA-Z]+)\:/)
|
||||
@@ -113,7 +130,7 @@ function naughtyHRef (href, options) {
|
||||
return options.allowedSchemes.indexOf(scheme) === -1
|
||||
}
|
||||
|
||||
function naughtyIFrame (src, options) {
|
||||
function naughtyIFrame(src, options) {
|
||||
try {
|
||||
const parsed = url.parse(src, false, true)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ const hasProp = Object.prototype.hasOwnProperty
|
||||
/**
|
||||
* From @enyaxu/markdown-it-anchor
|
||||
*/
|
||||
function uniqueSlug (slug, slugs, opts) {
|
||||
function uniqueSlug(slug, slugs, opts) {
|
||||
let uniq = slug
|
||||
let i = opts.uniqueSlugStartIndex
|
||||
while (hasProp.call(slugs, uniq)) uniq = `${slug}-${i++}`
|
||||
@@ -20,7 +20,7 @@ function uniqueSlug (slug, slugs, opts) {
|
||||
return uniq
|
||||
}
|
||||
|
||||
function linkify (token) {
|
||||
function linkify(token) {
|
||||
token.content = mdlink(token.content, `#${decodeURI(token.slug)}`)
|
||||
return token
|
||||
}
|
||||
@@ -36,8 +36,8 @@ const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
|
||||
* Otherwise,TOC is updated in place.
|
||||
* @param editor CodeMirror editor to be updated with TOC
|
||||
*/
|
||||
export function generateInEditor (editor) {
|
||||
function updateExistingToc () {
|
||||
export function generateInEditor(editor) {
|
||||
function updateExistingToc() {
|
||||
const toc = generate(editor.getValue())
|
||||
const search = editor.getSearchCursor(tocRegex)
|
||||
while (search.findNext()) {
|
||||
@@ -45,8 +45,10 @@ export function generateInEditor (editor) {
|
||||
}
|
||||
}
|
||||
|
||||
function addTocAtCursorPosition () {
|
||||
const toc = generate(editor.getRange(editor.getCursor(), {line: Infinity}))
|
||||
function addTocAtCursorPosition() {
|
||||
const toc = generate(
|
||||
editor.getRange(editor.getCursor(), { line: Infinity })
|
||||
)
|
||||
editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor())
|
||||
}
|
||||
|
||||
@@ -57,7 +59,7 @@ export function generateInEditor (editor) {
|
||||
}
|
||||
}
|
||||
|
||||
export function tocExistsInEditor (editor) {
|
||||
export function tocExistsInEditor(editor) {
|
||||
return tocRegex.test(editor.getValue())
|
||||
}
|
||||
|
||||
@@ -66,7 +68,7 @@ export function tocExistsInEditor (editor) {
|
||||
* @param markdownText MD document
|
||||
* @returns generatedTOC String containing generated TOC
|
||||
*/
|
||||
export function generate (markdownText) {
|
||||
export function generate(markdownText) {
|
||||
const slugs = {}
|
||||
const opts = {
|
||||
uniqueSlugStartIndex: 1
|
||||
@@ -86,9 +88,12 @@ export function generate (markdownText) {
|
||||
return TOC_MARKER_START + EOL + EOL + md + EOL + EOL + TOC_MARKER_END
|
||||
}
|
||||
|
||||
function wrapTocWithEol (toc, editor) {
|
||||
function wrapTocWithEol(toc, editor) {
|
||||
const leftWrap = editor.getCursor().ch === 0 ? '' : EOL
|
||||
const 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
|
||||
}
|
||||
|
||||
|
||||
@@ -4,23 +4,26 @@ import emoji from 'markdown-it-emoji'
|
||||
import math from '@rokt33r/markdown-it-math'
|
||||
import mdurl from 'mdurl'
|
||||
import smartArrows from 'markdown-it-smartarrows'
|
||||
import markdownItTocAndAnchor from '@hikerpig/markdown-it-toc-and-anchor'
|
||||
import _ from 'lodash'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import katex from 'katex'
|
||||
import { lastFindInArray } from './utils'
|
||||
import { escapeHtmlCharacters, lastFindInArray } from './utils'
|
||||
|
||||
function createGutter (str, firstLineNumber) {
|
||||
function createGutter(str, firstLineNumber) {
|
||||
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
||||
const lastLineNumber = (str.match(/\n/g) || []).length + firstLineNumber - 1
|
||||
const lines = []
|
||||
for (let i = firstLineNumber; i <= lastLineNumber; i++) {
|
||||
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 {
|
||||
constructor (options = {}) {
|
||||
constructor(options = {}) {
|
||||
const config = ConfigManager.get()
|
||||
const defaultOptions = {
|
||||
typographer: config.preview.smartQuotes,
|
||||
@@ -28,7 +31,8 @@ class Markdown {
|
||||
html: true,
|
||||
xhtmlOut: true,
|
||||
breaks: config.preview.breaks,
|
||||
sanitize: 'STRICT'
|
||||
sanitize: 'STRICT',
|
||||
onFence: () => {}
|
||||
}
|
||||
|
||||
const updatedOptions = Object.assign(defaultOptions, options)
|
||||
@@ -36,29 +40,129 @@ class Markdown {
|
||||
this.md.linkify.set({ fuzzyLink: false })
|
||||
|
||||
if (updatedOptions.sanitize !== 'NONE') {
|
||||
const allowedTags = ['iframe', 'input', '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 allowedTags = [
|
||||
'iframe',
|
||||
'input',
|
||||
'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 = [
|
||||
'abbr', 'accept', 'accept-charset',
|
||||
'accesskey', 'action', 'align', 'alt', 'axis',
|
||||
'border', 'cellpadding', 'cellspacing', 'char',
|
||||
'charoff', 'charset', 'checked',
|
||||
'clear', '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'
|
||||
'abbr',
|
||||
'accept',
|
||||
'accept-charset',
|
||||
'accesskey',
|
||||
'action',
|
||||
'align',
|
||||
'alt',
|
||||
'axis',
|
||||
'border',
|
||||
'cellpadding',
|
||||
'cellspacing',
|
||||
'char',
|
||||
'charoff',
|
||||
'charset',
|
||||
'checked',
|
||||
'clear',
|
||||
'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') {
|
||||
@@ -71,20 +175,20 @@ class Markdown {
|
||||
allowedTags,
|
||||
allowedAttributes: {
|
||||
'*': allowedAttributes,
|
||||
'a': ['href'],
|
||||
'div': ['itemscope', 'itemtype'],
|
||||
'blockquote': ['cite'],
|
||||
'del': ['cite'],
|
||||
'ins': ['cite'],
|
||||
'q': ['cite'],
|
||||
'img': ['src', 'width', 'height'],
|
||||
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
||||
'input': ['type', 'id', 'checked']
|
||||
a: ['href'],
|
||||
div: ['itemscope', 'itemtype'],
|
||||
blockquote: ['cite'],
|
||||
del: ['cite'],
|
||||
ins: ['cite'],
|
||||
q: ['cite'],
|
||||
img: ['src', 'width', 'height'],
|
||||
iframe: ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
||||
input: ['type', 'id', 'checked']
|
||||
},
|
||||
allowedIframeHostnames: ['www.youtube.com'],
|
||||
selfClosing: [ 'img', 'br', 'hr', 'input' ],
|
||||
allowedSchemes: [ 'http', 'https', 'ftp', 'mailto' ],
|
||||
allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ],
|
||||
selfClosing: ['img', 'br', 'hr', 'input'],
|
||||
allowedSchemes: ['http', 'https', 'ftp', 'mailto'],
|
||||
allowedSchemesAppliedToAttributes: ['href', 'src', 'cite'],
|
||||
allowProtocolRelative: true
|
||||
})
|
||||
}
|
||||
@@ -97,7 +201,7 @@ class Markdown {
|
||||
inlineClose: config.preview.latexInlineClose,
|
||||
blockOpen: config.preview.latexBlockOpen,
|
||||
blockClose: config.preview.latexBlockClose,
|
||||
inlineRenderer: function (str) {
|
||||
inlineRenderer: function(str) {
|
||||
let output = ''
|
||||
try {
|
||||
output = katex.renderToString(str.trim())
|
||||
@@ -106,7 +210,7 @@ class Markdown {
|
||||
}
|
||||
return output
|
||||
},
|
||||
blockRenderer: function (str) {
|
||||
blockRenderer: function(str) {
|
||||
let output = ''
|
||||
try {
|
||||
output = katex.renderToString(str.trim(), { displayMode: true })
|
||||
@@ -123,96 +227,177 @@ class Markdown {
|
||||
slugify: require('./slugify')
|
||||
})
|
||||
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-sub'))
|
||||
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-frontmatter'))
|
||||
|
||||
this.md.use(require('./markdown-it-fence'), {
|
||||
chart: token => {
|
||||
if (token.parameters.hasOwnProperty('yaml')) {
|
||||
token.parameters.format = 'yaml'
|
||||
}
|
||||
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="chart" data-height="${token.parameters.height}" data-format="${token.parameters.format || 'json'}">${token.content}</div>
|
||||
</pre>`
|
||||
},
|
||||
flowchart: token => {
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="flowchart" data-height="${token.parameters.height}">${token.content}</div>
|
||||
</pre>`
|
||||
},
|
||||
gallery: token => {
|
||||
const content = token.content.split('\n').slice(0, -1).map(line => {
|
||||
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
|
||||
if (match) {
|
||||
return mdurl.encode(match[1])
|
||||
} else {
|
||||
return mdurl.encode(line)
|
||||
this.md.use(
|
||||
require('./markdown-it-fence'),
|
||||
{
|
||||
chart: token => {
|
||||
if (token.parameters.hasOwnProperty('yaml')) {
|
||||
token.parameters.format = 'yaml'
|
||||
}
|
||||
}).join('\n')
|
||||
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="gallery" data-autoplay="${token.parameters.autoplay}" data-height="${token.parameters.height}">${content}</div>
|
||||
</pre>`
|
||||
updatedOptions.onFence('chart', token.parameters.format)
|
||||
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="chart" data-height="${
|
||||
token.parameters.height
|
||||
}" data-format="${token.parameters.format || 'json'}">${
|
||||
token.content
|
||||
}</div>
|
||||
</pre>`
|
||||
},
|
||||
flowchart: token => {
|
||||
updatedOptions.onFence('flowchart')
|
||||
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="flowchart" data-height="${token.parameters.height}">${
|
||||
token.content
|
||||
}</div>
|
||||
</pre>`
|
||||
},
|
||||
gallery: token => {
|
||||
const content = token.content
|
||||
.split('\n')
|
||||
.slice(0, -1)
|
||||
.map(line => {
|
||||
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
|
||||
if (match) {
|
||||
return mdurl.encode(match[1])
|
||||
} else {
|
||||
return mdurl.encode(line)
|
||||
}
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="gallery" data-autoplay="${
|
||||
token.parameters.autoplay
|
||||
}" data-height="${token.parameters.height}">${content}</div>
|
||||
</pre>`
|
||||
},
|
||||
mermaid: token => {
|
||||
updatedOptions.onFence('mermaid')
|
||||
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="mermaid" data-height="${token.parameters.height}">${
|
||||
token.content
|
||||
}</div>
|
||||
</pre>`
|
||||
},
|
||||
sequence: token => {
|
||||
updatedOptions.onFence('sequence')
|
||||
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="sequence" data-height="${token.parameters.height}">${
|
||||
token.content
|
||||
}</div>
|
||||
</pre>`
|
||||
}
|
||||
},
|
||||
mermaid: token => {
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
token => {
|
||||
updatedOptions.onFence('code', token.langType)
|
||||
|
||||
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="mermaid" data-height="${token.parameters.height}">${token.content}</div>
|
||||
</pre>`
|
||||
},
|
||||
sequence: token => {
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="sequence" data-height="${token.parameters.height}">${token.content}</div>
|
||||
${createGutter(token.content, token.firstLineNumber)}
|
||||
<code class="${token.langType}">${token.content}</code>
|
||||
</pre>`
|
||||
}
|
||||
}, token => {
|
||||
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
${createGutter(token.content, token.firstLineNumber)}
|
||||
<code class="${token.langType}">${token.content}</code>
|
||||
</pre>`
|
||||
})
|
||||
)
|
||||
|
||||
const deflate = require('markdown-it-plantuml/lib/deflate')
|
||||
this.md.use(require('markdown-it-plantuml'), {
|
||||
generateSource: function (umlCode) {
|
||||
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
|
||||
const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg'
|
||||
const s = unescape(encodeURIComponent(umlCode))
|
||||
const zippedCode = deflate.encode64(
|
||||
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
|
||||
)
|
||||
return `${serverAddress}/${zippedCode}`
|
||||
}
|
||||
const plantuml = require('markdown-it-plantuml')
|
||||
const plantUmlStripTrailingSlash = url =>
|
||||
url.endsWith('/') ? url.slice(0, -1) : url
|
||||
const plantUmlServerAddress = plantUmlStripTrailingSlash(
|
||||
config.preview.plantUMLServerAddress
|
||||
)
|
||||
const parsePlantUml = function(umlCode, openMarker, closeMarker, type) {
|
||||
const s = unescape(encodeURIComponent(umlCode))
|
||||
const zippedCode = deflate.encode64(
|
||||
deflate.zip_deflate(`${openMarker}\n${s}\n${closeMarker}`, 9)
|
||||
)
|
||||
return `${plantUmlServerAddress}/${type}/${zippedCode}`
|
||||
}
|
||||
|
||||
this.md.use(plantuml, {
|
||||
generateSource: umlCode =>
|
||||
parsePlantUml(umlCode, '@startuml', '@enduml', 'svg')
|
||||
})
|
||||
|
||||
// Ditaa support
|
||||
this.md.use(require('markdown-it-plantuml'), {
|
||||
// Ditaa support. PlantUML server doesn't support Ditaa in SVG, so we set the format as PNG at the moment.
|
||||
this.md.use(plantuml, {
|
||||
openMarker: '@startditaa',
|
||||
closeMarker: '@endditaa',
|
||||
generateSource: function (umlCode) {
|
||||
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
|
||||
// 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))
|
||||
const zippedCode = deflate.encode64(
|
||||
deflate.zip_deflate(`@startditaa\n${s}\n@endditaa`, 9)
|
||||
)
|
||||
return `${serverAddress}/${zippedCode}`
|
||||
}
|
||||
generateSource: umlCode =>
|
||||
parsePlantUml(umlCode, '@startditaa', '@endditaa', 'png')
|
||||
})
|
||||
|
||||
// Mindmap support
|
||||
this.md.use(plantuml, {
|
||||
openMarker: '@startmindmap',
|
||||
closeMarker: '@endmindmap',
|
||||
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
|
||||
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 nextLine = startLine + 1
|
||||
const terminatorRules = state.md.block.ruler.getRules('paragraph')
|
||||
@@ -222,10 +407,14 @@ class Markdown {
|
||||
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
|
||||
// this would be a code block normally, but after paragraph
|
||||
// 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
|
||||
if (state.sCount[nextLine] < 0) { continue }
|
||||
if (state.sCount[nextLine] < 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Some tags can terminate paragraph without empty line.
|
||||
terminate = false
|
||||
@@ -235,10 +424,14 @@ class Markdown {
|
||||
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
|
||||
|
||||
@@ -248,18 +441,31 @@ class Markdown {
|
||||
if (state.parentType === 'list') {
|
||||
const match = content.match(/^\[( |x)\] ?(.+)/i)
|
||||
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.attrs) {
|
||||
liToken.attrs = []
|
||||
}
|
||||
if (config.preview.lineThroughCheckbox) {
|
||||
liToken.attrs.push(['class', `taskListItem${match[1] !== ' ' ? ' checked' : ''}`])
|
||||
liToken.attrs.push([
|
||||
'class',
|
||||
`taskListItem${match[1] !== ' ' ? ' checked' : ''}`
|
||||
])
|
||||
} else {
|
||||
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>`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,6 +479,16 @@ class Markdown {
|
||||
return true
|
||||
})
|
||||
|
||||
this.md.renderer.rules.code_inline = function(tokens, idx) {
|
||||
const token = tokens[idx]
|
||||
|
||||
return (
|
||||
'<code class="inline">' +
|
||||
escapeHtmlCharacters(token.content) +
|
||||
'</code>'
|
||||
)
|
||||
}
|
||||
|
||||
if (config.preview.smartArrows) {
|
||||
this.md.use(smartArrows)
|
||||
}
|
||||
@@ -280,7 +496,7 @@ class Markdown {
|
||||
// Add line number attribute for scrolling
|
||||
const originalRender = this.md.renderer.render
|
||||
this.md.renderer.render = (tokens, options, env) => {
|
||||
tokens.forEach((token) => {
|
||||
tokens.forEach(token => {
|
||||
switch (token.type) {
|
||||
case 'blockquote_open':
|
||||
case 'dd_open':
|
||||
@@ -301,7 +517,7 @@ class Markdown {
|
||||
window.md = this.md
|
||||
}
|
||||
|
||||
render (content) {
|
||||
render(content) {
|
||||
if (!_.isString(content)) content = ''
|
||||
return this.md.render(content)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @param {string} input
|
||||
* @return {string}
|
||||
*/
|
||||
export function strip (input) {
|
||||
export function strip(input) {
|
||||
let output = input
|
||||
try {
|
||||
output = output
|
||||
|
||||
@@ -4,12 +4,22 @@ 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_ALLNOTE')
|
||||
|
||||
let tags = []
|
||||
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
|
||||
if (
|
||||
config.ui.tagNewNoteWithFilteringTags &&
|
||||
location.pathname.match(/\/tags/)
|
||||
) {
|
||||
tags = params.tagname.split(' ')
|
||||
}
|
||||
|
||||
@@ -29,25 +39,40 @@ export function createMarkdownNote (storage, folder, dispatch, location, params,
|
||||
note: note
|
||||
})
|
||||
|
||||
dispatch(push({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({ key: noteHash })
|
||||
}))
|
||||
dispatch(
|
||||
push({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({ key: noteHash })
|
||||
})
|
||||
)
|
||||
ee.emit('list:jump', noteHash)
|
||||
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_ALLNOTE')
|
||||
|
||||
let tags = []
|
||||
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
|
||||
if (
|
||||
config.ui.tagNewNoteWithFilteringTags &&
|
||||
location.pathname.match(/\/tags/)
|
||||
) {
|
||||
tags = params.tagname.split(' ')
|
||||
}
|
||||
|
||||
const defaultLanguage = config.editor.snippetDefaultLanguage === 'Auto Detect' ? null : config.editor.snippetDefaultLanguage
|
||||
const defaultLanguage =
|
||||
config.editor.snippetDefaultLanguage === 'Auto Detect'
|
||||
? null
|
||||
: config.editor.snippetDefaultLanguage
|
||||
|
||||
return dataApi
|
||||
.createNote(storage, {
|
||||
@@ -71,10 +96,12 @@ export function createSnippetNote (storage, folder, dispatch, location, params,
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
dispatch(push({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({ key: noteHash })
|
||||
}))
|
||||
dispatch(
|
||||
push({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({ key: noteHash })
|
||||
})
|
||||
)
|
||||
ee.emit('list:jump', noteHash)
|
||||
ee.emit('detail:focus')
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import consts from 'browser/lib/consts'
|
||||
import isString from 'lodash/isString'
|
||||
|
||||
export default function normalizeEditorFontFamily (fontFamily) {
|
||||
export default function normalizeEditorFontFamily(fontFamily) {
|
||||
const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
|
||||
return isString(fontFamily) && fontFamily.length > 0
|
||||
? [fontFamily].concat(defaultEditorFontFamily).join(', ')
|
||||
|
||||
@@ -1,31 +1,38 @@
|
||||
import _ from 'lodash'
|
||||
|
||||
export default function searchFromNotes (notes, search) {
|
||||
export default function searchFromNotes(notes, search) {
|
||||
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
|
||||
searchBlocks.forEach((block) => {
|
||||
searchBlocks.forEach(block => {
|
||||
foundNotes = findByWordOrTag(foundNotes, block)
|
||||
})
|
||||
return foundNotes
|
||||
}
|
||||
|
||||
function findByWordOrTag (notes, block) {
|
||||
function findByWordOrTag(notes, block) {
|
||||
let tag = block
|
||||
if (tag.match(/^#.+/)) {
|
||||
tag = tag.match(/#(.+)/)[1]
|
||||
}
|
||||
const tagRegExp = new RegExp(_.escapeRegExp(tag), 'i')
|
||||
const wordRegExp = new RegExp(_.escapeRegExp(block), 'i')
|
||||
return notes.filter((note) => {
|
||||
if (_.isArray(note.tags) && note.tags.some((_tag) => _tag.match(tagRegExp))) {
|
||||
return notes.filter(note => {
|
||||
if (_.isArray(note.tags) && note.tags.some(_tag => _tag.match(tagRegExp))) {
|
||||
return true
|
||||
}
|
||||
if (note.type === 'SNIPPET_NOTE') {
|
||||
return note.description.match(wordRegExp) || note.snippets.some((snippet) => {
|
||||
return snippet.name.match(wordRegExp) || snippet.content.match(wordRegExp)
|
||||
})
|
||||
return (
|
||||
note.description.match(wordRegExp) ||
|
||||
note.snippets.some(snippet => {
|
||||
return (
|
||||
snippet.name.match(wordRegExp) || snippet.content.match(wordRegExp)
|
||||
)
|
||||
})
|
||||
)
|
||||
} else if (note.type === 'MARKDOWN_NOTE') {
|
||||
return note.content.match(wordRegExp)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
module.exports = function slugify (title) {
|
||||
module.exports = function slugify(title) {
|
||||
const slug = encodeURI(
|
||||
title.trim()
|
||||
title
|
||||
.trim()
|
||||
.replace(/^\s+/, '')
|
||||
.replace(/\s+$/, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`]/g, '')
|
||||
.replace(
|
||||
/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`]/g,
|
||||
''
|
||||
)
|
||||
)
|
||||
|
||||
return slug
|
||||
|
||||
@@ -12,19 +12,19 @@ const MILLISECONDS_TILL_LIVECHECK = 500
|
||||
let dictionary = null
|
||||
let self
|
||||
|
||||
function getAvailableDictionaries () {
|
||||
function getAvailableDictionaries() {
|
||||
return [
|
||||
{label: i18n.__('Spellcheck disabled'), value: SPELLCHECK_DISABLED},
|
||||
{label: i18n.__('English'), value: 'en_GB'},
|
||||
{label: i18n.__('German'), value: 'de_DE'},
|
||||
{label: i18n.__('French'), value: 'fr_FR'}
|
||||
{ label: i18n.__('Spellcheck disabled'), value: SPELLCHECK_DISABLED },
|
||||
{ label: i18n.__('English'), value: 'en_GB' },
|
||||
{ label: i18n.__('German'), value: 'de_DE' },
|
||||
{ label: i18n.__('French'), value: 'fr_FR' }
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Only to be used in the tests :)
|
||||
*/
|
||||
function setDictionaryForTestsOnly (newDictionary) {
|
||||
function setDictionaryForTestsOnly(newDictionary) {
|
||||
dictionary = newDictionary
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ function setDictionaryForTestsOnly (newDictionary) {
|
||||
* @param {Codemirror} editor CodeMirror-Editor
|
||||
* @param {String} lang on of the values from getAvailableDictionaries()-Method
|
||||
*/
|
||||
function setLanguage (editor, lang) {
|
||||
function setLanguage(editor, lang) {
|
||||
self = this
|
||||
dictionary = null
|
||||
|
||||
@@ -50,8 +50,7 @@ function setLanguage (editor, lang) {
|
||||
dictionary = new Typo(lang, false, false, {
|
||||
dictionaryPath: DICTIONARY_PATH,
|
||||
asyncLoad: true,
|
||||
loadedCallback: () =>
|
||||
checkWholeDocument(editor)
|
||||
loadedCallback: () => checkWholeDocument(editor)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -60,12 +59,12 @@ function setLanguage (editor, lang) {
|
||||
* Checks the whole content of the editor for typos
|
||||
* @param {Codemirror} editor CodeMirror-Editor
|
||||
*/
|
||||
function checkWholeDocument (editor) {
|
||||
function checkWholeDocument(editor) {
|
||||
const lastLine = editor.lineCount() - 1
|
||||
const textOfLastLine = editor.getLine(lastLine) || ''
|
||||
const lastChar = textOfLastLine.length
|
||||
const from = {line: 0, ch: 0}
|
||||
const to = {line: lastLine, ch: lastChar}
|
||||
const from = { line: 0, ch: 0 }
|
||||
const to = { line: lastLine, ch: lastChar }
|
||||
checkMultiLineRange(editor, from, to)
|
||||
}
|
||||
|
||||
@@ -75,15 +74,18 @@ function checkWholeDocument (editor) {
|
||||
* @param {line, ch} from starting position of the spellcheck
|
||||
* @param {line, ch} to end position of the spellcheck
|
||||
*/
|
||||
function checkMultiLineRange (editor, from, to) {
|
||||
function sortRange (pos1, pos2) {
|
||||
if (pos1.line > pos2.line || (pos1.line === pos2.line && pos1.ch > pos2.ch)) {
|
||||
return {from: pos2, to: pos1}
|
||||
function checkMultiLineRange(editor, from, to) {
|
||||
function sortRange(pos1, pos2) {
|
||||
if (
|
||||
pos1.line > pos2.line ||
|
||||
(pos1.line === pos2.line && pos1.ch > pos2.ch)
|
||||
) {
|
||||
return { from: pos2, to: pos1 }
|
||||
}
|
||||
return {from: pos1, to: pos2}
|
||||
return { from: pos1, to: pos2 }
|
||||
}
|
||||
|
||||
const {from: smallerPos, to: higherPos} = sortRange(from, to)
|
||||
const { from: smallerPos, to: higherPos } = sortRange(from, to)
|
||||
for (let l = smallerPos.line; l <= higherPos.line; l++) {
|
||||
const line = editor.getLine(l) || ''
|
||||
let w = 0
|
||||
@@ -95,9 +97,9 @@ function checkMultiLineRange (editor, from, to) {
|
||||
wEnd = higherPos.ch
|
||||
}
|
||||
while (w <= wEnd) {
|
||||
const wordRange = editor.findWordAt({line: l, ch: w})
|
||||
const wordRange = editor.findWordAt({ line: l, ch: w })
|
||||
self.checkWord(editor, wordRange)
|
||||
w += (wordRange.head.ch - wordRange.anchor.ch) + 1
|
||||
w += wordRange.head.ch - wordRange.anchor.ch + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,13 +112,15 @@ function checkMultiLineRange (editor, from, to) {
|
||||
* @param wordRange Object specifying the range that should be checked.
|
||||
* Having the following structure: <code>{anchor: {line: integer, ch: integer}, head: {line: integer, ch: integer}}</code>
|
||||
*/
|
||||
function checkWord (editor, wordRange) {
|
||||
function checkWord(editor, wordRange) {
|
||||
const word = editor.getRange(wordRange.anchor, wordRange.head)
|
||||
if (word == null || word.length <= 3) {
|
||||
return
|
||||
}
|
||||
if (!dictionary.check(word)) {
|
||||
editor.markText(wordRange.anchor, wordRange.head, {className: styles[CSS_ERROR_CLASS]})
|
||||
editor.markText(wordRange.anchor, wordRange.head, {
|
||||
className: styles[CSS_ERROR_CLASS]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,32 +130,40 @@ function checkWord (editor, wordRange) {
|
||||
* @param fromChangeObject codeMirror changeObject describing the start of the editing
|
||||
* @param toChangeObject codeMirror changeObject describing the end of the editing
|
||||
*/
|
||||
function checkChangeRange (editor, fromChangeObject, toChangeObject) {
|
||||
function checkChangeRange(editor, fromChangeObject, toChangeObject) {
|
||||
/**
|
||||
* Calculate the smallest respectively largest position as a start, resp. end, position and return it
|
||||
* @param start CodeMirror change object
|
||||
* @param end CodeMirror change object
|
||||
* @returns {{start: {line: *, ch: *}, end: {line: *, ch: *}}}
|
||||
*/
|
||||
function getStartAndEnd (start, end) {
|
||||
function getStartAndEnd(start, end) {
|
||||
const possiblePositions = [start.from, start.to, end.from, end.to]
|
||||
let smallest = start.from
|
||||
let biggest = end.to
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
return {start: smallest, end: biggest}
|
||||
return { start: smallest, end: biggest }
|
||||
}
|
||||
|
||||
if (dictionary === null || editor == null) { return }
|
||||
if (dictionary === null || editor == null) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const {start, end} = getStartAndEnd(fromChangeObject, toChangeObject)
|
||||
const { start, end } = getStartAndEnd(fromChangeObject, toChangeObject)
|
||||
|
||||
// Expand the range to include words after/before whitespaces
|
||||
start.ch = Math.max(start.ch - 1, 0)
|
||||
@@ -165,29 +177,40 @@ function checkChangeRange (editor, fromChangeObject, toChangeObject) {
|
||||
|
||||
self.checkMultiLineRange(editor, start, end)
|
||||
} catch (e) {
|
||||
console.info('Error during the spell check. It might be due to problems figuring out the range of the new text..', e)
|
||||
console.info(
|
||||
'Error during the spell check. It might be due to problems figuring out the range of the new text..',
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function saveLiveSpellCheckFrom (changeObject) {
|
||||
function saveLiveSpellCheckFrom(changeObject) {
|
||||
liveSpellCheckFrom = changeObject
|
||||
}
|
||||
let liveSpellCheckFrom
|
||||
const debouncedSpellCheckLeading = _.debounce(saveLiveSpellCheckFrom, MILLISECONDS_TILL_LIVECHECK, {
|
||||
'leading': true,
|
||||
'trailing': false
|
||||
})
|
||||
const debouncedSpellCheck = _.debounce(checkChangeRange, MILLISECONDS_TILL_LIVECHECK, {
|
||||
'leading': false,
|
||||
'trailing': true
|
||||
})
|
||||
const debouncedSpellCheckLeading = _.debounce(
|
||||
saveLiveSpellCheckFrom,
|
||||
MILLISECONDS_TILL_LIVECHECK,
|
||||
{
|
||||
leading: true,
|
||||
trailing: false
|
||||
}
|
||||
)
|
||||
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
|
||||
* @param {Codemirror} editor CodeMirror-Editor
|
||||
* @param changeObject codeMirror changeObject
|
||||
*/
|
||||
function handleChange (editor, changeObject) {
|
||||
function handleChange(editor, changeObject) {
|
||||
if (dictionary === null) {
|
||||
return
|
||||
}
|
||||
@@ -201,7 +224,7 @@ function handleChange (editor, changeObject) {
|
||||
* @param word word to be checked
|
||||
* @returns {String[]} Array of suggestions
|
||||
*/
|
||||
function getSpellingSuggestion (word) {
|
||||
function getSpellingSuggestion(word) {
|
||||
if (dictionary == null || word == null) {
|
||||
return []
|
||||
}
|
||||
@@ -211,7 +234,7 @@ function getSpellingSuggestion (word) {
|
||||
/**
|
||||
* Returns the name of the CSS class used for errors
|
||||
*/
|
||||
function getCSSClassName () {
|
||||
function getCSSClassName() {
|
||||
return styles[CSS_ERROR_CLASS]
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
]
|
||||
@@ -1,4 +1,4 @@
|
||||
export function lastFindInArray (array, callback) {
|
||||
export function lastFindInArray(array, callback) {
|
||||
for (let i = array.length - 1; i >= 0; --i) {
|
||||
if (callback(array[i], i, array)) {
|
||||
return array[i]
|
||||
@@ -6,7 +6,7 @@ export function lastFindInArray (array, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
export function escapeHtmlCharacters (
|
||||
export function escapeHtmlCharacters(
|
||||
html,
|
||||
opt = { detectCodeBlock: false, skipSingleQuote: false }
|
||||
) {
|
||||
@@ -115,7 +115,7 @@ export function escapeHtmlCharacters (
|
||||
return html
|
||||
}
|
||||
|
||||
export function isObjectEqual (a, b) {
|
||||
export function isObjectEqual(a, b) {
|
||||
const aProps = Object.getOwnPropertyNames(a)
|
||||
const bProps = Object.getOwnPropertyNames(b)
|
||||
|
||||
@@ -132,13 +132,30 @@ export function isObjectEqual (a, b) {
|
||||
return true
|
||||
}
|
||||
|
||||
export function isMarkdownTitleURL (str) {
|
||||
return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
|
||||
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 {
|
||||
lastFindInArray,
|
||||
escapeHtmlCharacters,
|
||||
isObjectEqual,
|
||||
isMarkdownTitleURL
|
||||
isMarkdownTitleURL,
|
||||
humanFileSize
|
||||
}
|
||||
|
||||
49
browser/lib/wakatime-plugin.js
Normal file
49
browser/lib/wakatime-plugin.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import config from 'browser/main/lib/ConfigManager'
|
||||
const exec = require('child_process').exec
|
||||
const path = require('path')
|
||||
let lastHeartbeat = 0
|
||||
|
||||
function sendWakatimeHeartBeat(
|
||||
storagePath,
|
||||
noteKey,
|
||||
storageName,
|
||||
{ isWrite, hasFileChanges, isFileChange }
|
||||
) {
|
||||
if (
|
||||
config.get().wakatime.isActive &&
|
||||
!!config.get().wakatime.key &&
|
||||
(new Date().getTime() - lastHeartbeat > 120000 || isFileChange)
|
||||
) {
|
||||
const notePath = path.join(storagePath, 'notes', noteKey + '.cson')
|
||||
|
||||
if (!isWrite && !hasFileChanges && !isFileChange) {
|
||||
return
|
||||
}
|
||||
|
||||
lastHeartbeat = new Date()
|
||||
const wakatimeKey = config.get().wakatime.key
|
||||
if (wakatimeKey) {
|
||||
exec(
|
||||
`wakatime --file ${notePath} --project '${storageName}' --key ${wakatimeKey} --plugin Boostnote-wakatime`,
|
||||
(error, stdOut, stdErr) => {
|
||||
if (error) {
|
||||
console.log(error)
|
||||
lastHeartbeat = 0
|
||||
} else {
|
||||
console.log(
|
||||
'wakatime',
|
||||
'isWrite',
|
||||
isWrite,
|
||||
'hasChanges',
|
||||
hasFileChanges,
|
||||
'isFileChange',
|
||||
isFileChange
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { sendWakatimeHeartBeat }
|
||||
@@ -24,23 +24,16 @@ body[data-theme="dark"]
|
||||
.empty-message
|
||||
color $ui-dark-inactive-text-color
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
border-left 1px solid $ui-solarized-dark-borderColor
|
||||
.empty-message
|
||||
color $ui-solarized-dark-text-color
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.root
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
border-left 1px solid get-theme-var(theme, 'borderColor')
|
||||
.empty-message
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.root
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
border-left 1px solid $ui-monokai-borderColor
|
||||
.empty-message
|
||||
color $ui-monokai-text-color
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
border-left 1px solid $ui-dracula-borderColor
|
||||
.empty-message
|
||||
color $ui-dracula-text-color
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
@@ -6,7 +6,7 @@ import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class FolderSelect extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -16,24 +16,27 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.value = this.props.value
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
componentDidUpdate() {
|
||||
this.value = this.props.value
|
||||
}
|
||||
|
||||
handleClick (e) {
|
||||
this.setState({
|
||||
status: 'SEARCH',
|
||||
optionIndex: -1
|
||||
}, () => {
|
||||
this.refs.search.focus()
|
||||
})
|
||||
handleClick(e) {
|
||||
this.setState(
|
||||
{
|
||||
status: 'SEARCH',
|
||||
optionIndex: -1
|
||||
},
|
||||
() => {
|
||||
this.refs.search.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
handleFocus (e) {
|
||||
handleFocus(e) {
|
||||
if (this.state.status === 'IDLE') {
|
||||
this.setState({
|
||||
status: 'FOCUS'
|
||||
@@ -41,7 +44,7 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleBlur (e) {
|
||||
handleBlur(e) {
|
||||
if (this.state.status === 'FOCUS') {
|
||||
this.setState({
|
||||
status: 'IDLE'
|
||||
@@ -49,40 +52,49 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
handleKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
case 13:
|
||||
if (this.state.status === 'FOCUS') {
|
||||
this.setState({
|
||||
status: 'SEARCH',
|
||||
optionIndex: -1
|
||||
}, () => {
|
||||
this.refs.search.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'SEARCH',
|
||||
optionIndex: -1
|
||||
},
|
||||
() => {
|
||||
this.refs.search.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
break
|
||||
case 40:
|
||||
case 38:
|
||||
if (this.state.status === 'FOCUS') {
|
||||
this.setState({
|
||||
status: 'SEARCH',
|
||||
optionIndex: 0
|
||||
}, () => {
|
||||
this.refs.search.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'SEARCH',
|
||||
optionIndex: 0
|
||||
},
|
||||
() => {
|
||||
this.refs.search.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
break
|
||||
case 9:
|
||||
if (e.shiftKey) {
|
||||
e.preventDefault()
|
||||
const tabbable = document.querySelectorAll('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]
|
||||
const tabbable = document.querySelectorAll(
|
||||
'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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchInputBlur (e) {
|
||||
handleSearchInputBlur(e) {
|
||||
if (e.relatedTarget !== this.refs.root) {
|
||||
this.setState({
|
||||
status: 'IDLE'
|
||||
@@ -90,14 +102,17 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchInputChange (e) {
|
||||
handleSearchInputChange(e) {
|
||||
const { folders } = this.props
|
||||
const search = this.refs.search.value
|
||||
const optionIndex = search.length > 0
|
||||
? _.findIndex(folders, (folder) => {
|
||||
return folder.name.match(new RegExp('^' + _.escapeRegExp(search), 'i'))
|
||||
})
|
||||
: -1
|
||||
const optionIndex =
|
||||
search.length > 0
|
||||
? _.findIndex(folders, folder => {
|
||||
return folder.name.match(
|
||||
new RegExp('^' + _.escapeRegExp(search), 'i')
|
||||
)
|
||||
})
|
||||
: -1
|
||||
|
||||
this.setState({
|
||||
search: this.refs.search.value,
|
||||
@@ -105,7 +120,7 @@ class FolderSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleSearchInputKeyDown (e) {
|
||||
handleSearchInputKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
case 40:
|
||||
e.stopPropagation()
|
||||
@@ -121,15 +136,18 @@ class FolderSelect extends React.Component {
|
||||
break
|
||||
case 27:
|
||||
e.stopPropagation()
|
||||
this.setState({
|
||||
status: 'FOCUS'
|
||||
}, () => {
|
||||
this.refs.root.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'FOCUS'
|
||||
},
|
||||
() => {
|
||||
this.refs.root.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
nextOption () {
|
||||
nextOption() {
|
||||
let { optionIndex } = this.state
|
||||
const { folders } = this.props
|
||||
|
||||
@@ -141,7 +159,7 @@ class FolderSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
previousOption () {
|
||||
previousOption() {
|
||||
const { folders } = this.props
|
||||
let { optionIndex } = this.state
|
||||
|
||||
@@ -153,46 +171,52 @@ class FolderSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
selectOption () {
|
||||
selectOption() {
|
||||
const { folders } = this.props
|
||||
const optionIndex = this.state.optionIndex
|
||||
|
||||
const folder = folders[optionIndex]
|
||||
if (folder != null) {
|
||||
this.setState({
|
||||
status: 'FOCUS'
|
||||
}, () => {
|
||||
this.setValue(folder.key)
|
||||
this.refs.root.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'FOCUS'
|
||||
},
|
||||
() => {
|
||||
this.setValue(folder.key)
|
||||
this.refs.root.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
handleOptionClick (storageKey, folderKey) {
|
||||
return (e) => {
|
||||
handleOptionClick(storageKey, folderKey) {
|
||||
return e => {
|
||||
e.stopPropagation()
|
||||
this.setState({
|
||||
status: 'FOCUS'
|
||||
}, () => {
|
||||
this.setValue(storageKey + '-' + folderKey)
|
||||
this.refs.root.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'FOCUS'
|
||||
},
|
||||
() => {
|
||||
this.setValue(storageKey + '-' + folderKey)
|
||||
this.refs.root.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
setValue (value) {
|
||||
setValue(value) {
|
||||
this.value = value
|
||||
this.props.onChange()
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { className, data, value } = this.props
|
||||
const splitted = value.split('-')
|
||||
const storageKey = splitted.shift()
|
||||
const folderKey = splitted.shift()
|
||||
let options = []
|
||||
data.storageMap.forEach((storage, index) => {
|
||||
storage.folders.forEach((folder) => {
|
||||
storage.folders.forEach(folder => {
|
||||
options.push({
|
||||
storage: storage,
|
||||
folder: folder
|
||||
@@ -200,68 +224,78 @@ class FolderSelect extends React.Component {
|
||||
})
|
||||
})
|
||||
|
||||
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
||||
const currentOption = options.filter(
|
||||
option =>
|
||||
option.storage.key === storageKey && option.folder.key === folderKey
|
||||
)[0]
|
||||
|
||||
if (this.state.search.trim().length > 0) {
|
||||
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
|
||||
.map((option, index) => {
|
||||
return (
|
||||
<div styleName={index === this.state.optionIndex
|
||||
const optionList = options.map((option, index) => {
|
||||
return (
|
||||
<div
|
||||
styleName={
|
||||
index === this.state.optionIndex
|
||||
? 'search-optionList-item--active'
|
||||
: 'search-optionList-item'
|
||||
}
|
||||
key={option.storage.key + '-' + option.folder.key}
|
||||
onClick={(e) => this.handleOptionClick(option.storage.key, option.folder.key)(e)}
|
||||
}
|
||||
key={option.storage.key + '-' + option.folder.key}
|
||||
onClick={e =>
|
||||
this.handleOptionClick(option.storage.key, option.folder.key)(e)
|
||||
}
|
||||
>
|
||||
<span
|
||||
styleName='search-optionList-item-name'
|
||||
style={{ borderColor: option.folder.color }}
|
||||
>
|
||||
<span styleName='search-optionList-item-name'
|
||||
style={{borderColor: option.folder.color}}
|
||||
>
|
||||
{option.folder.name}
|
||||
<span styleName='search-optionList-item-name-surfix'>in {option.storage.name}</span>
|
||||
{option.folder.name}
|
||||
<span styleName='search-optionList-item-name-surfix'>
|
||||
in {option.storage.name}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={_.isString(className)
|
||||
? 'FolderSelect ' + className
|
||||
: 'FolderSelect'
|
||||
<div
|
||||
className={
|
||||
_.isString(className) ? 'FolderSelect ' + className : 'FolderSelect'
|
||||
}
|
||||
styleName={this.state.status === 'SEARCH'
|
||||
? 'root--search'
|
||||
: this.state.status === 'FOCUS'
|
||||
? 'root--focus'
|
||||
: 'root'
|
||||
styleName={
|
||||
this.state.status === 'SEARCH'
|
||||
? 'root--search'
|
||||
: this.state.status === 'FOCUS'
|
||||
? 'root--focus'
|
||||
: 'root'
|
||||
}
|
||||
ref='root'
|
||||
tabIndex='0'
|
||||
onClick={(e) => this.handleClick(e)}
|
||||
onFocus={(e) => this.handleFocus(e)}
|
||||
onBlur={(e) => this.handleBlur(e)}
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onClick={e => this.handleClick(e)}
|
||||
onFocus={e => this.handleFocus(e)}
|
||||
onBlur={e => this.handleBlur(e)}
|
||||
onKeyDown={e => this.handleKeyDown(e)}
|
||||
>
|
||||
{this.state.status === 'SEARCH'
|
||||
? <div styleName='search'>
|
||||
<input styleName='search-input'
|
||||
{this.state.status === 'SEARCH' ? (
|
||||
<div styleName='search'>
|
||||
<input
|
||||
styleName='search-input'
|
||||
ref='search'
|
||||
value={this.state.search}
|
||||
placeholder={i18n.__('Folder...')}
|
||||
onChange={(e) => this.handleSearchInputChange(e)}
|
||||
onBlur={(e) => this.handleSearchInputBlur(e)}
|
||||
onKeyDown={(e) => this.handleSearchInputKeyDown(e)}
|
||||
onChange={e => this.handleSearchInputChange(e)}
|
||||
onBlur={e => this.handleSearchInputBlur(e)}
|
||||
onKeyDown={e => this.handleSearchInputKeyDown(e)}
|
||||
/>
|
||||
<div styleName='search-optionList'
|
||||
ref='optionList'
|
||||
>
|
||||
<div styleName='search-optionList' ref='optionList'>
|
||||
{optionList}
|
||||
</div>
|
||||
</div>
|
||||
: <div styleName='idle' style={{color: currentOption.folder.color}}>
|
||||
) : currentOption ? (
|
||||
<div styleName='idle' style={{ color: currentOption.folder.color }}>
|
||||
<div styleName='idle-label'>
|
||||
<i className='fa fa-folder' />
|
||||
<span styleName='idle-label-name'>
|
||||
@@ -269,8 +303,7 @@ class FolderSelect extends React.Component {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -280,11 +313,13 @@ FolderSelect.propTypes = {
|
||||
className: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
folders: PropTypes.arrayOf(PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
color: PropTypes.string
|
||||
}))
|
||||
folders: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
color: PropTypes.string
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export default CSSModules(FolderSelect, styles)
|
||||
|
||||
@@ -134,54 +134,39 @@ body[data-theme="dark"]
|
||||
.search-optionList-item-name-surfix
|
||||
color $ui-dark-inactive-text-color
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.root
|
||||
color $ui-dark-text-color
|
||||
&:hover
|
||||
color white
|
||||
background-color $ui-monokai-button--hover-backgroundColor
|
||||
border-color $ui-monokai-borderColor
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.root
|
||||
&:hover
|
||||
background-color get-theme-var(theme, 'button--hover-backgroundColor')
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
|
||||
.search-optionList
|
||||
color white
|
||||
border-color $ui-monokai-borderColor
|
||||
background-color $ui-monokai-button-backgroundColor
|
||||
.search-input
|
||||
color get-theme-var(theme, 'text-color')
|
||||
background-color transparent
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
|
||||
.search-optionList-item
|
||||
&:hover
|
||||
background-color lighten($ui-monokai-button--hover-backgroundColor, 15%)
|
||||
.search-optionList
|
||||
color get-theme-var(theme, 'text-color')
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
|
||||
.search-optionList-item--active
|
||||
background-color $ui-monokai-button--active-backgroundColor
|
||||
color $ui-monokai-button--active-color
|
||||
&:hover
|
||||
background-color $ui-monokai-button--active-backgroundColor
|
||||
color $ui-monokai-button--active-color
|
||||
.search-optionList-item-name-surfix
|
||||
color $ui-monokai-inactive-text-color
|
||||
.search-optionList-item
|
||||
&:hover
|
||||
background-color lighten(get-theme-var(theme, 'button--hover-backgroundColor'), 15%)
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
color $ui-dracula-text-color
|
||||
&:hover
|
||||
color #f8f8f2
|
||||
background-color $ui-dark-button--hover-backgroundColor
|
||||
border-color $ui-dracula-borderColor
|
||||
.search-optionList-item--active
|
||||
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||
color get-theme-var(theme, 'button--active-color')
|
||||
&:hover
|
||||
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||
color get-theme-var(theme, 'button--active-color')
|
||||
|
||||
.search-optionList
|
||||
color #f8f8f2
|
||||
border-color $ui-dracula-borderColor
|
||||
background-color $ui-dracula-button-backgroundColor
|
||||
.search-optionList-item-name-surfix
|
||||
color get-theme-var(theme, 'inactive-text-color')
|
||||
|
||||
.search-optionList-item
|
||||
&:hover
|
||||
background-color lighten($ui-dracula-button--hover-backgroundColor, 15%)
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
.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
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
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,14 +5,18 @@ import styles from './FullscreenButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
const FullscreenButton = ({
|
||||
onClick
|
||||
}) => {
|
||||
const FullscreenButton = ({ onClick }) => {
|
||||
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
|
||||
return (
|
||||
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
|
||||
<button
|
||||
styleName='control-fullScreenButton'
|
||||
title={i18n.__('Fullscreen')}
|
||||
onMouseDown={e => onClick(e)}
|
||||
>
|
||||
<img src='../resources/icon/icon-full.svg' />
|
||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
|
||||
<span lang={i18n.locale} styleName='tooltip'>
|
||||
{i18n.__('Fullscreen')}({hotkey})
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
.control-fullScreenButton
|
||||
top 80px
|
||||
topBarButtonRight()
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 50px
|
||||
right 70px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
.tooltip:lang(ja)
|
||||
@extend .tooltip
|
||||
right 35px
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-fullScreenButton
|
||||
.control-fullScreenButton
|
||||
top 80px
|
||||
topBarButtonRight()
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 50px
|
||||
right 70px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
.tooltip:lang(ja)
|
||||
@extend .tooltip
|
||||
right 35px
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-fullScreenButton
|
||||
topBarButtonDark()
|
||||
@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './InfoButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const InfoButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-infoButton'
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
const InfoButton = ({ onClick }) => (
|
||||
<button styleName='control-infoButton' onClick={e => onClick(e)}>
|
||||
<img className='infoButton' src='../resources/icon/icon-info.svg' />
|
||||
<span styleName='tooltip'>{i18n.__('Info')}</span>
|
||||
</button>
|
||||
|
||||
@@ -6,28 +6,47 @@ import copy from 'copy-to-clipboard'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class InfoPanel extends React.Component {
|
||||
copyNoteLink () {
|
||||
const {noteLink} = this.props
|
||||
copyNoteLink() {
|
||||
const { noteLink } = this.props
|
||||
this.refs.noteLink.select()
|
||||
copy(noteLink)
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const {
|
||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf, wordCount, letterCount, type, print
|
||||
storageName,
|
||||
folderName,
|
||||
noteLink,
|
||||
updatedAt,
|
||||
createdAt,
|
||||
exportAsMd,
|
||||
exportAsTxt,
|
||||
exportAsHtml,
|
||||
exportAsPdf,
|
||||
wordCount,
|
||||
letterCount,
|
||||
type,
|
||||
print
|
||||
} = this.props
|
||||
return (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
||||
<div
|
||||
className='infoPanel'
|
||||
styleName='control-infoButton-panel'
|
||||
style={{ display: 'none' }}
|
||||
>
|
||||
<div>
|
||||
<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>
|
||||
|
||||
<hr />
|
||||
|
||||
{type === 'SNIPPET_NOTE'
|
||||
? ''
|
||||
: <div styleName='count-wrap'>
|
||||
{type === 'SNIPPET_NOTE' ? (
|
||||
''
|
||||
) : (
|
||||
<div styleName='count-wrap'>
|
||||
<div styleName='count-number'>
|
||||
<p styleName='infoPanel-defaul-count'>{wordCount}</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>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
|
||||
{type === 'SNIPPET_NOTE'
|
||||
? ''
|
||||
: <hr />
|
||||
}
|
||||
{type === 'SNIPPET_NOTE' ? '' : <hr />}
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'>{storageName}</p>
|
||||
@@ -60,8 +76,18 @@ class InfoPanel extends React.Component {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input styleName='infoPanel-noteLink' ref='noteLink' defaultValue={noteLink} onClick={(e) => { e.target.select() }} />
|
||||
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
|
||||
<input
|
||||
styleName='infoPanel-noteLink'
|
||||
ref='noteLink'
|
||||
defaultValue={noteLink}
|
||||
onClick={e => {
|
||||
e.target.select()
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onClick={() => this.copyNoteLink()}
|
||||
styleName='infoPanel-copyButton'
|
||||
>
|
||||
<i className='fa fa-clipboard' />
|
||||
</button>
|
||||
<p styleName='infoPanel-sub'>{i18n.__('NOTE LINK')}</p>
|
||||
@@ -70,27 +96,39 @@ class InfoPanel extends React.Component {
|
||||
<hr />
|
||||
|
||||
<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' />
|
||||
<p>{i18n.__('.md')}</p>
|
||||
</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' />
|
||||
<p>{i18n.__('.txt')}</p>
|
||||
</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' />
|
||||
<p>{i18n.__('.html')}</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsPdf(e, 'export-pdf')}
|
||||
>
|
||||
<i className='fa fa-file-pdf-o' />
|
||||
<p>{i18n.__('.pdf')}</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => print(e, 'print')}>
|
||||
<button styleName='export--enable' onClick={e => print(e, 'print')}>
|
||||
<i className='fa fa-print' />
|
||||
<p>{i18n.__('Print')}</p>
|
||||
</button>
|
||||
|
||||
@@ -138,162 +138,49 @@
|
||||
.export--unable
|
||||
cursor not-allowed
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-infoButton-panel
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.control-infoButton-panel
|
||||
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||
|
||||
.control-infoButton-panel-trash
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
.control-infoButton-panel-trash
|
||||
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||
|
||||
.modification-date
|
||||
color $ui-dark-text-color
|
||||
.modification-date
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
.modification-date-desc
|
||||
color $ui-inactive-text-color
|
||||
.modification-date-desc
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-defaul-count
|
||||
color $ui-dark-text-color
|
||||
.infoPanel-defaul-count
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
.infoPanel-sub-count
|
||||
color $ui-inactive-text-color
|
||||
.infoPanel-sub-count
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-default
|
||||
color $ui-dark-text-color
|
||||
.infoPanel-default
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
.infoPanel-sub
|
||||
color $ui-inactive-text-color
|
||||
.infoPanel-sub
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-noteLink
|
||||
background-color alpha($ui-dark-borderColor, 60%)
|
||||
color $ui-dark-text-color
|
||||
.infoPanel-noteLink
|
||||
background-color alpha(get-theme-var(theme, 'borderColor'), 20%)
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
[id=export-wrap]
|
||||
button
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-dark-borderColor, 20%)
|
||||
color $ui-dark-text-color
|
||||
p
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
color $ui-dark-text-color
|
||||
[id=export-wrap]
|
||||
button
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
background-color alpha(get-theme-var(theme, 'borderColor'), 20%)
|
||||
color get-theme-var(theme, 'text-color')
|
||||
p
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.control-infoButton-panel
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
for theme in 'dark' 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
.control-infoButton-panel-trash
|
||||
background-color $ui-solarized-ark-noteList-backgroundColor
|
||||
|
||||
.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
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
@@ -5,9 +5,20 @@ import styles from './InfoPanel.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const InfoPanelTrashed = ({
|
||||
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf
|
||||
storageName,
|
||||
folderName,
|
||||
updatedAt,
|
||||
createdAt,
|
||||
exportAsMd,
|
||||
exportAsTxt,
|
||||
exportAsHtml,
|
||||
exportAsPdf
|
||||
}) => (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
|
||||
<div
|
||||
className='infoPanel'
|
||||
styleName='control-infoButton-panel-trash'
|
||||
style={{ display: 'none' }}
|
||||
>
|
||||
<div>
|
||||
<p styleName='modification-date'>{updatedAt}</p>
|
||||
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
|
||||
@@ -21,7 +32,10 @@ const InfoPanelTrashed = ({
|
||||
</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>
|
||||
</div>
|
||||
|
||||
@@ -31,22 +45,34 @@ const InfoPanelTrashed = ({
|
||||
</div>
|
||||
|
||||
<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' />
|
||||
<p>.md</p>
|
||||
</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' />
|
||||
<p>.txt</p>
|
||||
</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' />
|
||||
<p>.html</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsPdf(e, 'export-pdf')}
|
||||
>
|
||||
<i className='fa fa-file-pdf-o' />
|
||||
<p>.pdf</p>
|
||||
</button>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable camelcase */
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
@@ -31,60 +32,75 @@ import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||
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 {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
isMovingNote: false,
|
||||
note: Object.assign({
|
||||
title: '',
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}, props.note),
|
||||
note: Object.assign(
|
||||
{
|
||||
title: '',
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
},
|
||||
props.note
|
||||
),
|
||||
isLockButtonShown: props.config.editor.type !== 'SPLIT',
|
||||
isLocked: false,
|
||||
editorType: props.config.editor.type,
|
||||
switchPreview: props.config.editor.switchPreview
|
||||
switchPreview: props.config.editor.switchPreview,
|
||||
RTL: false
|
||||
}
|
||||
|
||||
this.dispatchTimer = null
|
||||
|
||||
this.generateToc = this.handleGenerateToc.bind(this)
|
||||
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
||||
this.generateToc = () => this.handleGenerateToc()
|
||||
this.handleUpdateContent = this.handleUpdateContent.bind(this)
|
||||
this.handleSwitchStackDirection = this.handleSwitchStackDirection.bind(this)
|
||||
this.getNote = this.getNote.bind(this)
|
||||
}
|
||||
|
||||
focus () {
|
||||
focus() {
|
||||
this.refs.content.focus()
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
ee.on('editor:orientation', this.handleSwitchStackDirection)
|
||||
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
||||
ee.on('topbar:toggledirectionbutton', () => this.handleSwitchDirection())
|
||||
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)
|
||||
})
|
||||
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
|
||||
ee.on('code:generate-toc', this.generateToc)
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
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.saveQueue != null) this.saveNow()
|
||||
this.setState({
|
||||
note: Object.assign({linesHighlighted: []}, nextProps.note)
|
||||
}, () => {
|
||||
this.refs.content.reload()
|
||||
if (this.refs.tags) this.refs.tags.reset()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
note: Object.assign({ linesHighlighted: [] }, nextProps.note)
|
||||
},
|
||||
() => {
|
||||
this.refs.content.reload()
|
||||
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
|
||||
const { switchPreview } = nextProps.config.editor
|
||||
|
||||
if (this.state.switchPreview !== switchPreview) {
|
||||
this.setState({
|
||||
@@ -97,23 +113,28 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
||||
ee.on('topbar:toggledirectionbutton', this.handleSwitchDirection)
|
||||
ee.off('code:generate-toc', this.generateToc)
|
||||
if (this.saveQueue != null) this.saveNow()
|
||||
}
|
||||
|
||||
handleUpdateTag () {
|
||||
handleUpdateTag() {
|
||||
const { note } = this.state
|
||||
if (this.refs.tags) note.tags = this.refs.tags.value
|
||||
this.updateNote(note)
|
||||
}
|
||||
|
||||
handleUpdateContent () {
|
||||
handleUpdateContent() {
|
||||
const { note } = this.state
|
||||
note.content = this.refs.content.value
|
||||
|
||||
let title = findNoteTitle(note.content, this.props.config.editor.enableFrontMatterTitle, this.props.config.editor.frontMatterTitleField)
|
||||
let title = findNoteTitle(
|
||||
note.content,
|
||||
this.props.config.editor.enableFrontMatterTitle,
|
||||
this.props.config.editor.frontMatterTitleField
|
||||
)
|
||||
title = striptags(title)
|
||||
title = markdown.strip(title)
|
||||
note.title = title
|
||||
@@ -121,38 +142,35 @@ class MarkdownNoteDetail extends React.Component {
|
||||
this.updateNote(note)
|
||||
}
|
||||
|
||||
updateNote (note) {
|
||||
updateNote(note) {
|
||||
note.updatedAt = new Date()
|
||||
this.setState({note}, () => {
|
||||
this.setState({ note }, () => {
|
||||
this.save()
|
||||
})
|
||||
}
|
||||
|
||||
save () {
|
||||
save() {
|
||||
clearTimeout(this.saveQueue)
|
||||
this.saveQueue = setTimeout(() => {
|
||||
this.saveNow()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
saveNow () {
|
||||
saveNow() {
|
||||
const { note, dispatch } = this.props
|
||||
clearTimeout(this.saveQueue)
|
||||
this.saveQueue = null
|
||||
|
||||
dataApi
|
||||
.updateNote(note.storage, note.key, this.state.note)
|
||||
.then((note) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
||||
dataApi.updateNote(note.storage, note.key, this.state.note).then(note => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
||||
})
|
||||
}
|
||||
|
||||
handleFolderChange (e) {
|
||||
const { dispatch } = this.props
|
||||
handleFolderChange(e) {
|
||||
const { note } = this.state
|
||||
const value = this.refs.folder.value
|
||||
const splitted = value.split('-')
|
||||
@@ -161,64 +179,71 @@ class MarkdownNoteDetail extends React.Component {
|
||||
|
||||
dataApi
|
||||
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
||||
.then((newNote) => {
|
||||
this.setState({
|
||||
isMovingNote: true,
|
||||
note: Object.assign({}, newNote)
|
||||
}, () => {
|
||||
const { dispatch, location } = this.props
|
||||
dispatch({
|
||||
type: 'MOVE_NOTE',
|
||||
originNote: note,
|
||||
note: newNote
|
||||
})
|
||||
dispatch(replace({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({
|
||||
key: newNote.key
|
||||
.then(newNote => {
|
||||
this.setState(
|
||||
{
|
||||
isMovingNote: true,
|
||||
note: Object.assign({}, newNote)
|
||||
},
|
||||
() => {
|
||||
const { dispatch, location } = this.props
|
||||
dispatch({
|
||||
type: 'MOVE_NOTE',
|
||||
originNote: note,
|
||||
note: newNote
|
||||
})
|
||||
}))
|
||||
this.setState({
|
||||
isMovingNote: false
|
||||
})
|
||||
})
|
||||
dispatch(
|
||||
replace({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({
|
||||
key: newNote.key
|
||||
})
|
||||
})
|
||||
)
|
||||
this.setState({
|
||||
isMovingNote: false
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
handleStarButtonClick (e) {
|
||||
handleStarButtonClick(e) {
|
||||
const { note } = this.state
|
||||
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
|
||||
if (!note.isStarred)
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
|
||||
|
||||
note.isStarred = !note.isStarred
|
||||
|
||||
this.setState({
|
||||
note
|
||||
}, () => {
|
||||
this.save()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
note
|
||||
},
|
||||
() => {
|
||||
this.save()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
exportAsFile () {
|
||||
exportAsFile() {}
|
||||
|
||||
}
|
||||
|
||||
exportAsMd () {
|
||||
exportAsMd() {
|
||||
ee.emit('export:save-md')
|
||||
}
|
||||
|
||||
exportAsTxt () {
|
||||
exportAsTxt() {
|
||||
ee.emit('export:save-text')
|
||||
}
|
||||
|
||||
exportAsHtml () {
|
||||
exportAsHtml() {
|
||||
ee.emit('export:save-html')
|
||||
}
|
||||
|
||||
exportAsPdf () {
|
||||
exportAsPdf() {
|
||||
ee.emit('export:save-pdf')
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
handleKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
// tab key
|
||||
case 9:
|
||||
@@ -228,7 +253,11 @@ class MarkdownNoteDetail extends React.Component {
|
||||
} else if (e.ctrlKey && e.shiftKey) {
|
||||
e.preventDefault()
|
||||
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()
|
||||
this.focusEditor()
|
||||
}
|
||||
@@ -236,9 +265,8 @@ class MarkdownNoteDetail extends React.Component {
|
||||
// I key
|
||||
case 73:
|
||||
{
|
||||
const isSuper = global.process.platform === 'darwin'
|
||||
? e.metaKey
|
||||
: e.ctrlKey
|
||||
const isSuper =
|
||||
global.process.platform === 'darwin' ? e.metaKey : e.ctrlKey
|
||||
if (isSuper) {
|
||||
e.preventDefault()
|
||||
this.handleInfoButtonClick(e)
|
||||
@@ -248,17 +276,17 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleTrashButtonClick (e) {
|
||||
handleTrashButtonClick(e) {
|
||||
const { note } = this.state
|
||||
const { isTrashed } = note
|
||||
const { confirmDeletion } = this.props.config.ui
|
||||
|
||||
if (isTrashed) {
|
||||
if (confirmDeleteNote(confirmDeletion, true)) {
|
||||
const {note, dispatch} = this.props
|
||||
const { note, dispatch } = this.props
|
||||
dataApi
|
||||
.deleteNote(note.storage, note.key)
|
||||
.then((data) => {
|
||||
.then(data => {
|
||||
const dispatchHandler = () => {
|
||||
dispatch({
|
||||
type: 'DELETE_NOTE',
|
||||
@@ -274,103 +302,139 @@ class MarkdownNoteDetail extends React.Component {
|
||||
if (confirmDeleteNote(confirmDeletion, false)) {
|
||||
note.isTrashed = true
|
||||
|
||||
this.setState({
|
||||
note
|
||||
}, () => {
|
||||
this.save()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
note
|
||||
},
|
||||
() => {
|
||||
this.save()
|
||||
}
|
||||
)
|
||||
|
||||
ee.emit('list:next')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleUndoButtonClick (e) {
|
||||
handleUndoButtonClick(e) {
|
||||
const { note } = this.state
|
||||
|
||||
note.isTrashed = false
|
||||
|
||||
this.setState({
|
||||
note
|
||||
}, () => {
|
||||
this.save()
|
||||
this.refs.content.reload()
|
||||
ee.emit('list:next')
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
note
|
||||
},
|
||||
() => {
|
||||
this.save()
|
||||
this.refs.content.reload()
|
||||
ee.emit('list:next')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
handleFullScreenButton (e) {
|
||||
handleFullScreenButton(e) {
|
||||
ee.emit('editor:fullscreen')
|
||||
}
|
||||
|
||||
handleLockButtonMouseDown (e) {
|
||||
handleLockButtonMouseDown(e) {
|
||||
e.preventDefault()
|
||||
ee.emit('editor:lock')
|
||||
this.setState({ isLocked: !this.state.isLocked })
|
||||
if (this.state.isLocked) this.focus()
|
||||
}
|
||||
|
||||
getToggleLockButton () {
|
||||
return this.state.isLocked ? '../resources/icon/icon-lock.svg' : '../resources/icon/icon-unlock.svg'
|
||||
getToggleLockButton() {
|
||||
return this.state.isLocked
|
||||
? '../resources/icon/icon-lock.svg'
|
||||
: '../resources/icon/icon-unlock.svg'
|
||||
}
|
||||
|
||||
handleDeleteKeyDown (e) {
|
||||
handleDeleteKeyDown(e) {
|
||||
if (e.keyCode === 27) this.handleDeleteCancelButtonClick(e)
|
||||
}
|
||||
|
||||
handleToggleLockButton (event, noteStatus) {
|
||||
handleToggleLockButton(event, noteStatus) {
|
||||
// first argument event is not used
|
||||
if (noteStatus === 'CODE') {
|
||||
this.setState({isLockButtonShown: true})
|
||||
this.setState({ isLockButtonShown: true })
|
||||
} else {
|
||||
this.setState({isLockButtonShown: false})
|
||||
this.setState({ isLockButtonShown: false })
|
||||
}
|
||||
}
|
||||
|
||||
handleGenerateToc () {
|
||||
handleGenerateToc() {
|
||||
const editor = this.refs.content.refs.code.editor
|
||||
markdownToc.generateInEditor(editor)
|
||||
}
|
||||
|
||||
handleFocus (e) {
|
||||
handleFocus(e) {
|
||||
this.focus()
|
||||
}
|
||||
|
||||
handleInfoButtonClick (e) {
|
||||
handleInfoButtonClick(e) {
|
||||
const infoPanel = document.querySelector('.infoPanel')
|
||||
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
|
||||
if (infoPanel.style)
|
||||
infoPanel.style.display =
|
||||
infoPanel.style.display === 'none' ? 'inline' : 'none'
|
||||
}
|
||||
|
||||
print (e) {
|
||||
print(e) {
|
||||
ee.emit('print')
|
||||
}
|
||||
|
||||
handleSwitchMode (type) {
|
||||
handleSwitchMode(type) {
|
||||
// If in split mode, hide the lock button
|
||||
this.setState({ editorType: type, isLockButtonShown: !(type === 'SPLIT') }, () => {
|
||||
this.focus()
|
||||
const newConfig = Object.assign({}, this.props.config)
|
||||
newConfig.editor.type = type
|
||||
ConfigManager.set(newConfig)
|
||||
})
|
||||
this.setState(
|
||||
{ editorType: type, isLockButtonShown: type !== 'SPLIT' },
|
||||
() => {
|
||||
this.focus()
|
||||
const newConfig = Object.assign({}, this.props.config)
|
||||
newConfig.editor.type = type
|
||||
ConfigManager.set(newConfig)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
handleDeleteNote () {
|
||||
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() {
|
||||
this.handleTrashButtonClick()
|
||||
}
|
||||
|
||||
handleClearTodo () {
|
||||
handleClearTodo() {
|
||||
const { note } = this.state
|
||||
const splitted = note.content.split('\n')
|
||||
|
||||
const clearTodoContent = splitted.map((line) => {
|
||||
const trimmedLine = line.trim()
|
||||
if (trimmedLine.match(/\[x\]/i)) {
|
||||
return line.replace(/\[x\]/i, '[ ]')
|
||||
} else {
|
||||
return line
|
||||
}
|
||||
}).join('\n')
|
||||
const clearTodoContent = splitted
|
||||
.map(line => {
|
||||
const trimmedLine = line.trim()
|
||||
if (trimmedLine.match(/\[x\]/i)) {
|
||||
return line.replace(/\[x\]/i, '[ ]')
|
||||
} else {
|
||||
return line
|
||||
}
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
note.content = clearTodoContent
|
||||
this.refs.content.setValue(note.content)
|
||||
@@ -378,159 +442,200 @@ class MarkdownNoteDetail extends React.Component {
|
||||
this.updateNote(note)
|
||||
}
|
||||
|
||||
renderEditor () {
|
||||
getNote() {
|
||||
return this.state.note
|
||||
}
|
||||
|
||||
renderEditor() {
|
||||
const { config, ignorePreviewPointerEvents } = this.props
|
||||
const { note } = this.state
|
||||
const { note, isStacking } = this.state
|
||||
|
||||
if (this.state.editorType === 'EDITOR_PREVIEW') {
|
||||
return <MarkdownEditor
|
||||
ref='content'
|
||||
styleName='body-noteEditor'
|
||||
config={config}
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
noteKey={note.key}
|
||||
linesHighlighted={note.linesHighlighted}
|
||||
onChange={this.handleUpdateContent.bind(this)}
|
||||
isLocked={this.state.isLocked}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
/>
|
||||
return (
|
||||
<MarkdownEditor
|
||||
ref='content'
|
||||
styleName='body-noteEditor'
|
||||
config={config}
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
noteKey={note.key}
|
||||
linesHighlighted={note.linesHighlighted}
|
||||
onChange={this.handleUpdateContent}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
getNote={this.getNote}
|
||||
RTL={config.editor.rtlEnabled && this.state.RTL}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return <MarkdownSplitEditor
|
||||
ref='content'
|
||||
config={config}
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
noteKey={note.key}
|
||||
linesHighlighted={note.linesHighlighted}
|
||||
onChange={this.handleUpdateContent.bind(this)}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
/>
|
||||
return (
|
||||
<MarkdownSplitEditor
|
||||
ref='content'
|
||||
config={config}
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
noteKey={note.key}
|
||||
isStacking={isStacking}
|
||||
linesHighlighted={note.linesHighlighted}
|
||||
onChange={this.handleUpdateContent}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
getNote={this.getNote}
|
||||
RTL={config.editor.rtlEnabled && this.state.RTL}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { data, location, config } = this.props
|
||||
render() {
|
||||
const { data, dispatch, location, config } = this.props
|
||||
const { note, editorType } = this.state
|
||||
const storageKey = note.storage
|
||||
const folderKey = note.folder
|
||||
|
||||
const options = []
|
||||
data.storageMap.forEach((storage, index) => {
|
||||
storage.folders.forEach((folder) => {
|
||||
storage.folders.forEach(folder => {
|
||||
options.push({
|
||||
storage: storage,
|
||||
folder: folder
|
||||
})
|
||||
})
|
||||
})
|
||||
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
||||
|
||||
const trashTopBar = <div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
<InfoPanelTrashed
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsPdf={this.exportAsPdf}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
const currentOption = _.find(
|
||||
options,
|
||||
option =>
|
||||
option.storage.key === storageKey && option.folder.key === folderKey
|
||||
)
|
||||
|
||||
const detailTopBar = <div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<div>
|
||||
<FolderSelect styleName='info-left-top-folderSelect'
|
||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||
ref='folder'
|
||||
data={data}
|
||||
onChange={(e) => this.handleFolderChange(e)}
|
||||
// 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'>
|
||||
<RestoreButton onClick={e => this.handleUndoButtonClick(e)} />
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<PermanentDeleteButton
|
||||
onClick={e => this.handleTrashButtonClick(e)}
|
||||
/>
|
||||
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
|
||||
<InfoPanelTrashed
|
||||
storageName={storageName}
|
||||
folderName={folderName}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsPdf={this.exportAsPdf}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TagSelect
|
||||
ref='tags'
|
||||
value={this.state.note.tags}
|
||||
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
||||
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||
data={data}
|
||||
onChange={this.handleUpdateTag.bind(this)}
|
||||
coloredTags={config.coloredTags}
|
||||
/>
|
||||
<TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
||||
<StarButton
|
||||
onClick={(e) => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
/>
|
||||
)
|
||||
|
||||
{(() => {
|
||||
const imgSrc = `${this.getToggleLockButton()}`
|
||||
const lockButtonComponent =
|
||||
<button styleName='control-lockButton'
|
||||
onFocus={(e) => this.handleFocus(e)}
|
||||
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
|
||||
>
|
||||
<img src={imgSrc} />
|
||||
{this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
|
||||
</button>
|
||||
const detailTopBar = (
|
||||
<div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<div>
|
||||
<FolderSelect
|
||||
styleName='info-left-top-folderSelect'
|
||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||
ref='folder'
|
||||
data={data}
|
||||
onChange={e => this.handleFolderChange(e)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
return (
|
||||
this.state.isLockButtonShown ? lockButtonComponent : ''
|
||||
)
|
||||
})()}
|
||||
<TagSelect
|
||||
ref='tags'
|
||||
value={this.state.note.tags}
|
||||
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
||||
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||
data={data}
|
||||
dispatch={dispatch}
|
||||
onChange={this.handleUpdateTag.bind(this)}
|
||||
coloredTags={config.coloredTags}
|
||||
/>
|
||||
<TodoListPercentage
|
||||
onClearCheckboxClick={e => this.handleClearTodo(e)}
|
||||
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
|
||||
/>
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<ToggleModeButton
|
||||
onClick={e => this.handleSwitchMode(e)}
|
||||
editorType={editorType}
|
||||
/>
|
||||
{this.props.config.editor.rtlEnabled && (
|
||||
<ToggleDirectionButton
|
||||
onClick={e => this.handleSwitchDirection(e)}
|
||||
isRTL={this.state.RTL}
|
||||
/>
|
||||
)}
|
||||
<StarButton
|
||||
onClick={e => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
/>
|
||||
|
||||
<FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
|
||||
{(() => {
|
||||
const imgSrc = `${this.getToggleLockButton()}`
|
||||
const lockButtonComponent = (
|
||||
<button
|
||||
styleName='control-lockButton'
|
||||
onFocus={e => this.handleFocus(e)}
|
||||
onMouseDown={e => this.handleLockButtonMouseDown(e)}
|
||||
>
|
||||
<img src={imgSrc} />
|
||||
{this.state.isLocked ? (
|
||||
<span styleName='tooltip'>Unlock</span>
|
||||
) : (
|
||||
<span styleName='tooltip'>Lock</span>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
return this.state.isLockButtonShown ? lockButtonComponent : ''
|
||||
})()}
|
||||
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
<FullscreenButton onClick={e => this.handleFullScreenButton(e)} />
|
||||
|
||||
<InfoPanel
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsPdf={this.exportAsPdf}
|
||||
wordCount={note.content.split(' ').length}
|
||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||
type={note.type}
|
||||
print={this.print}
|
||||
/>
|
||||
<TrashButton onClick={e => this.handleTrashButtonClick(e)} />
|
||||
|
||||
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
|
||||
|
||||
<InfoPanel
|
||||
storageName={storageName}
|
||||
folderName={folderName}
|
||||
noteLink={`[${note.title}](:note:${
|
||||
queryString.parse(location.search).key
|
||||
})`}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsPdf={this.exportAsPdf}
|
||||
wordCount={note.content.trim().split(/\s+/g).length}
|
||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||
type={note.type}
|
||||
print={this.print}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='NoteDetail'
|
||||
<div
|
||||
className='NoteDetail'
|
||||
style={this.props.style}
|
||||
styleName='root'
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onKeyDown={e => this.handleKeyDown(e)}
|
||||
>
|
||||
|
||||
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
||||
|
||||
<div styleName='body'>
|
||||
{this.renderEditor()}
|
||||
</div>
|
||||
<div styleName='body'>{this.renderEditor()}</div>
|
||||
|
||||
<StatusBar
|
||||
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
||||
@@ -544,9 +649,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
MarkdownNoteDetail.propTypes = {
|
||||
dispatch: PropTypes.func,
|
||||
repositories: PropTypes.array,
|
||||
note: PropTypes.shape({
|
||||
|
||||
}),
|
||||
note: PropTypes.shape({}),
|
||||
style: PropTypes.shape({
|
||||
left: PropTypes.number
|
||||
}),
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
.control-lockButton
|
||||
topBarButtonRight()
|
||||
position absolute
|
||||
right 225px
|
||||
right 265px
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
@@ -66,26 +66,14 @@ body[data-theme="dark"]
|
||||
.control-fullScreenButton
|
||||
topBarButtonDark()
|
||||
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.root
|
||||
border-left 1px solid get-theme-var(theme, 'borderColor')
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
border-left 1px solid $ui-solarized-dark-borderColor
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.root
|
||||
border-left 1px solid $ui-monokai-borderColor
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
border-left 1px solid $ui-dracula-borderColor
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
|
||||
div
|
||||
> button, div
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
> img, span
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
|
||||
@@ -15,6 +15,14 @@ $info-margin-under-border = 30px
|
||||
padding 0 20px
|
||||
z-index 99
|
||||
|
||||
.info > div
|
||||
> button
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
> img, span
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
|
||||
.info-left
|
||||
padding 0 10px
|
||||
width 100%
|
||||
@@ -94,17 +102,14 @@ body[data-theme="dark"]
|
||||
.undo-button
|
||||
topBarButtonDark()
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.info
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.info
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.info
|
||||
border-color $ui-monokai-borderColor
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.info
|
||||
border-color $ui-dracula-borderColor
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
|
||||
@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './TrashButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const PermanentDeleteButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-trashButton--in-trash'
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
const PermanentDeleteButton = ({ onClick }) => (
|
||||
<button styleName='control-trashButton--in-trash' onClick={e => onClick(e)}>
|
||||
<img src='../resources/icon/icon-trash.svg' />
|
||||
<span styleName='tooltip'>{i18n.__('Permanent Delete')}</span>
|
||||
</button>
|
||||
|
||||
@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './RestoreButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const RestoreButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-restoreButton'
|
||||
onClick={onClick}
|
||||
>
|
||||
const RestoreButton = ({ onClick }) => (
|
||||
<button styleName='control-restoreButton' onClick={onClick}>
|
||||
<i className='fa fa-undo fa-fw' styleName='iconRestore' />
|
||||
<span styleName='tooltip'>{i18n.__('Restore')}</span>
|
||||
</button>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -156,78 +156,35 @@ body[data-theme="dark"]
|
||||
.control-fullScreenButton
|
||||
topBarButtonDark()
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
border-left 1px solid $ui-solarized-dark-borderColor
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.root
|
||||
border-left 1px solid get-theme-var(theme, 'borderColor')
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
|
||||
.body
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
.body
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
|
||||
.body .description textarea
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
border 1px solid $ui-solarized-dark-borderColor
|
||||
.body .description textarea
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
color get-theme-var(theme, 'text-color')
|
||||
border 1px solid get-theme-var(theme, 'borderColor')
|
||||
|
||||
.tabList .tabButton
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
.tabList .tabButton
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
|
||||
.tabButton
|
||||
&:hover
|
||||
color $ui-solarized-dark-button--active-color
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
transition 0.15s
|
||||
|
||||
.tabList
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
.tabButton
|
||||
&:hover
|
||||
color get-theme-var(theme, 'text-color')
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
transition 0.15s
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.root
|
||||
border-left 1px solid $ui-monokai-borderColor
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
.tabList
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
.body
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
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
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
@@ -6,7 +6,7 @@ import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class StarButton extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -14,47 +14,51 @@ class StarButton extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseDown (e) {
|
||||
handleMouseDown(e) {
|
||||
this.setState({
|
||||
isActive: true
|
||||
})
|
||||
}
|
||||
|
||||
handleMouseUp (e) {
|
||||
handleMouseUp(e) {
|
||||
this.setState({
|
||||
isActive: false
|
||||
})
|
||||
}
|
||||
|
||||
handleMouseLeave (e) {
|
||||
handleMouseLeave(e) {
|
||||
this.setState({
|
||||
isActive: false
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { className } = this.props
|
||||
|
||||
return (
|
||||
<button className={_.isString(className)
|
||||
? 'StarButton ' + className
|
||||
: 'StarButton'
|
||||
<button
|
||||
className={
|
||||
_.isString(className) ? 'StarButton ' + className : 'StarButton'
|
||||
}
|
||||
styleName={this.state.isActive || this.props.isActive
|
||||
? 'root--active'
|
||||
: 'root'
|
||||
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-starred.svg'
|
||||
: '../resources/icon/icon-star.svg'
|
||||
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-starred.svg'
|
||||
: '../resources/icon/icon-star.svg'
|
||||
}
|
||||
/>
|
||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Star')}</span>
|
||||
<span lang={i18n.locale} styleName='tooltip'>
|
||||
{i18n.__('Star')}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -42,4 +42,4 @@ body[data-theme="dark"]
|
||||
topBarButtonDark()
|
||||
&:hover
|
||||
transition 0.2s
|
||||
color alpha($ui-favorite-star-button-color, 0.6)
|
||||
color alpha($ui-favorite-star-button-color, 0.6)
|
||||
|
||||
@@ -8,9 +8,10 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import Autosuggest from 'react-autosuggest'
|
||||
import { push } from 'connected-react-router'
|
||||
|
||||
class TagSelect extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -19,15 +20,20 @@ class TagSelect extends React.Component {
|
||||
}
|
||||
|
||||
this.handleAddTag = this.handleAddTag.bind(this)
|
||||
this.handleRenameTag = this.handleRenameTag.bind(this)
|
||||
this.onInputBlur = this.onInputBlur.bind(this)
|
||||
this.onInputChange = this.onInputChange.bind(this)
|
||||
this.onInputKeyDown = this.onInputKeyDown.bind(this)
|
||||
this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this)
|
||||
this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this)
|
||||
this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(
|
||||
this
|
||||
)
|
||||
this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(
|
||||
this
|
||||
)
|
||||
this.onSuggestionSelected = this.onSuggestionSelected.bind(this)
|
||||
}
|
||||
|
||||
addNewTag (newTag) {
|
||||
addNewTag(newTag) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
|
||||
|
||||
newTag = newTag.trim().replace(/ +/g, '_')
|
||||
@@ -43,9 +49,7 @@ class TagSelect extends React.Component {
|
||||
}
|
||||
|
||||
let { value } = this.props
|
||||
value = _.isArray(value)
|
||||
? value.slice()
|
||||
: []
|
||||
value = _.isArray(value) ? value.slice() : []
|
||||
|
||||
if (!_.includes(value, newTag)) {
|
||||
value.push(newTag)
|
||||
@@ -55,68 +59,87 @@ class TagSelect extends React.Component {
|
||||
value = _.sortBy(value)
|
||||
}
|
||||
|
||||
this.setState({
|
||||
newTag: ''
|
||||
}, () => {
|
||||
this.value = value
|
||||
this.props.onChange()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
newTag: ''
|
||||
},
|
||||
() => {
|
||||
this.value = value
|
||||
this.props.onChange()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
buildSuggestions () {
|
||||
this.suggestions = _.sortBy(this.props.data.tagNoteMap.map(
|
||||
(tag, name) => ({
|
||||
name,
|
||||
nameLC: name.toLowerCase(),
|
||||
size: tag.size
|
||||
})
|
||||
).filter(
|
||||
tag => tag.size > 0
|
||||
), ['name'])
|
||||
buildSuggestions() {
|
||||
this.suggestions = _.sortBy(
|
||||
this.props.data.tagNoteMap
|
||||
.map((tag, name) => ({
|
||||
name,
|
||||
nameLC: name.toLowerCase(),
|
||||
size: tag.size
|
||||
}))
|
||||
.filter(tag => tag.size > 0),
|
||||
['name']
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.value = this.props.value
|
||||
|
||||
this.buildSuggestions()
|
||||
|
||||
ee.on('editor:add-tag', this.handleAddTag)
|
||||
ee.on('sidebar:rename-tag', this.handleRenameTag)
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
componentDidUpdate() {
|
||||
this.value = this.props.value
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
ee.off('editor:add-tag', this.handleAddTag)
|
||||
ee.off('sidebar:rename-tag', this.handleRenameTag)
|
||||
}
|
||||
|
||||
handleAddTag () {
|
||||
handleAddTag() {
|
||||
this.refs.newTag.input.focus()
|
||||
}
|
||||
|
||||
handleTagLabelClick (tag) {
|
||||
const { router } = this.context
|
||||
router.push(`/tags/${tag}`)
|
||||
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()
|
||||
}
|
||||
|
||||
handleTagRemoveButtonClick (tag) {
|
||||
handleTagLabelClick(tag) {
|
||||
const { dispatch } = this.props
|
||||
|
||||
// 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) {
|
||||
this.removeTagByCallback((value, tag) => {
|
||||
value.splice(value.indexOf(tag), 1)
|
||||
}, tag)
|
||||
}
|
||||
|
||||
onInputBlur (e) {
|
||||
onInputBlur(e) {
|
||||
this.submitNewTag()
|
||||
}
|
||||
|
||||
onInputChange (e, { newValue, method }) {
|
||||
onInputChange(e, { newValue, method }) {
|
||||
this.setState({
|
||||
newTag: newValue
|
||||
})
|
||||
}
|
||||
|
||||
onInputKeyDown (e) {
|
||||
onInputKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
case 9:
|
||||
e.preventDefault()
|
||||
@@ -132,17 +155,18 @@ class TagSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onSuggestionsClearRequested () {
|
||||
onSuggestionsClearRequested() {
|
||||
this.setState({
|
||||
suggestions: []
|
||||
})
|
||||
}
|
||||
|
||||
onSuggestionsFetchRequested ({ value }) {
|
||||
onSuggestionsFetchRequested({ value }) {
|
||||
const valueLC = value.toLowerCase()
|
||||
const suggestions = _.filter(
|
||||
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({
|
||||
@@ -150,22 +174,20 @@ class TagSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
onSuggestionSelected (event, { suggestion, suggestionValue }) {
|
||||
onSuggestionSelected(event, { suggestion, suggestionValue }) {
|
||||
this.addNewTag(suggestionValue)
|
||||
}
|
||||
|
||||
removeLastTag () {
|
||||
this.removeTagByCallback((value) => {
|
||||
removeLastTag() {
|
||||
this.removeTagByCallback(value => {
|
||||
value.pop()
|
||||
})
|
||||
}
|
||||
|
||||
removeTagByCallback (callback, tag = null) {
|
||||
removeTagByCallback(callback, tag = null) {
|
||||
let { value } = this.props
|
||||
|
||||
value = _.isArray(value)
|
||||
? value.slice()
|
||||
: []
|
||||
value = _.isArray(value) ? value.slice() : []
|
||||
callback(value, tag)
|
||||
value = _.uniq(value)
|
||||
|
||||
@@ -173,7 +195,7 @@ class TagSelect extends React.Component {
|
||||
this.props.onChange()
|
||||
}
|
||||
|
||||
reset () {
|
||||
reset() {
|
||||
this.buildSuggestions()
|
||||
|
||||
this.setState({
|
||||
@@ -181,51 +203,60 @@ class TagSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
submitNewTag () {
|
||||
submitNewTag() {
|
||||
this.addNewTag(this.refs.newTag.input.value)
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { value, className, showTagsAlphabetically, coloredTags } = this.props
|
||||
|
||||
const tagList = _.isArray(value)
|
||||
? (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 (
|
||||
<span styleName='tag'
|
||||
key={tag}
|
||||
style={wrapperStyle}
|
||||
>
|
||||
<span styleName='tag-label' style={textStyle} onClick={(e) => this.handleTagLabelClick(tag)}>#{tag}</span>
|
||||
<button styleName='tag-removeButton'
|
||||
onClick={(e) => this.handleTagRemoveButtonClick(tag)}
|
||||
>
|
||||
<img className='tag-removeButton-icon' src={iconRemove} width='8px' />
|
||||
</button>
|
||||
</span>
|
||||
)
|
||||
})
|
||||
? (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 (
|
||||
<span styleName='tag' key={tag} style={wrapperStyle}>
|
||||
<span
|
||||
styleName='tag-label'
|
||||
style={textStyle}
|
||||
onClick={e => this.handleTagLabelClick(tag)}
|
||||
>
|
||||
#{tag}
|
||||
</span>
|
||||
<button
|
||||
styleName='tag-removeButton'
|
||||
onClick={e => this.handleTagRemoveButtonClick(tag)}
|
||||
>
|
||||
<img
|
||||
className='tag-removeButton-icon'
|
||||
src={iconRemove}
|
||||
width='8px'
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
)
|
||||
})
|
||||
: []
|
||||
|
||||
const { newTag, suggestions } = this.state
|
||||
|
||||
return (
|
||||
<div className={_.isString(className)
|
||||
? 'TagSelect ' + className
|
||||
: 'TagSelect'
|
||||
<div
|
||||
className={
|
||||
_.isString(className) ? 'TagSelect ' + className : 'TagSelect'
|
||||
}
|
||||
styleName='root'
|
||||
>
|
||||
@@ -237,11 +268,7 @@ class TagSelect extends React.Component {
|
||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||
onSuggestionSelected={this.onSuggestionSelected}
|
||||
getSuggestionValue={suggestion => suggestion.name}
|
||||
renderSuggestion={suggestion => (
|
||||
<div>
|
||||
{suggestion.name}
|
||||
</div>
|
||||
)}
|
||||
renderSuggestion={suggestion => <div>{suggestion.name}</div>}
|
||||
inputProps={{
|
||||
placeholder: i18n.__('Add tag...'),
|
||||
value: newTag,
|
||||
@@ -255,11 +282,8 @@ class TagSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
TagSelect.contextTypes = {
|
||||
router: PropTypes.shape({})
|
||||
}
|
||||
|
||||
TagSelect.propTypes = {
|
||||
dispatch: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
value: PropTypes.arrayOf(PropTypes.string),
|
||||
onChange: PropTypes.func,
|
||||
|
||||
@@ -54,35 +54,20 @@ body[data-theme="dark"]
|
||||
.tag-label
|
||||
color $ui-dark-text-color
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.tag
|
||||
background-color $ui-solarized-dark-tag-backgroundColor
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.tag
|
||||
background-color get-theme-var(theme, 'tag-backgroundColor')
|
||||
|
||||
.tag-removeButton
|
||||
border-color $ui-button--focus-borderColor
|
||||
background-color transparent
|
||||
.tag-removeButton
|
||||
border-color $ui-button--focus-borderColor
|
||||
background-color transparent
|
||||
|
||||
.tag-label
|
||||
color $ui-solarized-dark-text-color
|
||||
.tag-label
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.tag
|
||||
background-color $ui-monokai-tag-backgroundColor
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
.tag-removeButton
|
||||
border-color $ui-button--focus-borderColor
|
||||
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
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
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,17 +4,35 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ToggleModeButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const ToggleModeButton = ({
|
||||
onClick, editorType
|
||||
}) => (
|
||||
const ToggleModeButton = ({ onClick, editorType }) => (
|
||||
<div styleName='control-toggleModeButton'>
|
||||
<div styleName={editorType === 'SPLIT' ? 'active' : undefined} onClick={() => onClick('SPLIT')}>
|
||||
<img src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} />
|
||||
<div
|
||||
styleName={editorType === 'SPLIT' ? 'active' : undefined}
|
||||
onClick={() => onClick('SPLIT')}
|
||||
>
|
||||
<img
|
||||
src={
|
||||
editorType === 'EDITOR_PREVIEW'
|
||||
? '../resources/icon/icon-mode-markdown-off-active.svg'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : undefined} onClick={() => onClick('EDITOR_PREVIEW')}>
|
||||
<img src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
|
||||
<div
|
||||
styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : undefined}
|
||||
onClick={() => onClick('EDITOR_PREVIEW')}
|
||||
>
|
||||
<img
|
||||
src={
|
||||
editorType === 'EDITOR_PREVIEW'
|
||||
? ''
|
||||
: '../resources/icon/icon-mode-split-on-active.svg'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
|
||||
<span lang={i18n.locale} styleName='tooltip'>
|
||||
{i18n.__('Toggle Mode')}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
@@ -1,77 +1,84 @@
|
||||
.control-toggleModeButton
|
||||
height 25px
|
||||
border-radius 50px
|
||||
background-color #F4F4F4
|
||||
width 52px
|
||||
display flex
|
||||
align-items center
|
||||
position: relative
|
||||
top 2px
|
||||
.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
|
||||
|
||||
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
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.control-toggleModeButton
|
||||
background-color #373831
|
||||
.active
|
||||
background-color #f92672
|
||||
box-shadow 2px 0px 7px #222222
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.control-toggleModeButton
|
||||
background-color #44475a
|
||||
.active
|
||||
background-color #bd93f9
|
||||
box-shadow 2px 0px 7px #222222
|
||||
.control-toggleModeButton
|
||||
height 25px
|
||||
border-radius 50px
|
||||
background-color #F4F4F4
|
||||
width 52px
|
||||
display flex
|
||||
align-items center
|
||||
position: relative
|
||||
top 2px
|
||||
.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
|
||||
|
||||
.control-toggleModeButton
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
> div img
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
|
||||
.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
|
||||
|
||||
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,14 +4,12 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './TrashButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const TrashButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-trashButton'
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
const TrashButton = ({ onClick }) => (
|
||||
<button styleName='control-trashButton' onClick={e => onClick(e)}>
|
||||
<img src='../resources/icon/icon-trash.svg' />
|
||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Trash')}</span>
|
||||
<span lang={i18n.locale} styleName='tooltip'>
|
||||
{i18n.__('Trash')}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import queryString from 'query-string'
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
|
||||
class Detail extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.focusHandler = () => {
|
||||
@@ -26,43 +26,55 @@ class Detail extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
ee.on('detail:focus', this.focusHandler)
|
||||
ee.on('detail:delete', this.deleteHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
ee.off('detail:focus', this.focusHandler)
|
||||
ee.off('detail:delete', this.deleteHandler)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { location, data, match: { params }, config } = this.props
|
||||
const noteKey = location.search !== '' && queryString.parse(location.search).key
|
||||
render() {
|
||||
const {
|
||||
location,
|
||||
data,
|
||||
match: { params },
|
||||
config
|
||||
} = this.props
|
||||
const noteKey =
|
||||
location.search !== '' && queryString.parse(location.search).key
|
||||
let note = null
|
||||
|
||||
if (location.search !== '') {
|
||||
const allNotes = data.noteMap.map(note => note)
|
||||
const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
|
||||
const trashedNotes = data.trashedSet
|
||||
.toJS()
|
||||
.map(uniqueKey => data.noteMap.get(uniqueKey))
|
||||
let displayedNotes = allNotes
|
||||
|
||||
if (location.pathname.match(/\/searched/)) {
|
||||
const searchStr = params.searchword
|
||||
displayedNotes = searchStr === undefined || searchStr === '' ? allNotes
|
||||
: searchFromNotes(allNotes, searchStr)
|
||||
}
|
||||
|
||||
if (location.pathname.match(/\/tags/)) {
|
||||
displayedNotes =
|
||||
searchStr === undefined || searchStr === ''
|
||||
? allNotes
|
||||
: searchFromNotes(allNotes, searchStr)
|
||||
} else if (location.pathname.match(/^\/tags/)) {
|
||||
const listOfTags = params.tagname.split(' ')
|
||||
displayedNotes = data.noteMap.map(note => note).filter(note =>
|
||||
listOfTags.every(tag => note.tags.includes(tag))
|
||||
)
|
||||
displayedNotes = data.noteMap
|
||||
.map(note => note)
|
||||
.filter(note => listOfTags.every(tag => note.tags.includes(tag)))
|
||||
}
|
||||
|
||||
if (location.pathname.match(/\/trashed/)) {
|
||||
if (location.pathname.match(/^\/trashed/)) {
|
||||
displayedNotes = trashedNotes
|
||||
} else {
|
||||
displayedNotes = _.differenceWith(displayedNotes, trashedNotes, (note, trashed) => note.key === trashed.key)
|
||||
displayedNotes = _.differenceWith(
|
||||
displayedNotes,
|
||||
trashedNotes,
|
||||
(note, trashed) => note.key === trashed.key
|
||||
)
|
||||
}
|
||||
|
||||
const noteKeys = displayedNotes.map(note => note.key)
|
||||
@@ -73,12 +85,12 @@ class Detail extends React.Component {
|
||||
|
||||
if (note == null) {
|
||||
return (
|
||||
<div styleName='root'
|
||||
style={this.props.style}
|
||||
tabIndex='0'
|
||||
>
|
||||
<div styleName='root' style={this.props.style} tabIndex='0'>
|
||||
<div styleName='empty'>
|
||||
<div styleName='empty-message'>{OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')} + N<br />{i18n.__('to create a new note')}</div>
|
||||
<div styleName='empty-message'>
|
||||
{OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')} + N<br />
|
||||
{i18n.__('to create a new note')}
|
||||
</div>
|
||||
</div>
|
||||
<StatusBar
|
||||
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
||||
|
||||
@@ -16,13 +16,16 @@ import { store } from 'browser/main/store'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import { getLocales } from 'browser/lib/Languages'
|
||||
import applyShortcuts from 'browser/main/lib/shortcutManager'
|
||||
import { chooseTheme, applyTheme } from 'browser/main/lib/ThemeManager'
|
||||
import { push } from 'connected-react-router'
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
const path = require('path')
|
||||
const electron = require('electron')
|
||||
const { remote } = electron
|
||||
|
||||
class Main extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
@@ -44,7 +47,7 @@ class Main extends React.Component {
|
||||
this.toggleFullScreen = () => this.handleFullScreenButton()
|
||||
}
|
||||
|
||||
getChildContext () {
|
||||
getChildContext() {
|
||||
const { status, config } = this.props
|
||||
|
||||
return {
|
||||
@@ -53,7 +56,7 @@ class Main extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
init () {
|
||||
init() {
|
||||
dataApi
|
||||
.addStorage({
|
||||
name: 'My Storage Location',
|
||||
@@ -91,18 +94,21 @@ class Main extends React.Component {
|
||||
type: 'SNIPPET_NOTE',
|
||||
folder: data.storage.folders[0].key,
|
||||
title: 'Snippet note example',
|
||||
description: 'Snippet note example\nYou can store a series of snippets as a single note, like Gist.',
|
||||
description:
|
||||
'Snippet note example\nYou can store a series of snippets as a single note, like Gist.',
|
||||
snippets: [
|
||||
{
|
||||
name: 'example.html',
|
||||
mode: 'html',
|
||||
content: "<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>",
|
||||
content:
|
||||
"<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>",
|
||||
linesHighlighted: []
|
||||
},
|
||||
{
|
||||
name: 'example.js',
|
||||
mode: 'javascript',
|
||||
content: "var boostnote = document.getElementById('enjoy').innerHTML\n\nconsole.log(boostnote)",
|
||||
content:
|
||||
"var boostnote = document.getElementById('hello').innerHTML\n\nconsole.log(boostnote)",
|
||||
linesHighlighted: []
|
||||
}
|
||||
]
|
||||
@@ -118,7 +124,8 @@ class Main extends React.Component {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
folder: data.storage.folders[0].key,
|
||||
title: 'Welcome to Boostnote!',
|
||||
content: '# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
|
||||
content:
|
||||
'# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
|
||||
})
|
||||
.then(note => {
|
||||
store.dispatch({
|
||||
@@ -139,16 +146,16 @@ class Main extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
const { dispatch, config } = this.props
|
||||
|
||||
const supportedThemes = ['dark', 'white', 'solarized-dark', 'monokai', 'dracula']
|
||||
this.refreshTheme = setInterval(() => {
|
||||
const conf = ConfigManager.get()
|
||||
chooseTheme(conf)
|
||||
}, 5 * 1000)
|
||||
|
||||
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
|
||||
document.body.setAttribute('data-theme', config.ui.theme)
|
||||
} else {
|
||||
document.body.setAttribute('data-theme', 'default')
|
||||
}
|
||||
chooseTheme(config)
|
||||
applyTheme(config.ui.theme)
|
||||
|
||||
if (getLocales().indexOf(config.ui.language) !== -1) {
|
||||
i18n.setLocale(config.ui.language)
|
||||
@@ -169,41 +176,57 @@ class Main extends React.Component {
|
||||
}
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
delete CodeMirror.keyMap.emacs['Ctrl-V']
|
||||
|
||||
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
|
||||
eventEmitter.on('menubar:togglemenubar', this.toggleMenuBarVisible.bind(this))
|
||||
eventEmitter.on(
|
||||
'menubar:togglemenubar',
|
||||
this.toggleMenuBarVisible.bind(this)
|
||||
)
|
||||
eventEmitter.on('dispatch:push', this.changeRoutePush.bind(this))
|
||||
eventEmitter.on('update', () => ipcRenderer.send('update-check', 'manual'))
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
eventEmitter.off('editor:fullscreen', this.toggleFullScreen)
|
||||
eventEmitter.off('menubar:togglemenubar', this.toggleMenuBarVisible.bind(this))
|
||||
eventEmitter.off(
|
||||
'menubar:togglemenubar',
|
||||
this.toggleMenuBarVisible.bind(this)
|
||||
)
|
||||
eventEmitter.off('dispatch:push', this.changeRoutePush.bind(this))
|
||||
clearInterval(this.refreshTheme)
|
||||
}
|
||||
|
||||
toggleMenuBarVisible () {
|
||||
changeRoutePush(event, destination) {
|
||||
const { dispatch } = this.props
|
||||
dispatch(push(destination))
|
||||
}
|
||||
|
||||
toggleMenuBarVisible() {
|
||||
const { config } = this.props
|
||||
const { ui } = config
|
||||
|
||||
const newUI = Object.assign(ui, {showMenuBar: !ui.showMenuBar})
|
||||
const newUI = Object.assign(ui, { showMenuBar: !ui.showMenuBar })
|
||||
const newConfig = Object.assign(config, newUI)
|
||||
ConfigManager.set(newConfig)
|
||||
}
|
||||
|
||||
handleLeftSlideMouseDown (e) {
|
||||
handleLeftSlideMouseDown(e) {
|
||||
e.preventDefault()
|
||||
this.setState({
|
||||
isLeftSliderFocused: true
|
||||
})
|
||||
}
|
||||
|
||||
handleRightSlideMouseDown (e) {
|
||||
handleRightSlideMouseDown(e) {
|
||||
e.preventDefault()
|
||||
this.setState({
|
||||
isRightSliderFocused: true
|
||||
})
|
||||
}
|
||||
|
||||
handleMouseUp (e) {
|
||||
handleMouseUp(e) {
|
||||
// Change width of NoteList component.
|
||||
if (this.state.isRightSliderFocused) {
|
||||
this.setState(
|
||||
@@ -243,7 +266,7 @@ class Main extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseMove (e) {
|
||||
handleMouseMove(e) {
|
||||
if (this.state.isRightSliderFocused) {
|
||||
const offset = this.refs.body.getBoundingClientRect().left
|
||||
let newListWidth = e.pageX - offset
|
||||
@@ -269,7 +292,7 @@ class Main extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleFullScreenButton (e) {
|
||||
handleFullScreenButton(e) {
|
||||
this.setState({ fullScreen: !this.state.fullScreen }, () => {
|
||||
const noteDetail = document.querySelector('.NoteDetail')
|
||||
const noteList = document.querySelector('.NoteList')
|
||||
@@ -283,7 +306,7 @@ class Main extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
hideLeftLists (noteDetail, noteList, mainBody) {
|
||||
hideLeftLists(noteDetail, noteList, mainBody) {
|
||||
this.setState({ noteDetailWidth: noteDetail.style.left })
|
||||
this.setState({ mainBodyWidth: mainBody.style.left })
|
||||
noteDetail.style.left = '0px'
|
||||
@@ -291,13 +314,13 @@ class Main extends React.Component {
|
||||
noteList.style.display = 'none'
|
||||
}
|
||||
|
||||
showLeftLists (noteDetail, noteList, mainBody) {
|
||||
showLeftLists(noteDetail, noteList, mainBody) {
|
||||
noteDetail.style.left = this.state.noteDetailWidth
|
||||
mainBody.style.left = this.state.mainBodyWidth
|
||||
noteList.style.display = 'inline'
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { config } = this.props
|
||||
|
||||
// the width of the navigation bar when it is folded/collapsed
|
||||
@@ -311,10 +334,16 @@ class Main extends React.Component {
|
||||
onMouseUp={e => this.handleMouseUp(e)}
|
||||
>
|
||||
<SideNav
|
||||
{..._.pick(this.props, ['dispatch', 'data', 'config', 'match', 'location'])}
|
||||
{..._.pick(this.props, [
|
||||
'dispatch',
|
||||
'data',
|
||||
'config',
|
||||
'match',
|
||||
'location'
|
||||
])}
|
||||
width={this.state.navWidth}
|
||||
/>
|
||||
{!config.isSideNavFolded &&
|
||||
{!config.isSideNavFolded && (
|
||||
<div
|
||||
styleName={
|
||||
this.state.isLeftSliderFocused ? 'slider--active' : 'slider'
|
||||
@@ -324,7 +353,8 @@ class Main extends React.Component {
|
||||
draggable='false'
|
||||
>
|
||||
<div styleName='slider-hitbox' />
|
||||
</div>}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
||||
id='main-body'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user