mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 18:26:26 +00:00
Compare commits
326 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae493cbd0e | ||
|
|
70d02e9a6d | ||
|
|
6401016424 | ||
|
|
cd031a89fb | ||
|
|
aadba79002 | ||
|
|
4a017fd8ae | ||
|
|
e6072c8fe9 | ||
|
|
f7fd99ec20 | ||
|
|
836fc13449 | ||
|
|
e1f78cd682 | ||
|
|
c69f34836a | ||
|
|
48a0db28d7 | ||
|
|
58eeb90158 | ||
|
|
c36689934d | ||
|
|
653b985018 | ||
|
|
40d90a53b2 | ||
|
|
7c3aaff635 | ||
|
|
4bb7929229 | ||
|
|
bdd5b7b3a7 | ||
|
|
a2ddb56540 | ||
|
|
7c0097951c | ||
|
|
7970016fbf | ||
|
|
129e3b283d | ||
|
|
868eefd2cf | ||
|
|
c42e1a0ab4 | ||
|
|
720475dae5 | ||
|
|
259df880ba | ||
|
|
68a328a364 | ||
|
|
3bc21cdb09 | ||
|
|
9e8ef70510 | ||
|
|
5fd822a24d | ||
|
|
8ee4dbbb5c | ||
|
|
9522a4d5d9 | ||
|
|
eefecdefbe | ||
|
|
84e670e71c | ||
|
|
c22b69234f | ||
|
|
29cd63d3a7 | ||
|
|
214ed388f2 | ||
|
|
0bd3445370 | ||
|
|
002dc9b017 | ||
|
|
ebf5a03f56 | ||
|
|
2797faafe5 | ||
|
|
84ac739993 | ||
|
|
13c37f046f | ||
|
|
2631cc3747 | ||
|
|
5873e8e896 | ||
|
|
6dc633c2a1 | ||
|
|
8b07126285 | ||
|
|
93a120543a | ||
|
|
af0aa4a567 | ||
|
|
58fd1f0c46 | ||
|
|
030041932e | ||
|
|
8dbf456398 | ||
|
|
3a90a078ce | ||
|
|
c30957fc9f | ||
|
|
f6db946c9a | ||
|
|
c6c0d4c62a | ||
|
|
57befc4ccb | ||
|
|
e716af75ed | ||
|
|
efe2bea64b | ||
|
|
9b926326ef | ||
|
|
de71033fe2 | ||
|
|
2c10bf251d | ||
|
|
8af50aa5bd | ||
|
|
05bedfe3d4 | ||
|
|
a1929dac8a | ||
|
|
834ecc643a | ||
|
|
60baabf7e7 | ||
|
|
185a149d74 | ||
|
|
5d62dd2002 | ||
|
|
9eaa6b5cec | ||
|
|
6ff03bbb95 | ||
|
|
9fac6bca64 | ||
|
|
88856b788a | ||
|
|
eda4e46d9f | ||
|
|
35bcbbbae4 | ||
|
|
22e2c3da1f | ||
|
|
b526d48946 | ||
|
|
91a95b7c20 | ||
|
|
d634e1124a | ||
|
|
309e159df1 | ||
|
|
ffae53326a | ||
|
|
ddd1522e19 | ||
|
|
4bc0cccb24 | ||
|
|
72fbefa300 | ||
|
|
30378eeb50 | ||
|
|
03293c0d25 | ||
|
|
2e3f6e39f6 | ||
|
|
e4d4041c6b | ||
|
|
166a5c10a7 | ||
|
|
1fec81cc3e | ||
|
|
9c247bcb22 | ||
|
|
fc88a49acc | ||
|
|
8ccf490e9b | ||
|
|
ea768f982e | ||
|
|
172ea82954 | ||
|
|
10500c3c1c | ||
|
|
225916fbba | ||
|
|
2fce78422b | ||
|
|
8132dd6847 | ||
|
|
4caee1e103 | ||
|
|
ca0b03e97c | ||
|
|
f03178bb8d | ||
|
|
d083a86138 | ||
|
|
8216b992ea | ||
|
|
8e74ee7dde | ||
|
|
b207fe14df | ||
|
|
92cfa21be6 | ||
|
|
5fd482428a | ||
|
|
98b09f7edc | ||
|
|
7b83a34777 | ||
|
|
aeb63ec901 | ||
|
|
436093e0b6 | ||
|
|
d7ee06ce6d | ||
|
|
e72003009d | ||
|
|
cfe8235a36 | ||
|
|
8c1ac9c5b3 | ||
|
|
77e3a7d43a | ||
|
|
53728a0f4a | ||
|
|
06f33d9a63 | ||
|
|
ed2698ecc3 | ||
|
|
0cb5554ae5 | ||
|
|
89850c0b22 | ||
|
|
d78b94f4e8 | ||
|
|
c2c50817f1 | ||
|
|
680c2a2904 | ||
|
|
a1085e3863 | ||
|
|
37f6a05170 | ||
|
|
0d296c3b25 | ||
|
|
73caa2508e | ||
|
|
69e012a6f0 | ||
|
|
21251a1915 | ||
|
|
67143ba2d5 | ||
|
|
d399cba4c0 | ||
|
|
9cad7cd025 | ||
|
|
a593842265 | ||
|
|
2f4eb595f6 | ||
|
|
bfcf349ffe | ||
|
|
2bd78cd47f | ||
|
|
713615e28b | ||
|
|
2b2f17525e | ||
|
|
cd6233a3d7 | ||
|
|
f76224bd17 | ||
|
|
8afa373726 | ||
|
|
199f2202e0 | ||
|
|
86a6311f75 | ||
|
|
0ca4e6ca4f | ||
|
|
50bce4892f | ||
|
|
ca345cf008 | ||
|
|
e0e1290fae | ||
|
|
a84b2611e4 | ||
|
|
ec31fab344 | ||
|
|
1b96eee4de | ||
|
|
a6af5de3e1 | ||
|
|
52b3068330 | ||
|
|
0934c08dfe | ||
|
|
f0428fde66 | ||
|
|
b3f57a67c4 | ||
|
|
8bc2b1262b | ||
|
|
fb24efd3de | ||
|
|
ce594b0b5a | ||
|
|
7b39ab4ec4 | ||
|
|
f717ed9f66 | ||
|
|
d3091a5384 | ||
|
|
2a6d950a4b | ||
|
|
b03b9d5334 | ||
|
|
c9cb31bd02 | ||
|
|
905d6860fc | ||
|
|
6f52744b0f | ||
|
|
007d3e52c5 | ||
|
|
f10fa632ca | ||
|
|
266323b90b | ||
|
|
60707a8f45 | ||
|
|
b89896a4e7 | ||
|
|
d69fd12fb9 | ||
|
|
0bdcfa6028 | ||
|
|
55b8488901 | ||
|
|
8ddbf2067b | ||
|
|
fa5cebda6d | ||
|
|
4fd01b4234 | ||
|
|
0f354f4f06 | ||
|
|
f94a197828 | ||
|
|
a26ff660b0 | ||
|
|
50cc648799 | ||
|
|
a7946805ae | ||
|
|
f3b2969b42 | ||
|
|
d6c3490165 | ||
|
|
6ee594d4d1 | ||
|
|
7e1596de30 | ||
|
|
67bba043ed | ||
|
|
26d7f4923d | ||
|
|
e9218d1088 | ||
|
|
03fd1e29e3 | ||
|
|
ff59af6b51 | ||
|
|
73ba8b8b13 | ||
|
|
ffc3fb770c | ||
|
|
d58ea70a95 | ||
|
|
90e8dd038d | ||
|
|
56d1e3edaa | ||
|
|
83a9e54896 | ||
|
|
ab038b1f31 | ||
|
|
f5a9d3928c | ||
|
|
2bc0bce1b5 | ||
|
|
372933fd99 | ||
|
|
9112347e95 | ||
|
|
30548a68e4 | ||
|
|
ce052d1691 | ||
|
|
765ba8c867 | ||
|
|
a20c0cd49e | ||
|
|
e06ca9a056 | ||
|
|
d04048c749 | ||
|
|
2d0f7589ea | ||
|
|
dc60be404a | ||
|
|
9ff5cc51f9 | ||
|
|
e9de8f42e5 | ||
|
|
5bd0499ae4 | ||
|
|
99e706bcd2 | ||
|
|
239edb0605 | ||
|
|
bf3f5a5971 | ||
|
|
92be3f32d6 | ||
|
|
106f5a53ff | ||
|
|
8c43f3d567 | ||
|
|
2e09501c8a | ||
|
|
a2592e48c8 | ||
|
|
291d76674b | ||
|
|
78957cf128 | ||
|
|
e88694b049 | ||
|
|
5e7bdf7354 | ||
|
|
2e3e0bc1d8 | ||
|
|
b33e6b232c | ||
|
|
df6b083670 | ||
|
|
05009d40c4 | ||
|
|
ea27a3b449 | ||
|
|
2831b0bd2a | ||
|
|
32e22dd507 | ||
|
|
2ee9951853 | ||
|
|
b1912135ed | ||
|
|
bed3d42923 | ||
|
|
c4ec69a43f | ||
|
|
24b004bb2d | ||
|
|
84925b24b5 | ||
|
|
c02b91dfd4 | ||
|
|
066d97220f | ||
|
|
61ed47dda0 | ||
|
|
68c0f210cc | ||
|
|
6c542750f4 | ||
|
|
25440a26ee | ||
|
|
ab393b1f6d | ||
|
|
e643147b69 | ||
|
|
6c4aa71cbc | ||
|
|
9930ba8748 | ||
|
|
fbb8b4687b | ||
|
|
01b1c49738 | ||
|
|
c9c28eda1b | ||
|
|
744bcba599 | ||
|
|
d76db726c4 | ||
|
|
71ec528a87 | ||
|
|
fbbc93900e | ||
|
|
33b45737c9 | ||
|
|
4a55f78a48 | ||
|
|
a82a79e25c | ||
|
|
6ec2124a9c | ||
|
|
1f1ef1440e | ||
|
|
a7d0a4bdac | ||
|
|
d2129ffac6 | ||
|
|
a4782f0663 | ||
|
|
16794b9d78 | ||
|
|
a76aed2d4e | ||
|
|
d2163dacf9 | ||
|
|
158305346f | ||
|
|
e692432242 | ||
|
|
a7b85b123e | ||
|
|
ddcd722598 | ||
|
|
358458a937 | ||
|
|
8925f7c381 | ||
|
|
ff2e39901a | ||
|
|
90ff0f43ea | ||
|
|
442c352c8d | ||
|
|
e91b7fb082 | ||
|
|
88de66a31f | ||
|
|
d3b3e45800 | ||
|
|
50d2f90621 | ||
|
|
8ccf6cb8a3 | ||
|
|
2e9b478824 | ||
|
|
89b2d54725 | ||
|
|
3d0af2d8ca | ||
|
|
813b433f4d | ||
|
|
0bce96b0c6 | ||
|
|
1d4f1764fc | ||
|
|
2994420160 | ||
|
|
65d8d7282f | ||
|
|
47af3f09fc | ||
|
|
f4024f4683 | ||
|
|
ee0ed6df7a | ||
|
|
d3fbba3572 | ||
|
|
4a6b22f5b7 | ||
|
|
d070305002 | ||
|
|
a8500150b0 | ||
|
|
02fb1d01ad | ||
|
|
497dee038f | ||
|
|
a4af77f91e | ||
|
|
f2a4e1d230 | ||
|
|
8560901f80 | ||
|
|
daea604c60 | ||
|
|
7aedb59f26 | ||
|
|
0be1c2f464 | ||
|
|
0dbfaf0e79 | ||
|
|
4147399cda | ||
|
|
cc667ac738 | ||
|
|
022915ffc9 | ||
|
|
eafccc4fc4 | ||
|
|
ce440351a5 | ||
|
|
be94edde0f | ||
|
|
a32cfc8aff | ||
|
|
b46b958105 | ||
|
|
11f8cfe0e6 | ||
|
|
e1e3cc7999 | ||
|
|
9a445e34fd | ||
|
|
ee78e113de | ||
|
|
f0144233f9 | ||
|
|
4f98995fe4 | ||
|
|
56231edc3a | ||
|
|
871ab428c2 | ||
|
|
a9b75f752e | ||
|
|
191f2cacbf | ||
|
|
8ae7d96cc7 |
@@ -19,5 +19,8 @@
|
|||||||
"FileReader": true,
|
"FileReader": true,
|
||||||
"localStorage": true,
|
"localStorage": true,
|
||||||
"fetch": true
|
"fetch": true
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"jest": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 6
|
- 7
|
||||||
script:
|
script:
|
||||||
- npm run lint && npm run test
|
- npm run lint && npm run test
|
||||||
|
- yarn jest
|
||||||
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
|
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
|
||||||
after_success:
|
after_success:
|
||||||
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
|
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
|
||||||
|
|||||||
@@ -1,10 +1,25 @@
|
|||||||
|
# Current behavior
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Please paste some **screenshots** with the **developer tool** open (console tab) when you report a bug.
|
Please paste some **screenshots** with the **developer tool** open (console tab) when you report a bug.
|
||||||
|
|
||||||
If your issue is regarding boostnote mobile, move to https://github.com/BoostIO/boostnote-mobile.
|
If your issue is regarding boostnote mobile, move to https://github.com/BoostIO/boostnote-mobile.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
# Expected behavior
|
||||||
|
|
||||||
|
# Steps to reproduce
|
||||||
|
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
|
||||||
|
- Version :
|
||||||
|
- OS Version and name :
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Love Boostnote? Please consider supporting us via OpenCollective:
|
Love Boostnote? Please consider supporting us on IssueHunt:
|
||||||
👉 https://opencollective.com/boostnoteio
|
👉 https://issuehunt.io/repos/53266139
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -3,34 +3,20 @@ import React from 'react'
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
import 'codemirror-mode-elixir'
|
import 'codemirror-mode-elixir'
|
||||||
import path from 'path'
|
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
||||||
import copyImage from 'browser/main/lib/dataApi/copyImage'
|
import convertModeName from 'browser/lib/convertModeName'
|
||||||
import { findStorage } from 'browser/lib/findStorage'
|
|
||||||
import fs from 'fs'
|
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
import iconv from 'iconv-lite'
|
import iconv from 'iconv-lite'
|
||||||
|
import crypto from 'crypto'
|
||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
import fs from 'fs'
|
||||||
const { ipcRenderer } = require('electron')
|
const { ipcRenderer } = require('electron')
|
||||||
|
|
||||||
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||||
|
|
||||||
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
||||||
const buildCMRulers = (rulers, enableRulers) =>
|
const buildCMRulers = (rulers, enableRulers) =>
|
||||||
enableRulers ? rulers.map(ruler => ({ column: ruler })) : []
|
enableRulers ? rulers.map(ruler => ({column: ruler})) : []
|
||||||
|
|
||||||
function pass (name) {
|
|
||||||
switch (name) {
|
|
||||||
case 'ejs':
|
|
||||||
return 'Embedded Javascript'
|
|
||||||
case 'html_ruby':
|
|
||||||
return 'Embedded Ruby'
|
|
||||||
case 'objectivec':
|
|
||||||
return 'Objective C'
|
|
||||||
case 'text':
|
|
||||||
return 'Plain Text'
|
|
||||||
default:
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class CodeEditor extends React.Component {
|
export default class CodeEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -52,6 +38,9 @@ export default class CodeEditor extends React.Component {
|
|||||||
el = el.parentNode
|
el = el.parentNode
|
||||||
}
|
}
|
||||||
this.props.onBlur != null && this.props.onBlur(e)
|
this.props.onBlur != null && this.props.onBlur(e)
|
||||||
|
|
||||||
|
const {storageKey, noteKey} = this.props
|
||||||
|
attachmentManagement.deleteAttachmentsNotPresentInNote(this.editor.getValue(), storageKey, noteKey)
|
||||||
}
|
}
|
||||||
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
||||||
this.loadStyleHandler = (e) => {
|
this.loadStyleHandler = (e) => {
|
||||||
@@ -94,8 +83,21 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { rulers, enableRulers } = this.props
|
const { rulers, enableRulers } = this.props
|
||||||
this.value = this.props.value
|
const expandSnippet = this.expandSnippet.bind(this)
|
||||||
|
|
||||||
|
const 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.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
if (!fs.existsSync(consts.SNIPPET_FILE)) {
|
||||||
|
fs.writeFileSync(consts.SNIPPET_FILE, JSON.stringify(defaultSnippet, null, 4), 'utf8')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.value = this.props.value
|
||||||
this.editor = CodeMirror(this.refs.root, {
|
this.editor = CodeMirror(this.refs.root, {
|
||||||
rulers: buildCMRulers(rulers, enableRulers),
|
rulers: buildCMRulers(rulers, enableRulers),
|
||||||
value: this.props.value,
|
value: this.props.value,
|
||||||
@@ -116,6 +118,8 @@ export default class CodeEditor extends React.Component {
|
|||||||
Tab: function (cm) {
|
Tab: function (cm) {
|
||||||
const cursor = cm.getCursor()
|
const cursor = cm.getCursor()
|
||||||
const line = cm.getLine(cursor.line)
|
const line = cm.getLine(cursor.line)
|
||||||
|
const cursorPosition = cursor.ch
|
||||||
|
const charBeforeCursor = line.substr(cursorPosition - 1, 1)
|
||||||
if (cm.somethingSelected()) cm.indentSelection('add')
|
if (cm.somethingSelected()) cm.indentSelection('add')
|
||||||
else {
|
else {
|
||||||
const tabs = cm.getOption('indentWithTabs')
|
const tabs = cm.getOption('indentWithTabs')
|
||||||
@@ -127,6 +131,16 @@ export default class CodeEditor extends React.Component {
|
|||||||
cm.execCommand('insertSoftTab')
|
cm.execCommand('insertSoftTab')
|
||||||
}
|
}
|
||||||
cm.execCommand('goLineEnd')
|
cm.execCommand('goLineEnd')
|
||||||
|
} else if (!charBeforeCursor.match(/\t|\s|\r|\n/) && cursor.ch > 1) {
|
||||||
|
// text expansion on tab key if the char before is alphabet
|
||||||
|
const snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8'))
|
||||||
|
if (expandSnippet(line, cursor, cm, snippets) === false) {
|
||||||
|
if (tabs) {
|
||||||
|
cm.execCommand('insertTab')
|
||||||
|
} else {
|
||||||
|
cm.execCommand('insertSoftTab')
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (tabs) {
|
if (tabs) {
|
||||||
cm.execCommand('insertTab')
|
cm.execCommand('insertTab')
|
||||||
@@ -170,6 +184,73 @@ export default class CodeEditor extends React.Component {
|
|||||||
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expandSnippet (line, cursor, cm, snippets) {
|
||||||
|
const wordBeforeCursor = this.getWordBeforeCursor(line, cursor.line, cursor.ch)
|
||||||
|
const templateCursorString = ':{}'
|
||||||
|
for (let i = 0; i < snippets.length; i++) {
|
||||||
|
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
|
||||||
|
if (snippets[i].content.indexOf(templateCursorString) !== -1) {
|
||||||
|
const snippetLines = snippets[i].content.split('\n')
|
||||||
|
let cursorLineNumber = 0
|
||||||
|
let cursorLinePosition = 0
|
||||||
|
for (let j = 0; j < snippetLines.length; j++) {
|
||||||
|
const cursorIndex = snippetLines[j].indexOf(templateCursorString)
|
||||||
|
if (cursorIndex !== -1) {
|
||||||
|
cursorLineNumber = j
|
||||||
|
cursorLinePosition = cursorIndex
|
||||||
|
cm.replaceRange(
|
||||||
|
snippets[i].content.replace(templateCursorString, ''),
|
||||||
|
wordBeforeCursor.range.from,
|
||||||
|
wordBeforeCursor.range.to
|
||||||
|
)
|
||||||
|
cm.setCursor({ line: cursor.line + cursorLineNumber, ch: cursorLinePosition })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cm.replaceRange(
|
||||||
|
snippets[i].content,
|
||||||
|
wordBeforeCursor.range.from,
|
||||||
|
wordBeforeCursor.range.to
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
getWordBeforeCursor (line, lineNumber, cursorPosition) {
|
||||||
|
let wordBeforeCursor = ''
|
||||||
|
const originCursorPosition = cursorPosition
|
||||||
|
const emptyChars = /\t|\s|\r|\n/
|
||||||
|
|
||||||
|
// to prevent the word to expand is long that will crash the whole app
|
||||||
|
// the safeStop is there to stop user to expand words that longer than 20 chars
|
||||||
|
const safeStop = 20
|
||||||
|
|
||||||
|
while (cursorPosition > 0) {
|
||||||
|
const currentChar = line.substr(cursorPosition - 1, 1)
|
||||||
|
// if char is not an empty char
|
||||||
|
if (!emptyChars.test(currentChar)) {
|
||||||
|
wordBeforeCursor = currentChar + wordBeforeCursor
|
||||||
|
} else if (wordBeforeCursor.length >= safeStop) {
|
||||||
|
throw new Error('Your snippet trigger is too long !')
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cursorPosition--
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: wordBeforeCursor,
|
||||||
|
range: {
|
||||||
|
from: {line: lineNumber, ch: originCursorPosition},
|
||||||
|
to: {line: lineNumber, ch: cursorPosition}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
quitEditor () {
|
quitEditor () {
|
||||||
document.querySelector('textarea').blur()
|
document.querySelector('textarea').blur()
|
||||||
}
|
}
|
||||||
@@ -187,7 +268,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
componentDidUpdate (prevProps, prevState) {
|
componentDidUpdate (prevProps, prevState) {
|
||||||
let needRefresh = false
|
let needRefresh = false
|
||||||
const { rulers, enableRulers } = this.props
|
const {rulers, enableRulers} = this.props
|
||||||
if (prevProps.mode !== this.props.mode) {
|
if (prevProps.mode !== this.props.mode) {
|
||||||
this.setMode(this.props.mode)
|
this.setMode(this.props.mode)
|
||||||
}
|
}
|
||||||
@@ -231,7 +312,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setMode (mode) {
|
setMode (mode) {
|
||||||
let syntax = CodeMirror.findModeByName(pass(mode))
|
let syntax = CodeMirror.findModeByName(convertModeName(mode))
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
|
|
||||||
this.editor.setOption('mode', syntax.mime)
|
this.editor.setOption('mode', syntax.mime)
|
||||||
@@ -275,28 +356,19 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.setCursor(cursor)
|
this.editor.setCursor(cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDropImage (e) {
|
handleDropImage (dropEvent) {
|
||||||
e.preventDefault()
|
dropEvent.preventDefault()
|
||||||
const ValidImageTypes = ['image/gif', 'image/jpeg', 'image/png']
|
const {storageKey, noteKey} = this.props
|
||||||
|
attachmentManagement.handleAttachmentDrop(this, storageKey, noteKey, dropEvent)
|
||||||
const file = e.dataTransfer.files[0]
|
|
||||||
const filePath = file.path
|
|
||||||
const filename = path.basename(filePath)
|
|
||||||
const fileType = file['type']
|
|
||||||
|
|
||||||
copyImage(filePath, this.props.storageKey).then((imagePath) => {
|
|
||||||
var showPreview = ValidImageTypes.indexOf(fileType) > 0
|
|
||||||
const imageMd = `${showPreview ? '!' : ''}[${filename}](${path.join('/:storage', imagePath)})`
|
|
||||||
this.insertImageMd(imageMd)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
insertImageMd (imageMd) {
|
insertAttachmentMd (imageMd) {
|
||||||
this.editor.replaceSelection(imageMd)
|
this.editor.replaceSelection(imageMd)
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePaste (editor, e) {
|
handlePaste (editor, e) {
|
||||||
const clipboardData = e.clipboardData
|
const clipboardData = e.clipboardData
|
||||||
|
const {storageKey, noteKey} = this.props
|
||||||
const dataTransferItem = clipboardData.items[0]
|
const dataTransferItem = clipboardData.items[0]
|
||||||
const pastedTxt = clipboardData.getData('text')
|
const pastedTxt = clipboardData.getData('text')
|
||||||
const isURL = (str) => {
|
const isURL = (str) => {
|
||||||
@@ -306,38 +378,28 @@ export default class CodeEditor extends React.Component {
|
|||||||
const isInLinkTag = (editor) => {
|
const isInLinkTag = (editor) => {
|
||||||
const startCursor = editor.getCursor('start')
|
const startCursor = editor.getCursor('start')
|
||||||
const prevChar = editor.getRange(
|
const prevChar = editor.getRange(
|
||||||
{ line: startCursor.line, ch: startCursor.ch - 2 },
|
{line: startCursor.line, ch: startCursor.ch - 2},
|
||||||
{ line: startCursor.line, ch: startCursor.ch }
|
{line: startCursor.line, ch: startCursor.ch}
|
||||||
)
|
)
|
||||||
const endCursor = editor.getCursor('end')
|
const endCursor = editor.getCursor('end')
|
||||||
const nextChar = editor.getRange(
|
const nextChar = editor.getRange(
|
||||||
{ line: endCursor.line, ch: endCursor.ch },
|
{line: endCursor.line, ch: endCursor.ch},
|
||||||
{ line: endCursor.line, ch: endCursor.ch + 1 }
|
{line: endCursor.line, ch: endCursor.ch + 1}
|
||||||
)
|
)
|
||||||
return prevChar === '](' && nextChar === ')'
|
return prevChar === '](' && nextChar === ')'
|
||||||
}
|
}
|
||||||
if (dataTransferItem.type.match('image')) {
|
if (dataTransferItem.type.match('image')) {
|
||||||
const blob = dataTransferItem.getAsFile()
|
attachmentManagement.handlePastImageEvent(this, storageKey, noteKey, dataTransferItem)
|
||||||
const reader = new FileReader()
|
|
||||||
let base64data
|
|
||||||
|
|
||||||
reader.readAsDataURL(blob)
|
|
||||||
reader.onloadend = () => {
|
|
||||||
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
|
|
||||||
base64data += base64data.replace('+', ' ')
|
|
||||||
const binaryData = new Buffer(base64data, 'base64').toString('binary')
|
|
||||||
const imageName = Math.random().toString(36).slice(-16)
|
|
||||||
const storagePath = findStorage(this.props.storageKey).path
|
|
||||||
const imageDir = path.join(storagePath, 'images')
|
|
||||||
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
|
|
||||||
const imagePath = path.join(imageDir, `${imageName}.png`)
|
|
||||||
fs.writeFile(imagePath, binaryData, 'binary')
|
|
||||||
const imageMd = `})`
|
|
||||||
this.insertImageMd(imageMd)
|
|
||||||
}
|
|
||||||
} else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
|
} else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
|
||||||
this.handlePasteUrl(e, editor, pastedTxt)
|
this.handlePasteUrl(e, editor, pastedTxt)
|
||||||
}
|
}
|
||||||
|
if (attachmentManagement.isAttachmentLink(pastedTxt)) {
|
||||||
|
attachmentManagement.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
|
||||||
|
.then((modifiedText) => {
|
||||||
|
this.editor.replaceSelection(modifiedText)
|
||||||
|
})
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScroll (e) {
|
handleScroll (e) {
|
||||||
@@ -351,24 +413,58 @@ export default class CodeEditor extends React.Component {
|
|||||||
const taggedUrl = `<${pastedTxt}>`
|
const taggedUrl = `<${pastedTxt}>`
|
||||||
editor.replaceSelection(taggedUrl)
|
editor.replaceSelection(taggedUrl)
|
||||||
|
|
||||||
|
const isImageReponse = (response) => {
|
||||||
|
return response.headers.has('content-type') &&
|
||||||
|
response.headers.get('content-type').match(/^image\/.+$/)
|
||||||
|
}
|
||||||
|
const replaceTaggedUrl = (replacement) => {
|
||||||
|
const value = editor.getValue()
|
||||||
|
const cursor = editor.getCursor()
|
||||||
|
const newValue = value.replace(taggedUrl, replacement)
|
||||||
|
const newCursor = Object.assign({}, cursor, { ch: cursor.ch + newValue.length - value.length })
|
||||||
|
editor.setValue(newValue)
|
||||||
|
editor.setCursor(newCursor)
|
||||||
|
}
|
||||||
|
|
||||||
fetch(pastedTxt, {
|
fetch(pastedTxt, {
|
||||||
method: 'get'
|
method: 'get'
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
return this.decodeResponse(response)
|
if (isImageReponse(response)) {
|
||||||
}).then((response) => {
|
return this.mapImageResponse(response, pastedTxt)
|
||||||
const parsedResponse = (new window.DOMParser()).parseFromString(response, 'text/html')
|
} else {
|
||||||
const value = editor.getValue()
|
return this.mapNormalResponse(response, pastedTxt)
|
||||||
const cursor = editor.getCursor()
|
}
|
||||||
const LinkWithTitle = `[${parsedResponse.title}](${pastedTxt})`
|
}).then((replacement) => {
|
||||||
const newValue = value.replace(taggedUrl, LinkWithTitle)
|
replaceTaggedUrl(replacement)
|
||||||
editor.setValue(newValue)
|
|
||||||
editor.setCursor(cursor)
|
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
const value = editor.getValue()
|
replaceTaggedUrl(pastedTxt)
|
||||||
const newValue = value.replace(taggedUrl, pastedTxt)
|
})
|
||||||
const cursor = editor.getCursor()
|
}
|
||||||
editor.setValue(newValue)
|
|
||||||
editor.setCursor(cursor)
|
mapNormalResponse (response, pastedTxt) {
|
||||||
|
return this.decodeResponse(response).then((body) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const parsedBody = (new window.DOMParser()).parseFromString(body, 'text/html')
|
||||||
|
const linkWithTitle = `[${parsedBody.title}](${pastedTxt})`
|
||||||
|
resolve(linkWithTitle)
|
||||||
|
} catch (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
mapImageResponse (response, pastedTxt) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const url = response.url
|
||||||
|
const name = url.substring(url.lastIndexOf('/') + 1)
|
||||||
|
const imageLinkWithName = ``
|
||||||
|
resolve(imageLinkWithName)
|
||||||
|
} catch (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,11 +494,12 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { className, fontSize } = this.props
|
const {className, fontSize} = this.props
|
||||||
let fontFamily = this.props.fontFamily
|
let fontFamily = this.props.fontFamily
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
||||||
? [fontFamily].concat(defaultEditorFontFamily)
|
? [fontFamily].concat(defaultEditorFontFamily)
|
||||||
: defaultEditorFontFamily
|
: defaultEditorFontFamily
|
||||||
|
const width = this.props.width
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={className == null
|
className={className == null
|
||||||
@@ -413,7 +510,8 @@ export default class CodeEditor extends React.Component {
|
|||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
style={{
|
style={{
|
||||||
fontFamily: fontFamily.join(', '),
|
fontFamily: fontFamily.join(', '),
|
||||||
fontSize: fontSize
|
fontSize: fontSize,
|
||||||
|
width: width
|
||||||
}}
|
}}
|
||||||
onDrop={(e) => this.handleDropImage(e)}
|
onDrop={(e) => this.handleDropImage(e)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import styles from './MarkdownEditor.styl'
|
|||||||
import CodeEditor from 'browser/components/CodeEditor'
|
import CodeEditor from 'browser/components/CodeEditor'
|
||||||
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
import {findStorage} from 'browser/lib/findStorage'
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
|
|
||||||
class MarkdownEditor extends React.Component {
|
class MarkdownEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -223,7 +223,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { className, value, config, storageKey } = this.props
|
const {className, value, config, storageKey, noteKey} = this.props
|
||||||
|
|
||||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
@@ -263,6 +263,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
displayLineNumbers={config.editor.displayLineNumbers}
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
storageKey={storageKey}
|
storageKey={storageKey}
|
||||||
|
noteKey={noteKey}
|
||||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={(e) => this.handleChange(e)}
|
||||||
onBlur={(e) => this.handleBlur(e)}
|
onBlur={(e) => this.handleBlur(e)}
|
||||||
@@ -282,6 +283,8 @@ class MarkdownEditor extends React.Component {
|
|||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
scrollPastEnd={config.preview.scrollPastEnd}
|
scrollPastEnd={config.preview.scrollPastEnd}
|
||||||
smartQuotes={config.preview.smartQuotes}
|
smartQuotes={config.preview.smartQuotes}
|
||||||
|
smartArrows={config.preview.smartArrows}
|
||||||
|
breaks={config.preview.breaks}
|
||||||
sanitize={config.preview.sanitize}
|
sanitize={config.preview.sanitize}
|
||||||
ref='preview'
|
ref='preview'
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||||
@@ -293,6 +296,9 @@ class MarkdownEditor extends React.Component {
|
|||||||
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
||||||
showCopyNotification={config.ui.showCopyNotification}
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
storagePath={storage.path}
|
storagePath={storage.path}
|
||||||
|
noteKey={noteKey}
|
||||||
|
customCSS={config.preview.customCSS}
|
||||||
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,18 +10,23 @@ import flowchart from 'flowchart'
|
|||||||
import SequenceDiagram from 'js-sequence-diagrams'
|
import SequenceDiagram from 'js-sequence-diagrams'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
||||||
|
import convertModeName from 'browser/lib/convertModeName'
|
||||||
import copy from 'copy-to-clipboard'
|
import copy from 'copy-to-clipboard'
|
||||||
import mdurl from 'mdurl'
|
import mdurl from 'mdurl'
|
||||||
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
||||||
import {escapeHtmlCharacters} from 'browser/lib/utils'
|
import { escapeHtmlCharacters } from 'browser/lib/utils'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
|
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
|
||||||
|
|
||||||
const { app } = remote
|
const { app } = remote
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const fileUrl = require('file-url')
|
||||||
|
|
||||||
const dialog = remote.dialog
|
const dialog = remote.dialog
|
||||||
|
|
||||||
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
||||||
const appPath = 'file://' + (process.env.NODE_ENV === 'production'
|
const appPath = fileUrl(process.env.NODE_ENV === 'production'
|
||||||
? app.getAppPath()
|
? app.getAppPath()
|
||||||
: path.resolve())
|
: path.resolve())
|
||||||
const CSS_FILES = [
|
const CSS_FILES = [
|
||||||
@@ -29,7 +34,7 @@ const CSS_FILES = [
|
|||||||
`${appPath}/node_modules/codemirror/lib/codemirror.css`
|
`${appPath}/node_modules/codemirror/lib/codemirror.css`
|
||||||
]
|
]
|
||||||
|
|
||||||
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd) {
|
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS) {
|
||||||
return `
|
return `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Lato';
|
font-family: 'Lato';
|
||||||
@@ -49,7 +54,19 @@ function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scro
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-rendering: optimizeLegibility;
|
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');
|
||||||
|
}
|
||||||
|
${allowCustomCSS ? customCSS : ''}
|
||||||
${markdownStyle}
|
${markdownStyle}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: '${fontFamily.join("','")}';
|
font-family: '${fontFamily.join("','")}';
|
||||||
font-size: ${fontSize}px;
|
font-size: ${fontSize}px;
|
||||||
@@ -101,6 +118,16 @@ h2 {
|
|||||||
body p {
|
body p {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body[data-theme="${theme}"] {
|
||||||
|
color: #000;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.clipboardButton {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +140,6 @@ if (!OSX) {
|
|||||||
defaultFontFamily.unshift('meiryo')
|
defaultFontFamily.unshift('meiryo')
|
||||||
}
|
}
|
||||||
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
||||||
|
|
||||||
export default class MarkdownPreview extends React.Component {
|
export default class MarkdownPreview extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -123,7 +149,6 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.mouseUpHandler = (e) => this.handleMouseUp(e)
|
this.mouseUpHandler = (e) => this.handleMouseUp(e)
|
||||||
this.DoubleClickHandler = (e) => this.handleDoubleClick(e)
|
this.DoubleClickHandler = (e) => this.handleDoubleClick(e)
|
||||||
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
|
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
|
||||||
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
|
|
||||||
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
||||||
this.saveAsTextHandler = () => this.handleSaveAsText()
|
this.saveAsTextHandler = () => this.handleSaveAsText()
|
||||||
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
||||||
@@ -136,29 +161,14 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initMarkdown () {
|
initMarkdown () {
|
||||||
const { smartQuotes, sanitize } = this.props
|
const { smartQuotes, sanitize, breaks } = this.props
|
||||||
this.markdown = new Markdown({
|
this.markdown = new Markdown({
|
||||||
typographer: smartQuotes,
|
typographer: smartQuotes,
|
||||||
sanitize
|
sanitize,
|
||||||
|
breaks
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePreviewAnchorClick (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
const anchor = e.target.closest('a')
|
|
||||||
const href = anchor.getAttribute('href')
|
|
||||||
if (_.isString(href) && href.match(/^#/)) {
|
|
||||||
const targetElement = this.refs.root.contentWindow.document.getElementById(href.substring(1, href.length))
|
|
||||||
if (targetElement != null) {
|
|
||||||
this.getWindow().scrollTo(0, targetElement.offsetTop)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
shell.openExternal(href)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCheckboxClick (e) {
|
handleCheckboxClick (e) {
|
||||||
this.props.onCheckboxClick(e)
|
this.props.onCheckboxClick(e)
|
||||||
}
|
}
|
||||||
@@ -206,11 +216,13 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
handleSaveAsHtml () {
|
handleSaveAsHtml () {
|
||||||
this.exportAsDocument('html', (noteContent, exportTasks) => {
|
this.exportAsDocument('html', (noteContent, exportTasks) => {
|
||||||
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams()
|
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(escapeHtmlCharacters(noteContent))
|
||||||
|
|
||||||
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber)
|
|
||||||
const body = this.markdown.render(escapeHtmlCharacters(noteContent))
|
|
||||||
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||||
|
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath)
|
||||||
|
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
file = file.replace('file://', '')
|
file = file.replace('file://', '')
|
||||||
@@ -219,6 +231,13 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
dst: 'css'
|
dst: 'css'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
attachmentsAbsolutePaths.forEach((attachment) => {
|
||||||
|
exportTasks.push({
|
||||||
|
src: attachment,
|
||||||
|
dst: attachmentManagement.DESTINATION_FOLDER
|
||||||
|
})
|
||||||
|
})
|
||||||
|
body = attachmentManagement.removeStorageAndNoteReferences(body, this.props.noteKey)
|
||||||
|
|
||||||
let styles = ''
|
let styles = ''
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
@@ -322,7 +341,10 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
||||||
if (prevProps.smartQuotes !== this.props.smartQuotes || prevProps.sanitize !== this.props.sanitize) {
|
if (prevProps.smartQuotes !== this.props.smartQuotes ||
|
||||||
|
prevProps.sanitize !== this.props.sanitize ||
|
||||||
|
prevProps.smartArrows !== this.props.smartArrows ||
|
||||||
|
prevProps.breaks !== this.props.breaks) {
|
||||||
this.initMarkdown()
|
this.initMarkdown()
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
}
|
}
|
||||||
@@ -333,14 +355,16 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
prevProps.lineNumber !== this.props.lineNumber ||
|
prevProps.lineNumber !== this.props.lineNumber ||
|
||||||
prevProps.showCopyNotification !== this.props.showCopyNotification ||
|
prevProps.showCopyNotification !== this.props.showCopyNotification ||
|
||||||
prevProps.theme !== this.props.theme ||
|
prevProps.theme !== this.props.theme ||
|
||||||
prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
|
prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
|
||||||
|
prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
|
||||||
|
prevProps.customCSS !== this.props.customCSS) {
|
||||||
this.applyStyle()
|
this.applyStyle()
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getStyleParams () {
|
getStyleParams () {
|
||||||
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd } = this.props
|
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS } = this.props
|
||||||
let { fontFamily, codeBlockFontFamily } = this.props
|
let { fontFamily, codeBlockFontFamily } = this.props
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
||||||
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
|
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
|
||||||
@@ -349,14 +373,14 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
|
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
|
||||||
: defaultCodeBlockFontFamily
|
: defaultCodeBlockFontFamily
|
||||||
|
|
||||||
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd}
|
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStyle () {
|
applyStyle () {
|
||||||
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd} = this.getStyleParams()
|
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
|
||||||
|
|
||||||
this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme)
|
this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme)
|
||||||
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd)
|
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
|
||||||
}
|
}
|
||||||
|
|
||||||
GetCodeThemeLink (theme) {
|
GetCodeThemeLink (theme) {
|
||||||
@@ -369,9 +393,6 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rewriteIframe () {
|
rewriteIframe () {
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
|
||||||
el.removeEventListener('click', this.anchorClickHandler)
|
|
||||||
})
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
||||||
el.removeEventListener('click', this.checkboxClickHandler)
|
el.removeEventListener('click', this.checkboxClickHandler)
|
||||||
})
|
})
|
||||||
@@ -380,7 +401,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
el.removeEventListener('click', this.linkClickHandler)
|
el.removeEventListener('click', this.linkClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
const { theme, indentSize, showCopyNotification, storagePath } = this.props
|
const { theme, indentSize, showCopyNotification, storagePath, noteKey } = this.props
|
||||||
let { value, codeBlockTheme } = this.props
|
let { value, codeBlockTheme } = this.props
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
||||||
@@ -391,36 +412,25 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
|
value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.refs.root.contentWindow.document.body.innerHTML = this.markdown.render(value)
|
let renderedHTML = this.markdown.render(value)
|
||||||
|
attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey)
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath)
|
||||||
this.fixDecodedURI(el)
|
|
||||||
el.href = this.markdown.normalizeLinkText(el.href)
|
|
||||||
if (!/\/:storage/.test(el.href)) return
|
|
||||||
el.href = `file:///${this.markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.href)))}`
|
|
||||||
el.addEventListener('click', this.anchorClickHandler)
|
|
||||||
})
|
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
||||||
el.addEventListener('click', this.checkboxClickHandler)
|
el.addEventListener('click', this.checkboxClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
|
this.fixDecodedURI(el)
|
||||||
el.addEventListener('click', this.linkClickHandler)
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('img'), (el) => {
|
|
||||||
el.src = this.markdown.normalizeLinkText(el.src)
|
|
||||||
if (!/\/:storage/.test(el.src)) return
|
|
||||||
el.src = `file:///${this.markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.src)))}`
|
|
||||||
})
|
|
||||||
|
|
||||||
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
|
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
|
||||||
? codeBlockTheme
|
? codeBlockTheme
|
||||||
: 'default'
|
: 'default'
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => {
|
||||||
let syntax = CodeMirror.findModeByName(el.className)
|
let syntax = CodeMirror.findModeByName(convertModeName(el.className))
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
CodeMirror.requireMode(syntax.mode, () => {
|
CodeMirror.requireMode(syntax.mode, () => {
|
||||||
const content = htmlTextHelper.decodeEntities(el.innerHTML)
|
const content = htmlTextHelper.decodeEntities(el.innerHTML)
|
||||||
@@ -462,7 +472,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
el.innerHTML = ''
|
el.innerHTML = ''
|
||||||
diagram.drawSVG(el, opts)
|
diagram.drawSVG(el, opts)
|
||||||
_.forEach(el.querySelectorAll('a'), (el) => {
|
_.forEach(el.querySelectorAll('a'), (el) => {
|
||||||
el.addEventListener('click', this.anchorClickHandler)
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
@@ -478,7 +488,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
el.innerHTML = ''
|
el.innerHTML = ''
|
||||||
diagram.drawSVG(el, {theme: 'simple'})
|
diagram.drawSVG(el, {theme: 'simple'})
|
||||||
_.forEach(el.querySelectorAll('a'), (el) => {
|
_.forEach(el.querySelectorAll('a'), (el) => {
|
||||||
el.addEventListener('click', this.anchorClickHandler)
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
@@ -523,22 +533,44 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlelinkClick (e) {
|
handlelinkClick (e) {
|
||||||
const noteHash = e.target.href.split('/').pop()
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
const href = e.target.href
|
||||||
|
const linkHash = href.split('/').pop()
|
||||||
|
|
||||||
|
const regexNoteInternalLink = /main.html#(.+)/
|
||||||
|
if (regexNoteInternalLink.test(linkHash)) {
|
||||||
|
const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1])
|
||||||
|
const targetElement = this.refs.root.contentWindow.document.getElementById(targetId)
|
||||||
|
|
||||||
|
if (targetElement != null) {
|
||||||
|
this.getWindow().scrollTo(0, targetElement.offsetTop)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// this will match the new uuid v4 hash and the old hash
|
// this will match the new uuid v4 hash and the old hash
|
||||||
// e.g.
|
// e.g.
|
||||||
// :note:1c211eb7dcb463de6490 and
|
// :note:1c211eb7dcb463de6490 and
|
||||||
// :note:7dd23275-f2b4-49cb-9e93-3454daf1af9c
|
// :note:7dd23275-f2b4-49cb-9e93-3454daf1af9c
|
||||||
const regexIsNoteLink = /^:note:([a-zA-Z0-9-]{20,36})$/
|
const regexIsNoteLink = /^:note:([a-zA-Z0-9-]{20,36})$/
|
||||||
if (regexIsNoteLink.test(noteHash)) {
|
if (regexIsNoteLink.test(linkHash)) {
|
||||||
eventEmitter.emit('list:jump', noteHash.replace(':note:', ''))
|
eventEmitter.emit('list:jump', linkHash.replace(':note:', ''))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// this will match the old link format storage.key-note.key
|
// this will match the old link format storage.key-note.key
|
||||||
// e.g.
|
// e.g.
|
||||||
// 877f99c3268608328037-1c211eb7dcb463de6490
|
// 877f99c3268608328037-1c211eb7dcb463de6490
|
||||||
const regexIsLegacyNoteLink = /^(.{20})-(.{20})$/
|
const regexIsLegacyNoteLink = /^(.{20})-(.{20})$/
|
||||||
if (regexIsLegacyNoteLink.test(noteHash)) {
|
if (regexIsLegacyNoteLink.test(linkHash)) {
|
||||||
eventEmitter.emit('list:jump', noteHash.split('-')[1])
|
eventEmitter.emit('list:jump', linkHash.split('-')[1])
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// other case
|
||||||
|
shell.openExternal(href)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@@ -561,9 +593,12 @@ MarkdownPreview.propTypes = {
|
|||||||
onDoubleClick: PropTypes.func,
|
onDoubleClick: PropTypes.func,
|
||||||
onMouseUp: PropTypes.func,
|
onMouseUp: PropTypes.func,
|
||||||
onMouseDown: PropTypes.func,
|
onMouseDown: PropTypes.func,
|
||||||
|
onContextMenu: PropTypes.func,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
showCopyNotification: PropTypes.bool,
|
showCopyNotification: PropTypes.bool,
|
||||||
storagePath: PropTypes.string,
|
storagePath: PropTypes.string,
|
||||||
smartQuotes: PropTypes.bool
|
smartQuotes: PropTypes.bool,
|
||||||
|
smartArrows: PropTypes.bool,
|
||||||
|
breaks: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
this.focus = () => this.refs.code.focus()
|
this.focus = () => this.refs.code.focus()
|
||||||
this.reload = () => this.refs.code.reload()
|
this.reload = () => this.refs.code.reload()
|
||||||
this.userScroll = true
|
this.userScroll = true
|
||||||
|
this.state = {
|
||||||
|
isSliderFocused: false,
|
||||||
|
codeEditorWidthInPercent: 50
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOnChange () {
|
handleOnChange () {
|
||||||
@@ -87,20 +91,60 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// limit minSize to 10%, maxSize to 90%
|
||||||
|
if (newCodeEditorWidthInPercent <= 10) {
|
||||||
|
newCodeEditorWidthInPercent = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newCodeEditorWidthInPercent >= 90) {
|
||||||
|
newCodeEditorWidthInPercent = 90
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
codeEditorWidthInPercent: newCodeEditorWidthInPercent
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseUp (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.setState({
|
||||||
|
isSliderFocused: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseDown (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.setState({
|
||||||
|
isSliderFocused: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { config, value, storageKey } = this.props
|
const {config, value, storageKey, noteKey} = this.props
|
||||||
const storage = findStorage(storageKey)
|
const storage = findStorage(storageKey)
|
||||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
const previewStyle = {}
|
const previewStyle = {}
|
||||||
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
|
previewStyle.width = (100 - this.state.codeEditorWidthInPercent) + '%'
|
||||||
|
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused) previewStyle.pointerEvents = 'none'
|
||||||
return (
|
return (
|
||||||
<div styleName='root'>
|
<div styleName='root' ref='root'
|
||||||
|
onMouseMove={e => this.handleMouseMove(e)}
|
||||||
|
onMouseUp={e => this.handleMouseUp(e)}>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
styleName='codeEditor'
|
styleName='codeEditor'
|
||||||
ref='code'
|
ref='code'
|
||||||
|
width={this.state.codeEditorWidthInPercent + '%'}
|
||||||
mode='GitHub Flavored Markdown'
|
mode='GitHub Flavored Markdown'
|
||||||
value={value}
|
value={value}
|
||||||
theme={config.editor.theme}
|
theme={config.editor.theme}
|
||||||
@@ -115,9 +159,13 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||||
storageKey={storageKey}
|
storageKey={storageKey}
|
||||||
|
noteKey={noteKey}
|
||||||
onChange={this.handleOnChange.bind(this)}
|
onChange={this.handleOnChange.bind(this)}
|
||||||
onScroll={this.handleScroll.bind(this)}
|
onScroll={this.handleScroll.bind(this)}
|
||||||
/>
|
/>
|
||||||
|
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
||||||
|
<div styleName='slider-hitbox' />
|
||||||
|
</div>
|
||||||
<MarkdownPreview
|
<MarkdownPreview
|
||||||
style={previewStyle}
|
style={previewStyle}
|
||||||
styleName='preview'
|
styleName='preview'
|
||||||
@@ -130,6 +178,8 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
lineNumber={config.preview.lineNumber}
|
lineNumber={config.preview.lineNumber}
|
||||||
scrollPastEnd={config.preview.scrollPastEnd}
|
scrollPastEnd={config.preview.scrollPastEnd}
|
||||||
smartQuotes={config.preview.smartQuotes}
|
smartQuotes={config.preview.smartQuotes}
|
||||||
|
smartArrows={config.preview.smartArrows}
|
||||||
|
breaks={config.preview.breaks}
|
||||||
sanitize={config.preview.sanitize}
|
sanitize={config.preview.sanitize}
|
||||||
ref='preview'
|
ref='preview'
|
||||||
tabInde='0'
|
tabInde='0'
|
||||||
@@ -138,6 +188,9 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
onScroll={this.handleScroll.bind(this)}
|
onScroll={this.handleScroll.bind(this)}
|
||||||
showCopyNotification={config.ui.showCopyNotification}
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
storagePath={storage.path}
|
storagePath={storage.path}
|
||||||
|
noteKey={noteKey}
|
||||||
|
customCSS={config.preview.customCSS}
|
||||||
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,14 @@
|
|||||||
height 100%
|
height 100%
|
||||||
font-size 30px
|
font-size 30px
|
||||||
display flex
|
display flex
|
||||||
.codeEditor
|
.slider
|
||||||
width 50%
|
absolute top bottom
|
||||||
.preview
|
top -2px
|
||||||
width 50%
|
width 0
|
||||||
|
z-index 0
|
||||||
|
.slider-hitbox
|
||||||
|
absolute top bottom left right
|
||||||
|
width 7px
|
||||||
|
left -3px
|
||||||
|
z-index 10
|
||||||
|
cursor col-resize
|
||||||
|
|||||||
@@ -321,3 +321,76 @@ body[data-theme="solarized-dark"]
|
|||||||
.item-bottom-tagList-empty
|
.item-bottom-tagList-empty
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-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
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
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
|
||||||
|
|
||||||
|
.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-text-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 #c0392b
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
|
||||||
|
.item-title
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-title-icon
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-title-empty
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-bottom-tagList-empty
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
vertical-align middle
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ body[data-theme="dark"]
|
|||||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
.item-simple-bottom-time
|
.item-simple-bottom-time
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
@@ -117,6 +118,7 @@ body[data-theme="dark"]
|
|||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
.item-simple-bottom-time
|
.item-simple-bottom-time
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
@@ -132,6 +134,7 @@ body[data-theme="dark"]
|
|||||||
.item-simple-wrapper
|
.item-simple-wrapper
|
||||||
border-color transparent
|
border-color transparent
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
.item-simple-bottom-time
|
.item-simple-bottom-time
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
@@ -165,9 +168,10 @@ body[data-theme="solarized-dark"]
|
|||||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
&:hover
|
&:hover
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
// background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
.item-simple-bottom-time
|
.item-simple-bottom-time
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
@@ -178,9 +182,10 @@ body[data-theme="solarized-dark"]
|
|||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
&:active
|
&:active
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color $ui-solarized-dark-button--active-backgroundColor
|
// background-color $ui-solarized-dark-button--active-backgroundColor
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-dark-text-color
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
.item-simple-bottom-time
|
.item-simple-bottom-time
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
@@ -192,11 +197,13 @@ body[data-theme="solarized-dark"]
|
|||||||
|
|
||||||
.item-simple--active
|
.item-simple--active
|
||||||
border-color $ui-solarized-dark-borderColor
|
border-color $ui-solarized-dark-borderColor
|
||||||
background-color $ui-solarized-dark-button--active-backgroundColor
|
background-color $ui-solarized-dark-tag-backgroundColor
|
||||||
.item-simple-wrapper
|
.item-simple-wrapper
|
||||||
border-color transparent
|
border-color transparent
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
|
color $ui-dark-text-color
|
||||||
.item-simple-bottom-time
|
.item-simple-bottom-time
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
.item-simple-bottom-tagList-item
|
.item-simple-bottom-tagList-item
|
||||||
@@ -207,8 +214,75 @@ body[data-theme="solarized-dark"]
|
|||||||
color #c0392b
|
color #c0392b
|
||||||
.item-simple-bottom-tagList-item
|
.item-simple-bottom-tagList-item
|
||||||
background-color alpha(#fff, 20%)
|
background-color alpha(#fff, 20%)
|
||||||
.item-simple-right
|
.item-simple-title
|
||||||
float right
|
color $ui-dark-text-color
|
||||||
.item-simple-right-storageName
|
border-bottom $ui-dark-borderColor
|
||||||
padding-left 4px
|
.item-simple-right
|
||||||
opacity 0.4
|
float right
|
||||||
|
.item-simple-right-storageName
|
||||||
|
padding-left 4px
|
||||||
|
opacity 0.4
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-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-title
|
||||||
|
.item-simple-title-empty
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-solarized-dark-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
|
||||||
|
.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
|
||||||
|
|||||||
@@ -41,3 +41,14 @@ body[data-theme="solarized-dark"]
|
|||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
&:hover
|
&:hover
|
||||||
color #5CB85C
|
color #5CB85C
|
||||||
|
|
||||||
|
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
|
||||||
@@ -51,7 +51,7 @@ const SideNavFilter = ({
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
|
<button styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
|
||||||
onClick={handleTrashedButtonClick}
|
onClick={handleTrashedButtonClick} onContextMenu={handleFilterButtonContextMenu}
|
||||||
>
|
>
|
||||||
<div styleName='iconWrap'>
|
<div styleName='iconWrap'>
|
||||||
<img src={isTrashedActive
|
<img src={isTrashedActive
|
||||||
@@ -60,7 +60,7 @@ const SideNavFilter = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span onContextMenu={handleFilterButtonContextMenu} styleName='menu-button-label'>{i18n.__('Trash')}</span>
|
<span styleName='menu-button-label'>{i18n.__('Trash')}</span>
|
||||||
<span styleName='counters'>{counterDelNote}</span>
|
<span styleName='counters'>{counterDelNote}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
.iconWrap
|
.iconWrap
|
||||||
width 20px
|
width 20px
|
||||||
text-align center
|
text-align center
|
||||||
|
|
||||||
.counters
|
.counters
|
||||||
float right
|
float right
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
@@ -68,10 +68,9 @@
|
|||||||
.menu-button-label
|
.menu-button-label
|
||||||
position fixed
|
position fixed
|
||||||
display inline-block
|
display inline-block
|
||||||
height 32px
|
height 36px
|
||||||
left 44px
|
left 44px
|
||||||
padding 0 10px
|
padding 0 10px
|
||||||
margin-top -8px
|
|
||||||
margin-left 0
|
margin-left 0
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
z-index 10
|
z-index 10
|
||||||
@@ -222,4 +221,46 @@ body[data-theme="solarized-dark"]
|
|||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-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
|
||||||
|
|
||||||
|
.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
|
||||||
@@ -55,10 +55,10 @@ class SnippetTab extends React.Component {
|
|||||||
this.handleRename()
|
this.handleRename()
|
||||||
break
|
break
|
||||||
case 27:
|
case 27:
|
||||||
this.setState({
|
this.setState((prevState, props) => ({
|
||||||
name: this.props.snippet.name,
|
name: props.snippet.name,
|
||||||
isRenaming: false
|
isRenaming: false
|
||||||
})
|
}))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,8 +58,8 @@
|
|||||||
opacity 0
|
opacity 0
|
||||||
border-top-right-radius 2px
|
border-top-right-radius 2px
|
||||||
border-bottom-right-radius 2px
|
border-bottom-right-radius 2px
|
||||||
height 26px
|
height 34px
|
||||||
line-height 26px
|
line-height 32px
|
||||||
|
|
||||||
.folderList-item:hover, .folderList-item--active:hover
|
.folderList-item:hover, .folderList-item--active:hover
|
||||||
.folderList-item-tooltip
|
.folderList-item-tooltip
|
||||||
@@ -138,3 +138,22 @@ body[data-theme="solarized-dark"]
|
|||||||
&:hover
|
&:hover
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color $ui-solarized-dark-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
|
||||||
|
|
||||||
|
.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
|
||||||
@@ -9,16 +9,26 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {Function} handleClickTagListItem
|
* @param {Function} handleClickTagListItem
|
||||||
|
* @param {Function} handleClickNarrowToTag
|
||||||
* @param {bool} isActive
|
* @param {bool} isActive
|
||||||
|
* @param {bool} isRelated
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const TagListItem = ({name, handleClickTagListItem, isActive, count}) => (
|
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isActive, isRelated, count}) => (
|
||||||
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
|
<div styleName='tagList-itemContainer'>
|
||||||
<span styleName='tagList-item-name'>
|
{isRelated
|
||||||
{`# ${name}`}
|
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
|
||||||
<span styleName='tagList-item-count'> {count}</span>
|
<i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
|
||||||
</span>
|
</button>
|
||||||
</button>
|
: <div styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} />
|
||||||
|
}
|
||||||
|
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
|
||||||
|
<span styleName='tagList-item-name'>
|
||||||
|
{`# ${name}`}
|
||||||
|
<span styleName='tagList-item-count'>{count}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
TagListItem.propTypes = {
|
TagListItem.propTypes = {
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
.tagList-itemContainer
|
||||||
|
display flex
|
||||||
|
|
||||||
.tagList-item
|
.tagList-item
|
||||||
display flex
|
display flex
|
||||||
|
flex 1
|
||||||
width 100%
|
width 100%
|
||||||
height 26px
|
height 26px
|
||||||
background-color transparent
|
background-color transparent
|
||||||
@@ -20,9 +24,16 @@
|
|||||||
color $ui-button-default-color
|
color $ui-button-default-color
|
||||||
background-color $ui-button-default--active-backgroundColor
|
background-color $ui-button-default--active-backgroundColor
|
||||||
|
|
||||||
|
.tagList-itemNarrow
|
||||||
|
composes tagList-item
|
||||||
|
flex none
|
||||||
|
width 20px
|
||||||
|
padding 0 4px
|
||||||
|
|
||||||
.tagList-item-active
|
.tagList-item-active
|
||||||
background-color $ui-button-default--active-backgroundColor
|
background-color $ui-button-default--active-backgroundColor
|
||||||
display flex
|
display flex
|
||||||
|
flex 1
|
||||||
width 100%
|
width 100%
|
||||||
height 26px
|
height 26px
|
||||||
padding 0
|
padding 0
|
||||||
@@ -36,10 +47,16 @@
|
|||||||
background-color alpha($ui-button-default--active-backgroundColor, 60%)
|
background-color alpha($ui-button-default--active-backgroundColor, 60%)
|
||||||
transition 0.2s
|
transition 0.2s
|
||||||
|
|
||||||
|
.tagList-itemNarrow-active
|
||||||
|
composes tagList-item-active
|
||||||
|
flex none
|
||||||
|
width 20px
|
||||||
|
padding 0 4px
|
||||||
|
|
||||||
.tagList-item-name
|
.tagList-item-name
|
||||||
display block
|
display block
|
||||||
flex 1
|
flex 1
|
||||||
padding 0 15px
|
padding 0 8px 0 4px
|
||||||
height 26px
|
height 26px
|
||||||
line-height 26px
|
line-height 26px
|
||||||
border-width 0 0 0 2px
|
border-width 0 0 0 2px
|
||||||
@@ -49,7 +66,10 @@
|
|||||||
text-overflow ellipsis
|
text-overflow ellipsis
|
||||||
|
|
||||||
.tagList-item-count
|
.tagList-item-count
|
||||||
padding 0 3px
|
float right
|
||||||
|
line-height 26px
|
||||||
|
padding-right 15px
|
||||||
|
font-size 13px
|
||||||
|
|
||||||
body[data-theme="white"]
|
body[data-theme="white"]
|
||||||
.tagList-item
|
.tagList-item
|
||||||
|
|||||||
@@ -47,5 +47,15 @@ body[data-theme="solarized-dark"]
|
|||||||
.progressBar
|
.progressBar
|
||||||
background-color: #2aa198
|
background-color: #2aa198
|
||||||
|
|
||||||
|
.percentageText
|
||||||
|
color #fdf6e3
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.percentageBar
|
||||||
|
background-color #f92672
|
||||||
|
|
||||||
|
.progressBar
|
||||||
|
background-color: #373831
|
||||||
|
|
||||||
.percentageText
|
.percentageText
|
||||||
color #fdf6e3
|
color #fdf6e3
|
||||||
@@ -199,7 +199,6 @@ ol
|
|||||||
&>li>ul, &>li>ol
|
&>li>ul, &>li>ol
|
||||||
margin 0
|
margin 0
|
||||||
code
|
code
|
||||||
color #CC305F
|
|
||||||
padding 0.2em 0.4em
|
padding 0.2em 0.4em
|
||||||
background-color #f7f7f7
|
background-color #f7f7f7
|
||||||
border-radius 3px
|
border-radius 3px
|
||||||
@@ -294,6 +293,84 @@ kbd
|
|||||||
line-height 1
|
line-height 1
|
||||||
padding 3px 5px
|
padding 3px 5px
|
||||||
|
|
||||||
|
$admonition
|
||||||
|
box-shadow 0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2)
|
||||||
|
position relative
|
||||||
|
margin 1.5625em 0
|
||||||
|
padding 0 1.2rem
|
||||||
|
border-left .4rem solid #448aff
|
||||||
|
border-radius .2rem
|
||||||
|
overflow auto
|
||||||
|
|
||||||
|
html .admonition>:last-child
|
||||||
|
margin-bottom 1.2rem
|
||||||
|
|
||||||
|
.admonition .admonition
|
||||||
|
margin 1em 0
|
||||||
|
|
||||||
|
.admonition p
|
||||||
|
margin-top: 0.5em
|
||||||
|
|
||||||
|
$admonition-icon
|
||||||
|
position absolute
|
||||||
|
left 1.2rem
|
||||||
|
font-family: "Material Icons"
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 24px
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
direction: ltr;
|
||||||
|
|
||||||
|
/* Support for all WebKit browsers. */
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
/* Support for Safari and Chrome. */
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
|
||||||
|
/* Support for Firefox. */
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
|
||||||
|
/* Support for IE. */
|
||||||
|
font-feature-settings: 'liga';
|
||||||
|
|
||||||
|
$admonition-title
|
||||||
|
margin 0 -1.2rem
|
||||||
|
padding .8rem 1.2rem .8rem 4rem
|
||||||
|
border-bottom .1rem solid rgba(68,138,255,.1)
|
||||||
|
background-color rgba(68,138,255,.1)
|
||||||
|
font-weight 700
|
||||||
|
|
||||||
|
.admonition>.admonition-title:last-child
|
||||||
|
margin-bottom 0
|
||||||
|
|
||||||
|
admonition_types = {
|
||||||
|
note: {color: #0288D1, icon: "note"},
|
||||||
|
hint: {color: #009688, icon: "info_outline"},
|
||||||
|
danger: {color: #c2185b, icon: "block"},
|
||||||
|
caution: {color: #ffa726, icon: "warning"},
|
||||||
|
error: {color: #d32f2f, icon: "error_outline"},
|
||||||
|
attention: {color: #455a64, icon: "priority_high"}
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, val in admonition_types
|
||||||
|
.admonition.{name}
|
||||||
|
@extend $admonition
|
||||||
|
border-left-color: val[color]
|
||||||
|
|
||||||
|
.admonition.{name}>.admonition-title
|
||||||
|
@extend $admonition-title
|
||||||
|
border-bottom-color: .1rem solid rgba(val[color], 0.2)
|
||||||
|
background-color: rgba(val[color], 0.2)
|
||||||
|
|
||||||
|
.admonition.{name}>.admonition-title:before
|
||||||
|
@extend $admonition-icon
|
||||||
|
color: val[color]
|
||||||
|
content: val[icon]
|
||||||
|
|
||||||
themeDarkBackground = darken(#21252B, 10%)
|
themeDarkBackground = darken(#21252B, 10%)
|
||||||
themeDarkText = #f9f9f9
|
themeDarkText = #f9f9f9
|
||||||
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
||||||
@@ -371,3 +448,32 @@ body[data-theme="solarized-dark"]
|
|||||||
border-color themeSolarizedDarkTableBorder
|
border-color themeSolarizedDarkTableBorder
|
||||||
&:last-child
|
&:last-child
|
||||||
border-right solid 1px themeSolarizedDarkTableBorder
|
border-right solid 1px themeSolarizedDarkTableBorder
|
||||||
|
|
||||||
|
themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
|
||||||
|
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
|
||||||
|
themeMonokaiTableHead = themeMonokaiTableEven
|
||||||
|
themeMonokaiTableBorder = themeDarkBorder
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
border-color themeDarkBorder
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
table
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
background-color themeMonokaiTableHead
|
||||||
|
th
|
||||||
|
border-color themeMonokaiTableBorder
|
||||||
|
&:last-child
|
||||||
|
border-right solid 1px themeMonokaiTableBorder
|
||||||
|
tbody
|
||||||
|
tr:nth-child(2n + 1)
|
||||||
|
background-color themeMonokaiTableOdd
|
||||||
|
tr:nth-child(2n)
|
||||||
|
background-color themeMonokaiTableEven
|
||||||
|
td
|
||||||
|
border-color themeMonokaiTableBorder
|
||||||
|
&:last-child
|
||||||
|
border-right solid 1px themeMonokaiTableBorder
|
||||||
|
kbd
|
||||||
|
background-color themeDarkBackground
|
||||||
78
browser/lib/Languages.js
Normal file
78
browser/lib/Languages.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
const languages = [
|
||||||
|
{
|
||||||
|
name: 'Albanian',
|
||||||
|
locale: 'sq'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Chinese (zh-CN)',
|
||||||
|
locale: 'zh-CN'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Chinese (zh-TW)',
|
||||||
|
locale: 'zh-TW'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Danish',
|
||||||
|
locale: 'da'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'English',
|
||||||
|
locale: 'en'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'French',
|
||||||
|
locale: 'fr'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'German',
|
||||||
|
locale: 'de'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Hungarian',
|
||||||
|
locale: 'hu'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Japanese',
|
||||||
|
locale: 'ja'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Korean',
|
||||||
|
locale: 'ko'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Norwegian',
|
||||||
|
locale: 'no'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Polish',
|
||||||
|
locale: 'pl'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Portuguese',
|
||||||
|
locale: 'pt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Russian',
|
||||||
|
locale: 'ru'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Spanish',
|
||||||
|
locale: 'es-ES'
|
||||||
|
}, {
|
||||||
|
name: 'Turkish',
|
||||||
|
locale: 'tr'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getLocales () {
|
||||||
|
return languages.reduce(function (localeList, locale) {
|
||||||
|
localeList.push(locale.locale)
|
||||||
|
return localeList
|
||||||
|
}, [])
|
||||||
|
},
|
||||||
|
getLanguages () {
|
||||||
|
return languages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
23
browser/lib/confirmDeleteNote.js
Normal file
23
browser/lib/confirmDeleteNote.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import electron from 'electron'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
const { remote } = electron
|
||||||
|
const { dialog } = remote
|
||||||
|
|
||||||
|
export function confirmDeleteNote (confirmDeletion, permanent) {
|
||||||
|
if (confirmDeletion || permanent) {
|
||||||
|
const alertConfig = {
|
||||||
|
ype: '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
|
||||||
|
)
|
||||||
|
|
||||||
|
return dialogButtonIndex === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -12,6 +12,10 @@ const themes = fs.readdirSync(themePath)
|
|||||||
})
|
})
|
||||||
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
|
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
|
||||||
|
|
||||||
|
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 = {
|
const consts = {
|
||||||
FOLDER_COLORS: [
|
FOLDER_COLORS: [
|
||||||
'#E10051',
|
'#E10051',
|
||||||
@@ -31,7 +35,8 @@ const consts = {
|
|||||||
'Dodger Blue',
|
'Dodger Blue',
|
||||||
'Violet Eggplant'
|
'Violet Eggplant'
|
||||||
],
|
],
|
||||||
THEMES: ['default'].concat(themes)
|
THEMES: ['default'].concat(themes),
|
||||||
|
SNIPPET_FILE: snippetFile
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = consts
|
module.exports = consts
|
||||||
|
|||||||
14
browser/lib/convertModeName.js
Normal file
14
browser/lib/convertModeName.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export default function convertModeName (name) {
|
||||||
|
switch (name) {
|
||||||
|
case 'ejs':
|
||||||
|
return 'Embedded Javascript'
|
||||||
|
case 'html_ruby':
|
||||||
|
return 'Embedded Ruby'
|
||||||
|
case 'objectivec':
|
||||||
|
return 'Objective C'
|
||||||
|
case 'text':
|
||||||
|
return 'Plain Text'
|
||||||
|
default:
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { app } = remote
|
const { app } = remote
|
||||||
|
const { getLocales } = require('./Languages.js')
|
||||||
|
|
||||||
// load package for localization
|
// load package for localization
|
||||||
const i18n = new (require('i18n-2'))({
|
const i18n = new (require('i18n-2'))({
|
||||||
// setup some locales - other locales default to the first locale
|
// setup some locales - other locales default to the first locale
|
||||||
locales: ['en', 'sq', 'zh-CN', 'zh-TW', 'da', 'fr', 'de', 'hu', 'ja', 'ko', 'no', 'pl', 'pt', 'es-ES'],
|
locales: getLocales(),
|
||||||
extension: '.json',
|
extension: '.json',
|
||||||
directory: process.env.NODE_ENV === 'production'
|
directory: process.env.NODE_ENV === 'production'
|
||||||
? path.join(app.getAppPath(), './locales')
|
? path.join(app.getAppPath(), './locales')
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import markdownit from 'markdown-it'
|
|||||||
import sanitize from './markdown-it-sanitize-html'
|
import sanitize from './markdown-it-sanitize-html'
|
||||||
import emoji from 'markdown-it-emoji'
|
import emoji from 'markdown-it-emoji'
|
||||||
import math from '@rokt33r/markdown-it-math'
|
import math from '@rokt33r/markdown-it-math'
|
||||||
|
import smartArrows from 'markdown-it-smartarrows'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import katex from 'katex'
|
import katex from 'katex'
|
||||||
import {lastFindInArray} from './utils'
|
import { lastFindInArray } from './utils'
|
||||||
|
|
||||||
function createGutter (str, firstLineNumber) {
|
function createGutter (str, firstLineNumber) {
|
||||||
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
||||||
@@ -25,7 +26,7 @@ class Markdown {
|
|||||||
linkify: true,
|
linkify: true,
|
||||||
html: true,
|
html: true,
|
||||||
xhtmlOut: true,
|
xhtmlOut: true,
|
||||||
breaks: true,
|
breaks: config.preview.breaks,
|
||||||
highlight: function (str, lang) {
|
highlight: function (str, lang) {
|
||||||
const delimiter = ':'
|
const delimiter = ':'
|
||||||
const langInfo = lang.split(delimiter)
|
const langInfo = lang.split(delimiter)
|
||||||
@@ -141,15 +142,18 @@ class Markdown {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.md.use(require('markdown-it-kbd'))
|
this.md.use(require('markdown-it-kbd'))
|
||||||
|
this.md.use(require('markdown-it-admonition'))
|
||||||
|
|
||||||
const deflate = require('markdown-it-plantuml/lib/deflate')
|
const deflate = require('markdown-it-plantuml/lib/deflate')
|
||||||
this.md.use(require('markdown-it-plantuml'), '', {
|
this.md.use(require('markdown-it-plantuml'), '', {
|
||||||
generateSource: function (umlCode) {
|
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 s = unescape(encodeURIComponent(umlCode))
|
||||||
const zippedCode = deflate.encode64(
|
const zippedCode = deflate.encode64(
|
||||||
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
|
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
|
||||||
)
|
)
|
||||||
return `http://www.plantuml.com/plantuml/svg/${zippedCode}`
|
return `${serverAddress}/${zippedCode}`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -211,6 +215,10 @@ class Markdown {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (config.preview.smartArrows) {
|
||||||
|
this.md.use(smartArrows)
|
||||||
|
}
|
||||||
|
|
||||||
// Add line number attribute for scrolling
|
// Add line number attribute for scrolling
|
||||||
const originalRender = this.md.renderer.render
|
const originalRender = this.md.renderer.render
|
||||||
this.md.renderer.render = (tokens, options, env) => {
|
this.md.renderer.render = (tokens, options, env) => {
|
||||||
@@ -234,10 +242,6 @@ class Markdown {
|
|||||||
if (!_.isString(content)) content = ''
|
if (!_.isString(content)) content = ''
|
||||||
return this.md.render(content)
|
return this.md.render(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizeLinkText (linkText) {
|
|
||||||
return this.md.normalizeLinkText(linkText)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Markdown
|
export default Markdown
|
||||||
|
|||||||
0
browser/lib/markdown2.js
Normal file
0
browser/lib/markdown2.js
Normal file
@@ -54,7 +54,25 @@ export function escapeHtmlCharacters (text) {
|
|||||||
: html
|
: html
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isObjectEqual (a, b) {
|
||||||
|
const aProps = Object.getOwnPropertyNames(a)
|
||||||
|
const bProps = Object.getOwnPropertyNames(b)
|
||||||
|
|
||||||
|
if (aProps.length !== bProps.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < aProps.length; i++) {
|
||||||
|
const propName = aProps[i]
|
||||||
|
if (a[propName] !== b[propName]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
lastFindInArray,
|
lastFindInArray,
|
||||||
escapeHtmlCharacters
|
escapeHtmlCharacters,
|
||||||
|
isObjectEqual
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,3 +30,10 @@ body[data-theme="solarized-dark"]
|
|||||||
border-left 1px solid $ui-solarized-dark-borderColor
|
border-left 1px solid $ui-solarized-dark-borderColor
|
||||||
.empty-message
|
.empty-message
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-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
|
||||||
|
|||||||
@@ -133,3 +133,29 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-button--active-color
|
color $ui-dark-button--active-color
|
||||||
.search-optionList-item-name-surfix
|
.search-optionList-item-name-surfix
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
color $ui-dark-text-color
|
||||||
|
&:hover
|
||||||
|
color white
|
||||||
|
background-color $ui-monokai-button--hover-backgroundColor
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
|
||||||
|
.search-optionList
|
||||||
|
color white
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-button-backgroundColor
|
||||||
|
|
||||||
|
.search-optionList-item
|
||||||
|
&:hover
|
||||||
|
background-color lighten($ui-monokai-button--hover-backgroundColor, 15%)
|
||||||
|
|
||||||
|
.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
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
.control-infoButton-panel
|
.control-infoButton-panel
|
||||||
z-index 200
|
z-index 200
|
||||||
margin-top 0px
|
margin-top 0px
|
||||||
|
top: 50px
|
||||||
right 25px
|
right 25px
|
||||||
position absolute
|
position absolute
|
||||||
padding 20px 25px 0 25px
|
padding 20px 25px 0 25px
|
||||||
@@ -215,3 +216,43 @@ body[data-theme="solarized-dark"]
|
|||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-solarized-ark-text-color
|
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
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import InfoPanelTrashed from './InfoPanelTrashed'
|
|||||||
import { formatDate } from 'browser/lib/date-formatter'
|
import { formatDate } from 'browser/lib/date-formatter'
|
||||||
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
||||||
import striptags from 'striptags'
|
import striptags from 'striptags'
|
||||||
|
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||||
|
|
||||||
class MarkdownNoteDetail extends React.Component {
|
class MarkdownNoteDetail extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -54,10 +55,14 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
|
ee.on('topbar:togglemodebutton', () => {
|
||||||
|
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
||||||
|
this.handleSwitchMode(reversedType)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
|
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
|
||||||
if (this.saveQueue != null) this.saveNow()
|
if (this.saveQueue != null) this.saveNow()
|
||||||
this.setState({
|
this.setState({
|
||||||
note: Object.assign({}, nextProps.note)
|
note: Object.assign({}, nextProps.note)
|
||||||
@@ -181,10 +186,10 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
handleTrashButtonClick (e) {
|
handleTrashButtonClick (e) {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
const { isTrashed } = note
|
const { isTrashed } = note
|
||||||
const { confirmDeletion } = this.props
|
const { confirmDeletion } = this.props.config.ui
|
||||||
|
|
||||||
if (isTrashed) {
|
if (isTrashed) {
|
||||||
if (confirmDeletion(true)) {
|
if (confirmDeleteNote(confirmDeletion, true)) {
|
||||||
const {note, dispatch} = this.props
|
const {note, dispatch} = this.props
|
||||||
dataApi
|
dataApi
|
||||||
.deleteNote(note.storage, note.key)
|
.deleteNote(note.storage, note.key)
|
||||||
@@ -201,7 +206,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
.then(() => ee.emit('list:next'))
|
.then(() => ee.emit('list:next'))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (confirmDeletion()) {
|
if (confirmDeleteNote(confirmDeletion, false)) {
|
||||||
note.isTrashed = true
|
note.isTrashed = true
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -272,6 +277,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
handleSwitchMode (type) {
|
handleSwitchMode (type) {
|
||||||
this.setState({ editorType: type }, () => {
|
this.setState({ editorType: type }, () => {
|
||||||
|
this.focus()
|
||||||
const newConfig = Object.assign({}, this.props.config)
|
const newConfig = Object.assign({}, this.props.config)
|
||||||
newConfig.editor.type = type
|
newConfig.editor.type = type
|
||||||
ConfigManager.set(newConfig)
|
ConfigManager.set(newConfig)
|
||||||
@@ -288,6 +294,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
config={config}
|
config={config}
|
||||||
value={note.content}
|
value={note.content}
|
||||||
storageKey={note.storage}
|
storageKey={note.storage}
|
||||||
|
noteKey={note.key}
|
||||||
onChange={this.handleUpdateContent.bind(this)}
|
onChange={this.handleUpdateContent.bind(this)}
|
||||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
/>
|
/>
|
||||||
@@ -297,6 +304,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
config={config}
|
config={config}
|
||||||
value={note.content}
|
value={note.content}
|
||||||
storageKey={note.storage}
|
storageKey={note.storage}
|
||||||
|
noteKey={note.key}
|
||||||
onChange={this.handleUpdateContent.bind(this)}
|
onChange={this.handleUpdateContent.bind(this)}
|
||||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
/>
|
/>
|
||||||
@@ -437,8 +445,7 @@ MarkdownNoteDetail.propTypes = {
|
|||||||
style: PropTypes.shape({
|
style: PropTypes.shape({
|
||||||
left: PropTypes.number
|
left: PropTypes.number
|
||||||
}),
|
}),
|
||||||
ignorePreviewPointerEvents: PropTypes.bool,
|
ignorePreviewPointerEvents: PropTypes.bool
|
||||||
confirmDeletion: PropTypes.bool.isRequired
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(MarkdownNoteDetail, styles)
|
export default CSSModules(MarkdownNoteDetail, styles)
|
||||||
|
|||||||
@@ -71,3 +71,8 @@ body[data-theme="solarized-dark"]
|
|||||||
.root
|
.root
|
||||||
border-left 1px solid $ui-solarized-dark-borderColor
|
border-left 1px solid $ui-solarized-dark-borderColor
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
border-left 1px solid $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
|||||||
@@ -98,3 +98,7 @@ body[data-theme="solarized-dark"]
|
|||||||
border-color $ui-solarized-dark-borderColor
|
border-color $ui-solarized-dark-borderColor
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.info
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
@@ -18,6 +18,7 @@ import context from 'browser/lib/context'
|
|||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import {findNoteTitle} from 'browser/lib/findNoteTitle'
|
import {findNoteTitle} from 'browser/lib/findNoteTitle'
|
||||||
|
import convertModeName from 'browser/lib/convertModeName'
|
||||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
import TrashButton from './TrashButton'
|
import TrashButton from './TrashButton'
|
||||||
import RestoreButton from './RestoreButton'
|
import RestoreButton from './RestoreButton'
|
||||||
@@ -27,21 +28,7 @@ import InfoPanel from './InfoPanel'
|
|||||||
import InfoPanelTrashed from './InfoPanelTrashed'
|
import InfoPanelTrashed from './InfoPanelTrashed'
|
||||||
import { formatDate } from 'browser/lib/date-formatter'
|
import { formatDate } from 'browser/lib/date-formatter'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||||
function pass (name) {
|
|
||||||
switch (name) {
|
|
||||||
case 'ejs':
|
|
||||||
return 'Embedded Javascript'
|
|
||||||
case 'html_ruby':
|
|
||||||
return 'Embedded Ruby'
|
|
||||||
case 'objectivec':
|
|
||||||
return 'Objective C'
|
|
||||||
case 'text':
|
|
||||||
return 'Plain Text'
|
|
||||||
default:
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
@@ -81,7 +68,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
|
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
|
||||||
if (this.saveQueue != null) this.saveNow()
|
if (this.saveQueue != null) this.saveNow()
|
||||||
const nextNote = Object.assign({
|
const nextNote = Object.assign({
|
||||||
description: ''
|
description: ''
|
||||||
@@ -197,10 +184,10 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
handleTrashButtonClick (e) {
|
handleTrashButtonClick (e) {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
const { isTrashed } = note
|
const { isTrashed } = note
|
||||||
const { confirmDeletion } = this.props
|
const { confirmDeletion } = this.props.config.ui
|
||||||
|
|
||||||
if (isTrashed) {
|
if (isTrashed) {
|
||||||
if (confirmDeletion(true)) {
|
if (confirmDeleteNote(confirmDeletion, true)) {
|
||||||
const {note, dispatch} = this.props
|
const {note, dispatch} = this.props
|
||||||
dataApi
|
dataApi
|
||||||
.deleteNote(note.storage, note.key)
|
.deleteNote(note.storage, note.key)
|
||||||
@@ -217,7 +204,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
.then(() => ee.emit('list:next'))
|
.then(() => ee.emit('list:next'))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (confirmDeletion()) {
|
if (confirmDeleteNote(confirmDeletion, false)) {
|
||||||
note.isTrashed = true
|
note.isTrashed = true
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -381,11 +368,11 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
name: mode
|
name: mode
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
|
||||||
|
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
note: this.state.note
|
note: state.note
|
||||||
}, () => {
|
}), () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -394,11 +381,11 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
return (e) => {
|
return (e) => {
|
||||||
const snippets = this.state.note.snippets.slice()
|
const snippets = this.state.note.snippets.slice()
|
||||||
snippets[index].mode = name
|
snippets[index].mode = name
|
||||||
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
|
||||||
|
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
note: this.state.note
|
note: state.note
|
||||||
}, () => {
|
}), () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -412,10 +399,10 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
return (e) => {
|
return (e) => {
|
||||||
const snippets = this.state.note.snippets.slice()
|
const snippets = this.state.note.snippets.slice()
|
||||||
snippets[index].content = this.refs['code-' + index].value
|
snippets[index].content = this.refs['code-' + index].value
|
||||||
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
note: this.state.note
|
note: state.note
|
||||||
}, () => {
|
}), () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -610,17 +597,17 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
jumpNextTab () {
|
jumpNextTab () {
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
snippetIndex: (this.state.snippetIndex + 1) % this.state.note.snippets.length
|
snippetIndex: (state.snippetIndex + 1) % state.note.snippets.length
|
||||||
}, () => {
|
}), () => {
|
||||||
this.focusEditor()
|
this.focusEditor()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
jumpPrevTab () {
|
jumpPrevTab () {
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
snippetIndex: (this.state.snippetIndex - 1 + this.state.note.snippets.length) % this.state.note.snippets.length
|
snippetIndex: (state.snippetIndex - 1 + state.note.snippets.length) % state.note.snippets.length
|
||||||
}, () => {
|
}), () => {
|
||||||
this.focusEditor()
|
this.focusEditor()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -676,7 +663,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
const viewList = note.snippets.map((snippet, index) => {
|
const viewList = note.snippets.map((snippet, index) => {
|
||||||
const isActive = this.state.snippetIndex === index
|
const isActive = this.state.snippetIndex === index
|
||||||
|
|
||||||
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
|
let syntax = CodeMirror.findModeByName(convertModeName(snippet.mode))
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
|
|
||||||
return <div styleName='tabView'
|
return <div styleName='tabView'
|
||||||
@@ -883,8 +870,7 @@ SnippetNoteDetail.propTypes = {
|
|||||||
style: PropTypes.shape({
|
style: PropTypes.shape({
|
||||||
left: PropTypes.number
|
left: PropTypes.number
|
||||||
}),
|
}),
|
||||||
ignorePreviewPointerEvents: PropTypes.bool,
|
ignorePreviewPointerEvents: PropTypes.bool
|
||||||
confirmDeletion: PropTypes.bool.isRequired
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(SnippetNoteDetail, styles)
|
export default CSSModules(SnippetNoteDetail, styles)
|
||||||
|
|||||||
@@ -152,4 +152,21 @@ body[data-theme="solarized-dark"]
|
|||||||
|
|
||||||
.tabList
|
.tabList
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
border-left 1px solid $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.body
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.body .description textarea
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
border 1px solid $ui-monokai-borderColor
|
||||||
|
|
||||||
|
.tabList
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
@@ -44,16 +44,9 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeLastTag () {
|
removeLastTag () {
|
||||||
let { value } = this.props
|
this.removeTagByCallback((value) => {
|
||||||
|
value.pop()
|
||||||
value = _.isArray(value)
|
})
|
||||||
? value.slice()
|
|
||||||
: []
|
|
||||||
value.pop()
|
|
||||||
value = _.uniq(value)
|
|
||||||
|
|
||||||
this.value = value
|
|
||||||
this.props.onChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset () {
|
reset () {
|
||||||
@@ -96,15 +89,22 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleTagRemoveButtonClick (tag) {
|
handleTagRemoveButtonClick (tag) {
|
||||||
return (e) => {
|
this.removeTagByCallback((value, tag) => {
|
||||||
let { value } = this.props
|
|
||||||
|
|
||||||
value.splice(value.indexOf(tag), 1)
|
value.splice(value.indexOf(tag), 1)
|
||||||
value = _.uniq(value)
|
}, tag)
|
||||||
|
}
|
||||||
|
|
||||||
this.value = value
|
removeTagByCallback (callback, tag = null) {
|
||||||
this.props.onChange()
|
let { value } = this.props
|
||||||
}
|
|
||||||
|
value = _.isArray(value)
|
||||||
|
? value.slice()
|
||||||
|
: []
|
||||||
|
callback(value, tag)
|
||||||
|
value = _.uniq(value)
|
||||||
|
|
||||||
|
this.value = value
|
||||||
|
this.props.onChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@@ -118,7 +118,7 @@ class TagSelect extends React.Component {
|
|||||||
>
|
>
|
||||||
<span styleName='tag-label'>#{tag}</span>
|
<span styleName='tag-label'>#{tag}</span>
|
||||||
<button styleName='tag-removeButton'
|
<button styleName='tag-removeButton'
|
||||||
onClick={(e) => this.handleTagRemoveButtonClick(tag)(e)}
|
onClick={(e) => this.handleTagRemoveButtonClick(tag)}
|
||||||
>
|
>
|
||||||
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
|
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -81,4 +81,20 @@ body[data-theme="solarized-dark"]
|
|||||||
.newTag
|
.newTag
|
||||||
border-color none
|
border-color none
|
||||||
background-color transparent
|
background-color transparent
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.tag
|
||||||
|
background-color $ui-monokai-button-backgroundColor
|
||||||
|
|
||||||
|
.tag-removeButton
|
||||||
|
border-color $ui-button--focus-borderColor
|
||||||
|
background-color transparent
|
||||||
|
|
||||||
|
.tag-label
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.newTag
|
||||||
|
border-color none
|
||||||
|
background-color transparent
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|||||||
@@ -56,3 +56,10 @@ body[data-theme="solarized-dark"]
|
|||||||
.active
|
.active
|
||||||
background-color #1EC38B
|
background-color #1EC38B
|
||||||
box-shadow 2px 0px 7px #222222
|
box-shadow 2px 0px 7px #222222
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.control-toggleModeButton
|
||||||
|
background-color #272822
|
||||||
|
.active
|
||||||
|
background-color #1EC38B
|
||||||
|
box-shadow 2px 0px 7px #222222
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import SnippetNoteDetail from './SnippetNoteDetail'
|
|||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import StatusBar from '../StatusBar'
|
import StatusBar from '../StatusBar'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import debounceRender from 'react-debounce-render'
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
|
||||||
@@ -33,26 +34,6 @@ class Detail extends React.Component {
|
|||||||
ee.off('detail:delete', this.deleteHandler)
|
ee.off('detail:delete', this.deleteHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmDeletion (permanent) {
|
|
||||||
if (this.props.config.ui.confirmDeletion || permanent) {
|
|
||||||
const electron = require('electron')
|
|
||||||
const { remote } = electron
|
|
||||||
const { dialog } = remote
|
|
||||||
|
|
||||||
const alertConfig = {
|
|
||||||
type: 'warning',
|
|
||||||
message: i18n.__('Confirm note deletion'),
|
|
||||||
detail: i18n.__('This will permanently remove this note.'),
|
|
||||||
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
|
||||||
}
|
|
||||||
|
|
||||||
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), alertConfig)
|
|
||||||
return dialogueButtonIndex === 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { location, data, config } = this.props
|
const { location, data, config } = this.props
|
||||||
let note = null
|
let note = null
|
||||||
@@ -82,7 +63,6 @@ class Detail extends React.Component {
|
|||||||
<SnippetNoteDetail
|
<SnippetNoteDetail
|
||||||
note={note}
|
note={note}
|
||||||
config={config}
|
config={config}
|
||||||
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
|
|
||||||
ref='root'
|
ref='root'
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
@@ -99,7 +79,6 @@ class Detail extends React.Component {
|
|||||||
<MarkdownNoteDetail
|
<MarkdownNoteDetail
|
||||||
note={note}
|
note={note}
|
||||||
config={config}
|
config={config}
|
||||||
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
|
|
||||||
ref='root'
|
ref='root'
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
@@ -121,4 +100,4 @@ Detail.propTypes = {
|
|||||||
ignorePreviewPointerEvents: PropTypes.bool
|
ignorePreviewPointerEvents: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(Detail, styles)
|
export default debounceRender(CSSModules(Detail, styles))
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import eventEmitter from 'browser/main/lib/eventEmitter'
|
|||||||
import { hashHistory } from 'react-router'
|
import { hashHistory } from 'react-router'
|
||||||
import store from 'browser/main/store'
|
import store from 'browser/main/store'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import { getLocales } from 'browser/lib/Languages'
|
||||||
|
import applyShortcuts from 'browser/main/lib/shortcutManager'
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
@@ -140,47 +142,25 @@ class Main extends React.Component {
|
|||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { dispatch, config } = this.props
|
const { dispatch, config } = this.props
|
||||||
|
|
||||||
if (config.ui.theme === 'dark') {
|
const supportedThemes = [
|
||||||
document.body.setAttribute('data-theme', 'dark')
|
'dark',
|
||||||
} else if (config.ui.theme === 'white') {
|
'white',
|
||||||
document.body.setAttribute('data-theme', 'white')
|
'solarized-dark',
|
||||||
} else if (config.ui.theme === 'solarized-dark') {
|
'monokai'
|
||||||
document.body.setAttribute('data-theme', 'solarized-dark')
|
]
|
||||||
|
|
||||||
|
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
|
||||||
|
document.body.setAttribute('data-theme', config.ui.theme)
|
||||||
} else {
|
} else {
|
||||||
document.body.setAttribute('data-theme', 'default')
|
document.body.setAttribute('data-theme', 'default')
|
||||||
}
|
}
|
||||||
if (config.ui.language === 'sq') {
|
|
||||||
i18n.setLocale('sq')
|
if (getLocales().indexOf(config.ui.language) !== -1) {
|
||||||
} else if (config.ui.language === 'zh-CN') {
|
i18n.setLocale(config.ui.language)
|
||||||
i18n.setLocale('zh-CN')
|
|
||||||
} else if (config.ui.language === 'zh-TW') {
|
|
||||||
i18n.setLocale('zh-TW')
|
|
||||||
} else if (config.ui.language === 'da') {
|
|
||||||
i18n.setLocale('da')
|
|
||||||
} else if (config.ui.language === 'fr') {
|
|
||||||
i18n.setLocale('fr')
|
|
||||||
} else if (config.ui.language === 'de') {
|
|
||||||
i18n.setLocale('de')
|
|
||||||
} else if (config.ui.language === 'hu') {
|
|
||||||
i18n.setLocale('hu')
|
|
||||||
} else if (config.ui.language === 'ja') {
|
|
||||||
i18n.setLocale('ja')
|
|
||||||
} else if (config.ui.language === 'ko') {
|
|
||||||
i18n.setLocale('ko')
|
|
||||||
} else if (config.ui.language === 'no') {
|
|
||||||
i18n.setLocale('no')
|
|
||||||
} else if (config.ui.language === 'pl') {
|
|
||||||
i18n.setLocale('pl')
|
|
||||||
} else if (config.ui.language === 'pt') {
|
|
||||||
i18n.setLocale('pt')
|
|
||||||
} else if (config.ui.language === 'ru') {
|
|
||||||
i18n.setLocale('ru')
|
|
||||||
} else if (config.ui.language === 'es-ES') {
|
|
||||||
i18n.setLocale('es-ES')
|
|
||||||
} else {
|
} else {
|
||||||
i18n.setLocale('en')
|
i18n.setLocale('en')
|
||||||
}
|
}
|
||||||
|
applyShortcuts()
|
||||||
// Reload all data
|
// Reload all data
|
||||||
dataApi.init()
|
dataApi.init()
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
|
|||||||
@@ -74,4 +74,8 @@ body[data-theme="dark"]
|
|||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
body[data-theme="solarized-dark"]
|
||||||
.root, .root--expanded
|
.root, .root--expanded
|
||||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root, .root--expanded
|
||||||
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
|
|||||||
@@ -113,4 +113,28 @@ body[data-theme="solarized-dark"]
|
|||||||
.control-button--active
|
.control-button--active
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
&:active
|
&:active
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
|
|
||||||
|
.control
|
||||||
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
|
||||||
|
.control-sortBy-select
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.control-button
|
||||||
|
color $ui-monokai-inactive-text-color
|
||||||
|
&:hover
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.control-button--active
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
&:active
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import debounceRender from 'react-debounce-render'
|
||||||
import styles from './NoteList.styl'
|
import styles from './NoteList.styl'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import NoteItem from 'browser/components/NoteItem'
|
import NoteItem from 'browser/components/NoteItem'
|
||||||
import NoteItemSimple from 'browser/components/NoteItemSimple'
|
import NoteItemSimple from 'browser/components/NoteItemSimple'
|
||||||
@@ -18,6 +20,7 @@ import copy from 'copy-to-clipboard'
|
|||||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
import Markdown from '../../lib/markdown'
|
import Markdown from '../../lib/markdown'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { Menu, MenuItem, dialog } = remote
|
||||||
@@ -281,8 +284,8 @@ class NoteList extends React.Component {
|
|||||||
ee.emit('detail:focus')
|
ee.emit('detail:focus')
|
||||||
}
|
}
|
||||||
|
|
||||||
// F or S key
|
// L or S key
|
||||||
if (e.keyCode === 70 || e.keyCode === 83) {
|
if (e.keyCode === 76 || e.keyCode === 83) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
ee.emit('top:focus-search')
|
ee.emit('top:focus-search')
|
||||||
}
|
}
|
||||||
@@ -342,11 +345,10 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (location.pathname.match(/\/tags/)) {
|
if (location.pathname.match(/\/tags/)) {
|
||||||
|
const listOfTags = params.tagname.split(' ')
|
||||||
return data.noteMap.map(note => {
|
return data.noteMap.map(note => {
|
||||||
return note
|
return note
|
||||||
}).filter(note => {
|
}).filter(note => listOfTags.every(tag => note.tags.includes(tag)))
|
||||||
return note.tags.includes(params.tagname)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getContextNotes()
|
return this.getContextNotes()
|
||||||
@@ -455,12 +457,19 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleDragStart (e, note) {
|
handleDragStart (e, note) {
|
||||||
const { selectedNoteKeys } = this.state
|
let { selectedNoteKeys } = this.state
|
||||||
|
const noteKey = getNoteKey(note)
|
||||||
|
|
||||||
|
if (!selectedNoteKeys.includes(noteKey)) {
|
||||||
|
selectedNoteKeys = []
|
||||||
|
selectedNoteKeys.push(noteKey)
|
||||||
|
}
|
||||||
|
|
||||||
const notes = this.notes.map((note) => Object.assign({}, note))
|
const notes = this.notes.map((note) => Object.assign({}, note))
|
||||||
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
|
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
|
||||||
const noteData = JSON.stringify(selectedNotes)
|
const noteData = JSON.stringify(selectedNotes)
|
||||||
e.dataTransfer.setData('note', noteData)
|
e.dataTransfer.setData('note', noteData)
|
||||||
this.setState({ selectedNoteKeys: [] })
|
this.selectNextNote()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNoteContextMenu (e, uniqueKey) {
|
handleNoteContextMenu (e, uniqueKey) {
|
||||||
@@ -585,16 +594,11 @@ class NoteList extends React.Component {
|
|||||||
const notes = this.notes.map((note) => Object.assign({}, note))
|
const notes = this.notes.map((note) => Object.assign({}, note))
|
||||||
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
|
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
|
||||||
const firstNote = selectedNotes[0]
|
const firstNote = selectedNotes[0]
|
||||||
|
const { confirmDeletion } = this.props.config.ui
|
||||||
|
|
||||||
if (firstNote.isTrashed) {
|
if (firstNote.isTrashed) {
|
||||||
const noteExp = selectedNotes.length > 1 ? 'notes' : 'note'
|
if (!confirmDeleteNote(confirmDeletion, true)) return
|
||||||
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
|
||||||
type: 'warning',
|
|
||||||
message: i18n.__('Confirm note deletion'),
|
|
||||||
detail: `This will permanently remove ${selectedNotes.length} ${noteExp}.`,
|
|
||||||
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
|
||||||
})
|
|
||||||
if (dialogueButtonIndex === 1) return
|
|
||||||
Promise.all(
|
Promise.all(
|
||||||
selectedNotes.map((note) => {
|
selectedNotes.map((note) => {
|
||||||
return dataApi
|
return dataApi
|
||||||
@@ -615,6 +619,8 @@ class NoteList extends React.Component {
|
|||||||
})
|
})
|
||||||
console.log('Notes were all deleted')
|
console.log('Notes were all deleted')
|
||||||
} else {
|
} else {
|
||||||
|
if (!confirmDeleteNote(confirmDeletion, false)) return
|
||||||
|
|
||||||
Promise.all(
|
Promise.all(
|
||||||
selectedNotes.map((note) => {
|
selectedNotes.map((note) => {
|
||||||
note.isTrashed = true
|
note.isTrashed = true
|
||||||
@@ -658,6 +664,10 @@ class NoteList extends React.Component {
|
|||||||
title: firstNote.title + ' ' + i18n.__('copy'),
|
title: firstNote.title + ' ' + i18n.__('copy'),
|
||||||
content: firstNote.content
|
content: firstNote.content
|
||||||
})
|
})
|
||||||
|
.then((note) => {
|
||||||
|
attachmentManagement.cloneAttachments(firstNote, note)
|
||||||
|
return note
|
||||||
|
})
|
||||||
.then((note) => {
|
.then((note) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
@@ -918,7 +928,7 @@ class NoteList extends React.Component {
|
|||||||
if (note.isTrashed !== true || location.pathname === '/trashed') return true
|
if (note.isTrashed !== true || location.pathname === '/trashed') return true
|
||||||
})
|
})
|
||||||
|
|
||||||
moment.locale('en', {
|
moment.updateLocale('en', {
|
||||||
relativeTime: {
|
relativeTime: {
|
||||||
future: 'in %s',
|
future: 'in %s',
|
||||||
past: '%s ago',
|
past: '%s ago',
|
||||||
@@ -939,15 +949,24 @@ class NoteList extends React.Component {
|
|||||||
|
|
||||||
const viewType = this.getViewType()
|
const viewType = this.getViewType()
|
||||||
|
|
||||||
|
const autoSelectFirst =
|
||||||
|
notes.length === 1 ||
|
||||||
|
selectedNoteKeys.length === 0 ||
|
||||||
|
notes.every(note => !selectedNoteKeys.includes(note.key))
|
||||||
|
|
||||||
const noteList = notes
|
const noteList = notes
|
||||||
.map(note => {
|
.map((note, index) => {
|
||||||
if (note == null) {
|
if (note == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDefault = config.listStyle === 'DEFAULT'
|
const isDefault = config.listStyle === 'DEFAULT'
|
||||||
const uniqueKey = getNoteKey(note)
|
const uniqueKey = getNoteKey(note)
|
||||||
const isActive = selectedNoteKeys.includes(uniqueKey)
|
|
||||||
|
const isActive =
|
||||||
|
selectedNoteKeys.includes(uniqueKey) ||
|
||||||
|
notes.length === 1 ||
|
||||||
|
(autoSelectFirst && index === 0)
|
||||||
const dateDisplay = moment(
|
const dateDisplay = moment(
|
||||||
config.sortBy === 'CREATED_AT'
|
config.sortBy === 'CREATED_AT'
|
||||||
? note.createdAt : note.updatedAt
|
? note.createdAt : note.updatedAt
|
||||||
@@ -1049,4 +1068,4 @@ NoteList.propTypes = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(NoteList, styles)
|
export default debounceRender(CSSModules(NoteList, styles))
|
||||||
|
|||||||
@@ -48,4 +48,5 @@ body[data-theme="dark"]
|
|||||||
line-height normal
|
line-height normal
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
opacity 0
|
opacity 0
|
||||||
transition 0.1s
|
transition 0.1s
|
||||||
|
white-space nowrap
|
||||||
|
|||||||
@@ -117,3 +117,8 @@ body[data-theme="solarized-dark"]
|
|||||||
.root, .root--folded
|
.root, .root--folded
|
||||||
background-color $ui-solarized-dark-backgroundColor
|
background-color $ui-solarized-dark-backgroundColor
|
||||||
border-right 1px solid $ui-solarized-dark-borderColor
|
border-right 1px solid $ui-solarized-dark-borderColor
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root, .root--folded
|
||||||
|
background-color $ui-monokai-backgroundColor
|
||||||
|
border-right 1px solid $ui-monokai-borderColor
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import i18n from 'browser/lib/i18n'
|
|||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, dialog } = remote
|
const { Menu, dialog } = remote
|
||||||
|
const escapeStringRegexp = require('escape-string-regexp')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
class StorageItem extends React.Component {
|
class StorageItem extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -201,7 +203,7 @@ class StorageItem extends React.Component {
|
|||||||
createdNoteData.forEach((newNote) => {
|
createdNoteData.forEach((newNote) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'MOVE_NOTE',
|
type: 'MOVE_NOTE',
|
||||||
originNote: noteData.find((note) => note.content === newNote.content),
|
originNote: noteData.find((note) => note.content === newNote.oldContent),
|
||||||
note: newNote
|
note: newNote
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -223,7 +225,8 @@ class StorageItem extends React.Component {
|
|||||||
const { folderNoteMap, trashedSet } = data
|
const { folderNoteMap, trashedSet } = data
|
||||||
const SortableStorageItemChild = SortableElement(StorageItemChild)
|
const SortableStorageItemChild = SortableElement(StorageItemChild)
|
||||||
const folderList = storage.folders.map((folder, index) => {
|
const folderList = storage.folders.map((folder, index) => {
|
||||||
const isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key)))
|
let folderRegex = new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + escapeStringRegexp(path.sep) + 'folders' + escapeStringRegexp(path.sep) + folder.key)
|
||||||
|
const isActive = !!(location.pathname.match(folderRegex))
|
||||||
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
||||||
|
|
||||||
let noteCount = 0
|
let noteCount = 0
|
||||||
@@ -253,7 +256,7 @@ class StorageItem extends React.Component {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const isActive = location.pathname.match(new RegExp('\/storages\/' + storage.key + '$'))
|
const isActive = location.pathname.match(new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + '$'))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div styleName={isFolded ? 'root--folded' : 'root'}
|
<div styleName={isFolded ? 'root--folded' : 'root'}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
height 36px
|
height 36px
|
||||||
padding-left 25px
|
padding-left 25px
|
||||||
padding-right 15px
|
padding-right 15px
|
||||||
line-height 22px
|
line-height 36px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
font-size 14px
|
font-size 14px
|
||||||
border none
|
border none
|
||||||
@@ -147,7 +147,7 @@ body[data-theme="dark"]
|
|||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
&:active
|
&:active
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
.header--active
|
.header--active
|
||||||
.header-addFolderButton
|
.header-addFolderButton
|
||||||
@@ -180,7 +180,7 @@ body[data-theme="dark"]
|
|||||||
&:active, &:active:hover
|
&:active, &:active:hover
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
border-radius 2px
|
border-radius 2px
|
||||||
opacity 0
|
opacity 0
|
||||||
transition 0.1s
|
transition 0.1s
|
||||||
|
white-space nowrap
|
||||||
|
|
||||||
body[data-theme="white"]
|
body[data-theme="white"]
|
||||||
.non-active-button
|
.non-active-button
|
||||||
|
|||||||
@@ -145,20 +145,29 @@ class SideNav extends React.Component {
|
|||||||
|
|
||||||
tagListComponent () {
|
tagListComponent () {
|
||||||
const { data, location, config } = this.props
|
const { data, location, config } = this.props
|
||||||
|
const relatedTags = this.getRelatedTags(this.getActiveTags(location.pathname), data.noteMap)
|
||||||
let tagList = _.sortBy(data.tagNoteMap.map(
|
let tagList = _.sortBy(data.tagNoteMap.map(
|
||||||
(tag, name) => ({name, size: tag.size})),
|
(tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
|
||||||
['name']
|
), ['name']).filter(
|
||||||
|
tag => tag.size > 0
|
||||||
)
|
)
|
||||||
if (config.sortTagsBy === 'COUNTER') {
|
if (config.sortTagsBy === 'COUNTER') {
|
||||||
tagList = _.sortBy(tagList, item => (0 - item.size))
|
tagList = _.sortBy(tagList, item => (0 - item.size))
|
||||||
}
|
}
|
||||||
|
if (config.ui.showOnlyRelatedTags && (relatedTags.size > 0)) {
|
||||||
|
tagList = tagList.filter(
|
||||||
|
tag => tag.related
|
||||||
|
)
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
tagList.map(tag => {
|
tagList.map(tag => {
|
||||||
return (
|
return (
|
||||||
<TagListItem
|
<TagListItem
|
||||||
name={tag.name}
|
name={tag.name}
|
||||||
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
|
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
|
||||||
|
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
|
||||||
isActive={this.getTagActive(location.pathname, tag.name)}
|
isActive={this.getTagActive(location.pathname, tag.name)}
|
||||||
|
isRelated={tag.related}
|
||||||
key={tag.name}
|
key={tag.name}
|
||||||
count={tag.size}
|
count={tag.size}
|
||||||
/>
|
/>
|
||||||
@@ -167,10 +176,30 @@ class SideNav extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRelatedTags (activeTags, noteMap) {
|
||||||
|
if (activeTags.length === 0) {
|
||||||
|
return new Set()
|
||||||
|
}
|
||||||
|
const relatedNotes = noteMap.map(
|
||||||
|
note => ({key: note.key, tags: note.tags})
|
||||||
|
).filter(
|
||||||
|
note => activeTags.every(tag => note.tags.includes(tag))
|
||||||
|
)
|
||||||
|
const relatedTags = new Set()
|
||||||
|
relatedNotes.forEach(note => note.tags.map(tag => relatedTags.add(tag)))
|
||||||
|
return relatedTags
|
||||||
|
}
|
||||||
|
|
||||||
getTagActive (path, tag) {
|
getTagActive (path, tag) {
|
||||||
|
return this.getActiveTags(path).includes(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveTags (path) {
|
||||||
const pathSegments = path.split('/')
|
const pathSegments = path.split('/')
|
||||||
const pathTag = pathSegments[pathSegments.length - 1]
|
const tags = pathSegments[pathSegments.length - 1]
|
||||||
return pathTag === tag
|
return (tags === 'alltags')
|
||||||
|
? []
|
||||||
|
: tags.split(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClickTagListItem (name) {
|
handleClickTagListItem (name) {
|
||||||
@@ -192,6 +221,19 @@ class SideNav extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleClickNarrowToTag (tag) {
|
||||||
|
const { router } = this.context
|
||||||
|
const { location } = this.props
|
||||||
|
const listOfTags = this.getActiveTags(location.pathname)
|
||||||
|
const indexOfTag = listOfTags.indexOf(tag)
|
||||||
|
if (indexOfTag > -1) {
|
||||||
|
listOfTags.splice(indexOfTag, 1)
|
||||||
|
} else {
|
||||||
|
listOfTags.push(tag)
|
||||||
|
}
|
||||||
|
router.push(`/tags/${listOfTags.join(' ')}`)
|
||||||
|
}
|
||||||
|
|
||||||
emptyTrash (entries) {
|
emptyTrash (entries) {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
const deletionPromises = entries.map((note) => {
|
const deletionPromises = entries.map((note) => {
|
||||||
|
|||||||
@@ -69,3 +69,14 @@ body[data-theme="dark"]
|
|||||||
navDarkButtonColor()
|
navDarkButtonColor()
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
border-left 1px solid $ui-dark-borderColor
|
border-left 1px solid $ui-dark-borderColor
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
navButtonColor()
|
||||||
|
.zoom
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
&:hover
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-monokai-active-color
|
||||||
|
&:active
|
||||||
|
color $ui-monokai-active-color
|
||||||
|
|||||||
@@ -234,3 +234,25 @@ body[data-theme="solarized-dark"]
|
|||||||
input
|
input
|
||||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root, .root--expanded
|
||||||
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
|
|
||||||
|
.control
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
.control-search
|
||||||
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
|
|
||||||
|
.control-search-icon
|
||||||
|
absolute top bottom left
|
||||||
|
line-height 32px
|
||||||
|
width 35px
|
||||||
|
color $ui-monokai-inactive-text-color
|
||||||
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
|
|
||||||
|
.control-search-input
|
||||||
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
|
input
|
||||||
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|||||||
@@ -134,4 +134,10 @@ body[data-theme="solarized-dark"]
|
|||||||
.sortableItemHelper
|
.sortableItemHelper
|
||||||
color: $ui-solarized-dark-text-color
|
color: $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.ModalBase
|
||||||
|
.modalBack
|
||||||
|
background-color $ui-monokai-backgroundColor
|
||||||
|
.sortableItemHelper
|
||||||
|
color: $ui-monokai-text-color
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,45 @@ document.addEventListener('dragover', function (e) {
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// prevent menu from popup when alt pressed
|
||||||
|
// but still able to toggle menu when only alt is pressed
|
||||||
|
let isAltPressing = false
|
||||||
|
let isAltWithMouse = false
|
||||||
|
let isAltWithOtherKey = false
|
||||||
|
let isOtherKey = false
|
||||||
|
|
||||||
|
document.addEventListener('keydown', function (e) {
|
||||||
|
if (e.key === 'Alt') {
|
||||||
|
isAltPressing = true
|
||||||
|
if (isOtherKey) {
|
||||||
|
isAltWithOtherKey = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isAltPressing) {
|
||||||
|
isAltWithOtherKey = true
|
||||||
|
}
|
||||||
|
isOtherKey = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', function (e) {
|
||||||
|
if (isAltPressing) {
|
||||||
|
isAltWithMouse = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener('keyup', function (e) {
|
||||||
|
if (e.key === 'Alt') {
|
||||||
|
if (isAltWithMouse || isAltWithOtherKey) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
isAltWithMouse = false
|
||||||
|
isAltWithOtherKey = false
|
||||||
|
isAltPressing = false
|
||||||
|
isOtherKey = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
document.addEventListener('click', function (e) {
|
document.addEventListener('click', function (e) {
|
||||||
const className = e.target.className
|
const className = e.target.className
|
||||||
if (!className && typeof (className) !== 'string') return
|
if (!className && typeof (className) !== 'string') return
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import RcParser from 'browser/lib/RcParser'
|
import RcParser from 'browser/lib/RcParser'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
const win = global.process.platform === 'win32'
|
const win = global.process.platform === 'win32'
|
||||||
@@ -20,7 +21,8 @@ export const DEFAULT_CONFIG = {
|
|||||||
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
|
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
|
||||||
amaEnabled: true,
|
amaEnabled: true,
|
||||||
hotkey: {
|
hotkey: {
|
||||||
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
|
toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
|
||||||
|
toggleMode: OSX ? 'Command + M' : 'Ctrl + M'
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
language: 'en',
|
language: 'en',
|
||||||
@@ -53,8 +55,13 @@ export const DEFAULT_CONFIG = {
|
|||||||
latexInlineClose: '$',
|
latexInlineClose: '$',
|
||||||
latexBlockOpen: '$$',
|
latexBlockOpen: '$$',
|
||||||
latexBlockClose: '$$',
|
latexBlockClose: '$$',
|
||||||
|
plantUMLServerAddress: 'http://www.plantuml.com/plantuml',
|
||||||
scrollPastEnd: false,
|
scrollPastEnd: false,
|
||||||
smartQuotes: true,
|
smartQuotes: true,
|
||||||
|
breaks: true,
|
||||||
|
smartArrows: false,
|
||||||
|
allowCustomCSS: false,
|
||||||
|
customCSS: '',
|
||||||
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'
|
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'
|
||||||
},
|
},
|
||||||
blog: {
|
blog: {
|
||||||
@@ -135,6 +142,8 @@ function set (updates) {
|
|||||||
document.body.setAttribute('data-theme', 'white')
|
document.body.setAttribute('data-theme', 'white')
|
||||||
} else if (newConfig.ui.theme === 'solarized-dark') {
|
} else if (newConfig.ui.theme === 'solarized-dark') {
|
||||||
document.body.setAttribute('data-theme', 'solarized-dark')
|
document.body.setAttribute('data-theme', 'solarized-dark')
|
||||||
|
} else if (newConfig.ui.theme === 'monokai') {
|
||||||
|
document.body.setAttribute('data-theme', 'monokai')
|
||||||
} else {
|
} else {
|
||||||
document.body.setAttribute('data-theme', 'default')
|
document.body.setAttribute('data-theme', 'default')
|
||||||
}
|
}
|
||||||
@@ -163,6 +172,7 @@ function set (updates) {
|
|||||||
ipcRenderer.send('config-renew', {
|
ipcRenderer.send('config-renew', {
|
||||||
config: get()
|
config: get()
|
||||||
})
|
})
|
||||||
|
ee.emit('config-renew')
|
||||||
}
|
}
|
||||||
|
|
||||||
function assignConfigValues (originalConfig, rcConfig) {
|
function assignConfigValues (originalConfig, rcConfig) {
|
||||||
@@ -172,6 +182,17 @@ function assignConfigValues (originalConfig, rcConfig) {
|
|||||||
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
|
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
|
||||||
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
|
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
|
||||||
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview)
|
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview)
|
||||||
|
|
||||||
|
rewriteHotkey(config)
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewriteHotkey (config) {
|
||||||
|
const keys = [...Object.keys(config.hotkey)]
|
||||||
|
keys.forEach(key => {
|
||||||
|
config.hotkey[key] = config.hotkey[key].replace(/Cmd/g, 'Command')
|
||||||
|
})
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
424
browser/main/lib/dataApi/attachmentManagement.js
Normal file
424
browser/main/lib/dataApi/attachmentManagement.js
Normal file
@@ -0,0 +1,424 @@
|
|||||||
|
const uniqueSlug = require('unique-slug')
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const findStorage = require('browser/lib/findStorage')
|
||||||
|
const mdurl = require('mdurl')
|
||||||
|
const fse = require('fs-extra')
|
||||||
|
const escapeStringRegexp = require('escape-string-regexp')
|
||||||
|
const sander = require('sander')
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
|
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
|
||||||
|
const DESTINATION_FOLDER = 'attachments'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* Copies a copy of an attachment to the storage folder specified by the given key and return the generated attachment name.
|
||||||
|
* Renames the file to match a unique file name.
|
||||||
|
*
|
||||||
|
* @param {String} sourceFilePath The source path of the attachment to be copied
|
||||||
|
* @param {String} storageKey Storage key of the destination storage
|
||||||
|
* @param {String} noteKey Key of the current note. Will be used as subfolder in :storage
|
||||||
|
* @param {boolean} useRandomName determines whether a random filename for the new file is used. If false the source file name is used
|
||||||
|
* @return {Promise<String>} name (inclusive extension) of the generated file
|
||||||
|
*/
|
||||||
|
function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!sourceFilePath) {
|
||||||
|
reject('sourceFilePath has to be given')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!storageKey) {
|
||||||
|
reject('storageKey has to be given')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!noteKey) {
|
||||||
|
reject('noteKey has to be given')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(sourceFilePath)) {
|
||||||
|
reject('source file does not exist')
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetStorage = findStorage.findStorage(storageKey)
|
||||||
|
|
||||||
|
const inputFileStream = fs.createReadStream(sourceFilePath)
|
||||||
|
let destinationName
|
||||||
|
if (useRandomName) {
|
||||||
|
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}`
|
||||||
|
} else {
|
||||||
|
destinationName = path.basename(sourceFilePath)
|
||||||
|
}
|
||||||
|
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
||||||
|
createAttachmentDestinationFolder(targetStorage.path, noteKey)
|
||||||
|
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
|
||||||
|
inputFileStream.pipe(outputFile)
|
||||||
|
inputFileStream.on('end', () => {
|
||||||
|
resolve(destinationName)
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
return reject(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAttachmentDestinationFolder (destinationStoragePath, noteKey) {
|
||||||
|
let destinationDir = path.join(destinationStoragePath, DESTINATION_FOLDER)
|
||||||
|
if (!fs.existsSync(destinationDir)) {
|
||||||
|
fs.mkdirSync(destinationDir)
|
||||||
|
}
|
||||||
|
destinationDir = path.join(destinationStoragePath, DESTINATION_FOLDER, noteKey)
|
||||||
|
if (!fs.existsSync(destinationDir)) {
|
||||||
|
fs.mkdirSync(destinationDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Moves attachments from the old location ('/images') to the new one ('/attachments/noteKey)
|
||||||
|
* @param renderedHTML HTML of the current note
|
||||||
|
* @param storagePath Storage path of the current note
|
||||||
|
* @param noteKey Key of the current note
|
||||||
|
*/
|
||||||
|
function migrateAttachments (renderedHTML, storagePath, noteKey) {
|
||||||
|
if (sander.existsSync(path.join(storagePath, 'images'))) {
|
||||||
|
const attachments = getAttachmentsInContent(renderedHTML) || []
|
||||||
|
if (attachments !== []) {
|
||||||
|
createAttachmentDestinationFolder(storagePath, noteKey)
|
||||||
|
}
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
const attachmentBaseName = path.basename(attachment)
|
||||||
|
const possibleLegacyPath = path.join(storagePath, 'images', attachmentBaseName)
|
||||||
|
if (sander.existsSync(possibleLegacyPath)) {
|
||||||
|
const destinationPath = path.join(storagePath, DESTINATION_FOLDER, attachmentBaseName)
|
||||||
|
if (!sander.existsSync(destinationPath)) {
|
||||||
|
sander.copyFileSync(possibleLegacyPath).to(destinationPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Fixes the URLs embedded in the generated HTML so that they again refer actual local files.
|
||||||
|
* @param {String} renderedHTML HTML in that the links should be fixed
|
||||||
|
* @param {String} storagePath Path of the current storage
|
||||||
|
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
|
||||||
|
*/
|
||||||
|
function fixLocalURLS (renderedHTML, storagePath) {
|
||||||
|
return renderedHTML.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Generates the markdown code for a given attachment
|
||||||
|
* @param {String} fileName Name of the attachment
|
||||||
|
* @param {String} path Path of the attachment
|
||||||
|
* @param {Boolean} showPreview Indicator whether the generated markdown should show a preview of the image. Note that at the moment only previews for images are supported
|
||||||
|
* @returns {String} Generated markdown code
|
||||||
|
*/
|
||||||
|
function generateAttachmentMarkdown (fileName, path, showPreview) {
|
||||||
|
return `${showPreview ? '!' : ''}[${fileName}](${path})`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Handles the drop-event of a file. Includes the necessary markdown code and copies the file to the corresponding storage folder.
|
||||||
|
* The method calls {CodeEditor#insertAttachmentMd()} to include the generated markdown at the needed place!
|
||||||
|
* @param {CodeEditor} codeEditor Markdown editor. Its insertAttachmentMd() method will be called to include the markdown code
|
||||||
|
* @param {String} storageKey Key of the current storage
|
||||||
|
* @param {String} noteKey Key of the current note
|
||||||
|
* @param {Event} dropEvent DropEvent
|
||||||
|
*/
|
||||||
|
function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
|
||||||
|
const file = dropEvent.dataTransfer.files[0]
|
||||||
|
const filePath = file.path
|
||||||
|
const originalFileName = path.basename(filePath)
|
||||||
|
const fileType = file['type']
|
||||||
|
|
||||||
|
copyAttachment(filePath, storageKey, noteKey).then((fileName) => {
|
||||||
|
const showPreview = fileType.startsWith('image')
|
||||||
|
const imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), showPreview)
|
||||||
|
codeEditor.insertAttachmentMd(imageMd)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Creates a new file in the storage folder belonging to the current note and inserts the correct markdown code
|
||||||
|
* @param {CodeEditor} codeEditor Markdown editor. Its insertAttachmentMd() method will be called to include the markdown code
|
||||||
|
* @param {String} storageKey Key of the current storage
|
||||||
|
* @param {String} noteKey Key of the current note
|
||||||
|
* @param {DataTransferItem} dataTransferItem Part of the past-event
|
||||||
|
*/
|
||||||
|
function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem) {
|
||||||
|
if (!codeEditor) {
|
||||||
|
throw new Error('codeEditor has to be given')
|
||||||
|
}
|
||||||
|
if (!storageKey) {
|
||||||
|
throw new Error('storageKey has to be given')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!noteKey) {
|
||||||
|
throw new Error('noteKey has to be given')
|
||||||
|
}
|
||||||
|
if (!dataTransferItem) {
|
||||||
|
throw new Error('dataTransferItem has to be given')
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = dataTransferItem.getAsFile()
|
||||||
|
const reader = new FileReader()
|
||||||
|
let base64data
|
||||||
|
const targetStorage = findStorage.findStorage(storageKey)
|
||||||
|
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
||||||
|
createAttachmentDestinationFolder(targetStorage.path, noteKey)
|
||||||
|
|
||||||
|
const imageName = `${uniqueSlug()}.png`
|
||||||
|
const imagePath = path.join(destinationDir, imageName)
|
||||||
|
|
||||||
|
reader.onloadend = function () {
|
||||||
|
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
|
||||||
|
base64data += base64data.replace('+', ' ')
|
||||||
|
const binaryData = new Buffer(base64data, 'base64').toString('binary')
|
||||||
|
fs.writeFileSync(imagePath, binaryData, 'binary')
|
||||||
|
const imageReferencePath = path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, imageName)
|
||||||
|
const imageMd = generateAttachmentMarkdown(imageName, imageReferencePath, true)
|
||||||
|
codeEditor.insertAttachmentMd(imageMd)
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(blob)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Returns all attachment paths of the given markdown
|
||||||
|
* @param {String} markdownContent content in which the attachment paths should be found
|
||||||
|
* @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown
|
||||||
|
*/
|
||||||
|
function getAttachmentsInContent (markdownContent) {
|
||||||
|
const preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep)
|
||||||
|
const regexp = new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + '|/)' + '?([a-zA-Z0-9]|-)*' + '(' + escapeStringRegexp(path.sep) + '|/)' + '([a-zA-Z0-9]|\\.)+(\\.[a-zA-Z0-9]+)?', 'g')
|
||||||
|
return preparedInput.match(regexp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Returns an array of the absolute paths of the attachments referenced in the given markdown code
|
||||||
|
* @param {String} markdownContent content in which the attachment paths should be found
|
||||||
|
* @param {String} storagePath path of the current storage
|
||||||
|
* @returns {String[]} Absolute paths of the referenced attachments
|
||||||
|
*/
|
||||||
|
function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
|
||||||
|
const temp = getAttachmentsInContent(markdownContent) || []
|
||||||
|
const result = []
|
||||||
|
for (const relativePath of temp) {
|
||||||
|
result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER)))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Moves the attachments of the current note to the new location.
|
||||||
|
* Returns a modified version of the given content so that the links to the attachments point to the new note key.
|
||||||
|
* @param {String} oldPath Source of the note to be moved
|
||||||
|
* @param {String} newPath Destination of the note to be moved
|
||||||
|
* @param {String} noteKey Old note key
|
||||||
|
* @param {String} newNoteKey New note key
|
||||||
|
* @param {String} noteContent Content of the note to be moved
|
||||||
|
* @returns {String} Modified version of noteContent in which the paths of the attachments are fixed
|
||||||
|
*/
|
||||||
|
function moveAttachments (oldPath, newPath, noteKey, newNoteKey, noteContent) {
|
||||||
|
const src = path.join(oldPath, DESTINATION_FOLDER, noteKey)
|
||||||
|
const dest = path.join(newPath, DESTINATION_FOLDER, newNoteKey)
|
||||||
|
if (fse.existsSync(src)) {
|
||||||
|
fse.moveSync(src, dest)
|
||||||
|
}
|
||||||
|
return replaceNoteKeyWithNewNoteKey(noteContent, noteKey, newNoteKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies the given content so that in all attachment references the oldNoteKey is replaced by the new one
|
||||||
|
* @param noteContent content that should be modified
|
||||||
|
* @param oldNoteKey note key to be replaced
|
||||||
|
* @param newNoteKey note key serving as a replacement
|
||||||
|
* @returns {String} modified note content
|
||||||
|
*/
|
||||||
|
function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) {
|
||||||
|
if (noteContent) {
|
||||||
|
return noteContent.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
|
||||||
|
}
|
||||||
|
return noteContent
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Deletes all :storage and noteKey references from the given input.
|
||||||
|
* @param input Input in which the references should be deleted
|
||||||
|
* @param noteKey Key of the current note
|
||||||
|
* @returns {String} Input without the references
|
||||||
|
*/
|
||||||
|
function removeStorageAndNoteReferences (input, noteKey) {
|
||||||
|
return input.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + noteKey + ')?', 'g'), DESTINATION_FOLDER)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Deletes the attachment folder specified by the given storageKey and noteKey
|
||||||
|
* @param storageKey Key of the storage of the note to be deleted
|
||||||
|
* @param noteKey Key of the note to be deleted
|
||||||
|
*/
|
||||||
|
function deleteAttachmentFolder (storageKey, noteKey) {
|
||||||
|
const storagePath = findStorage.findStorage(storageKey)
|
||||||
|
const noteAttachmentPath = path.join(storagePath.path, DESTINATION_FOLDER, noteKey)
|
||||||
|
sander.rimrafSync(noteAttachmentPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Deletes all attachments stored in the attachment folder of the give not that are not referenced in the markdownContent
|
||||||
|
* @param markdownContent Content of the note. All unreferenced notes will be deleted
|
||||||
|
* @param storageKey StorageKey of the current note. Is used to determine the belonging attachment folder.
|
||||||
|
* @param noteKey NoteKey of the current note. Is used to determine the belonging attachment folder.
|
||||||
|
*/
|
||||||
|
function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey) {
|
||||||
|
if (storageKey == null || noteKey == null || markdownContent == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const targetStorage = findStorage.findStorage(storageKey)
|
||||||
|
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
||||||
|
const attachmentsInNote = getAttachmentsInContent(markdownContent)
|
||||||
|
const attachmentsInNoteOnlyFileNames = []
|
||||||
|
if (attachmentsInNote) {
|
||||||
|
for (let i = 0; i < attachmentsInNote.length; i++) {
|
||||||
|
attachmentsInNoteOnlyFileNames.push(attachmentsInNote[i].replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey + escapeStringRegexp(path.sep), 'g'), ''))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fs.existsSync(attachmentFolder)) {
|
||||||
|
fs.readdir(attachmentFolder, (err, files) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error reading directory "' + attachmentFolder + '". Error:')
|
||||||
|
console.error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
files.forEach(file => {
|
||||||
|
if (!attachmentsInNoteOnlyFileNames.includes(file)) {
|
||||||
|
const absolutePathOfFile = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey, file)
|
||||||
|
fs.unlink(absolutePathOfFile, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Could not delete "%s"', absolutePathOfFile)
|
||||||
|
console.error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.info('File "' + absolutePathOfFile + '" deleted because it was not included in the content of the note')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.info('Attachment folder ("' + attachmentFolder + '") did not exist..')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clones the attachments of a given note.
|
||||||
|
* Copies the attachments to their new destination and updates the content of the new note so that the attachment-links again point to the correct destination.
|
||||||
|
* @param oldNote Note that is being cloned
|
||||||
|
* @param newNote Clone of the note
|
||||||
|
*/
|
||||||
|
function cloneAttachments (oldNote, newNote) {
|
||||||
|
if (newNote.type === 'MARKDOWN_NOTE') {
|
||||||
|
const oldStorage = findStorage.findStorage(oldNote.storage)
|
||||||
|
const newStorage = findStorage.findStorage(newNote.storage)
|
||||||
|
const attachmentsPaths = getAbsolutePathsOfAttachmentsInContent(oldNote.content, oldStorage.path) || []
|
||||||
|
|
||||||
|
const destinationFolder = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key)
|
||||||
|
if (!sander.existsSync(destinationFolder)) {
|
||||||
|
sander.mkdirSync(destinationFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const attachment of attachmentsPaths) {
|
||||||
|
const destination = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key, path.basename(attachment))
|
||||||
|
sander.copyFileSync(attachment).to(destination)
|
||||||
|
}
|
||||||
|
newNote.content = replaceNoteKeyWithNewNoteKey(newNote.content, oldNote.key, newNote.key)
|
||||||
|
} else {
|
||||||
|
console.debug('Cloning of the attachment was skipped since it only works for MARKDOWN_NOTEs')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateFileNotFoundMarkdown () {
|
||||||
|
return '**' + i18n.__('⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠') + '**'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether a given text is a link to an boostnote attachment
|
||||||
|
* @param text Text that might contain a attachment link
|
||||||
|
* @return {Boolean} Result of the test
|
||||||
|
*/
|
||||||
|
function isAttachmentLink (text) {
|
||||||
|
if (text) {
|
||||||
|
return text.match(new RegExp('.*\\[.*\\]\\( *' + escapeStringRegexp(STORAGE_FOLDER_PLACEHOLDER) + escapeStringRegexp(path.sep) + '.*\\).*', 'gi')) != null
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Handles the paste of an attachment link. Copies the referenced attachment to the location belonging to the new note.
|
||||||
|
* Returns a modified version of the pasted text so that it matches the copied attachment (resp. the new location)
|
||||||
|
* @param storageKey StorageKey of the current note
|
||||||
|
* @param noteKey NoteKey of the currentNote
|
||||||
|
* @param linkText Text that was pasted
|
||||||
|
* @return {Promise<String>} Promise returning the modified text
|
||||||
|
*/
|
||||||
|
function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
|
||||||
|
if (storageKey != null && noteKey != null && linkText != null) {
|
||||||
|
const storagePath = findStorage.findStorage(storageKey).path
|
||||||
|
const attachments = getAttachmentsInContent(linkText) || []
|
||||||
|
const replaceInstructions = []
|
||||||
|
const copies = []
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
const absPathOfAttachment = attachment.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER))
|
||||||
|
copies.push(
|
||||||
|
sander.exists(absPathOfAttachment)
|
||||||
|
.then((fileExists) => {
|
||||||
|
if (!fileExists) {
|
||||||
|
const fileNotFoundRegexp = new RegExp('!?' + escapeStringRegexp('[') + '[\\w|\\d|\\s|\\.]*\\]\\(\\s*' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + escapeStringRegexp(path.sep) + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + escapeStringRegexp(')'))
|
||||||
|
replaceInstructions.push({regexp: fileNotFoundRegexp, replacement: this.generateFileNotFoundMarkdown()})
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
return this.copyAttachment(absPathOfAttachment, storageKey, noteKey)
|
||||||
|
.then((fileName) => {
|
||||||
|
const replaceLinkRegExp = new RegExp(escapeStringRegexp('(') + ' *' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + escapeStringRegexp(path.sep) + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + ' *' + escapeStringRegexp(')'))
|
||||||
|
replaceInstructions.push({
|
||||||
|
regexp: replaceLinkRegExp,
|
||||||
|
replacement: '(' + path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName) + ')'
|
||||||
|
})
|
||||||
|
return Promise.resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return Promise.all(copies).then(() => {
|
||||||
|
let modifiedLinkText = linkText
|
||||||
|
for (const replaceInstruction of replaceInstructions) {
|
||||||
|
modifiedLinkText = modifiedLinkText.replace(replaceInstruction.regexp, replaceInstruction.replacement)
|
||||||
|
}
|
||||||
|
return modifiedLinkText
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log('One if the parameters was null -> Do nothing..')
|
||||||
|
return Promise.resolve(linkText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
copyAttachment,
|
||||||
|
fixLocalURLS,
|
||||||
|
generateAttachmentMarkdown,
|
||||||
|
handleAttachmentDrop,
|
||||||
|
handlePastImageEvent,
|
||||||
|
getAttachmentsInContent,
|
||||||
|
getAbsolutePathsOfAttachmentsInContent,
|
||||||
|
removeStorageAndNoteReferences,
|
||||||
|
deleteAttachmentFolder,
|
||||||
|
deleteAttachmentsNotPresentInNote,
|
||||||
|
moveAttachments,
|
||||||
|
cloneAttachments,
|
||||||
|
isAttachmentLink,
|
||||||
|
handleAttachmentLinkPaste,
|
||||||
|
generateFileNotFoundMarkdown,
|
||||||
|
migrateAttachments,
|
||||||
|
STORAGE_FOLDER_PLACEHOLDER,
|
||||||
|
DESTINATION_FOLDER
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const { findStorage } = require('browser/lib/findStorage')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Copy an image and return the path.
|
|
||||||
* @param {String} filePath
|
|
||||||
* @param {String} storageKey
|
|
||||||
* @param {Boolean} rename create new filename or leave the old one
|
|
||||||
* @return {Promise<any>} an image path
|
|
||||||
*/
|
|
||||||
function copyImage (filePath, storageKey, rename = true) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
try {
|
|
||||||
const targetStorage = findStorage(storageKey)
|
|
||||||
|
|
||||||
const inputImage = fs.createReadStream(filePath)
|
|
||||||
const imageExt = path.extname(filePath)
|
|
||||||
const imageName = rename ? Math.random().toString(36).slice(-16) : path.basename(filePath, imageExt)
|
|
||||||
const basename = `${imageName}${imageExt}`
|
|
||||||
const imageDir = path.join(targetStorage.path, 'images')
|
|
||||||
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
|
|
||||||
const outputImage = fs.createWriteStream(path.join(imageDir, basename))
|
|
||||||
inputImage.pipe(outputImage)
|
|
||||||
resolve(basename)
|
|
||||||
} catch (e) {
|
|
||||||
return reject(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = copyImage
|
|
||||||
26
browser/main/lib/dataApi/createSnippet.js
Normal file
26
browser/main/lib/dataApi/createSnippet.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import crypto from 'crypto'
|
||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet'
|
||||||
|
|
||||||
|
function createSnippet (snippetFile) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const newSnippet = {
|
||||||
|
id: crypto.randomBytes(16).toString('hex'),
|
||||||
|
name: 'Unnamed snippet',
|
||||||
|
prefix: [],
|
||||||
|
content: ''
|
||||||
|
}
|
||||||
|
fetchSnippet(null, snippetFile).then((snippets) => {
|
||||||
|
snippets.push(newSnippet)
|
||||||
|
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
resolve(newSnippet)
|
||||||
|
})
|
||||||
|
}).catch((err) => {
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = createSnippet
|
||||||
@@ -5,6 +5,7 @@ const resolveStorageNotes = require('./resolveStorageNotes')
|
|||||||
const CSON = require('@rokt33r/season')
|
const CSON = require('@rokt33r/season')
|
||||||
const sander = require('sander')
|
const sander = require('sander')
|
||||||
const { findStorage } = require('browser/lib/findStorage')
|
const { findStorage } = require('browser/lib/findStorage')
|
||||||
|
const deleteSingleNote = require('./deleteNote')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} storageKey
|
* @param {String} storageKey
|
||||||
@@ -49,11 +50,7 @@ function deleteFolder (storageKey, folderKey) {
|
|||||||
|
|
||||||
const deleteAllNotes = targetNotes
|
const deleteAllNotes = targetNotes
|
||||||
.map(function deleteNote (note) {
|
.map(function deleteNote (note) {
|
||||||
const notePath = path.join(storage.path, 'notes', note.key + '.cson')
|
return deleteSingleNote(storageKey, note.key)
|
||||||
return sander.unlink(notePath)
|
|
||||||
.catch(function (err) {
|
|
||||||
console.warn('Failed to delete', notePath, err)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
return Promise.all(deleteAllNotes)
|
return Promise.all(deleteAllNotes)
|
||||||
.then(() => storage)
|
.then(() => storage)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const resolveStorageData = require('./resolveStorageData')
|
const resolveStorageData = require('./resolveStorageData')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const sander = require('sander')
|
const sander = require('sander')
|
||||||
|
const attachmentManagement = require('./attachmentManagement')
|
||||||
const { findStorage } = require('browser/lib/findStorage')
|
const { findStorage } = require('browser/lib/findStorage')
|
||||||
|
|
||||||
function deleteNote (storageKey, noteKey) {
|
function deleteNote (storageKey, noteKey) {
|
||||||
@@ -25,6 +26,10 @@ function deleteNote (storageKey, noteKey) {
|
|||||||
storageKey
|
storageKey
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.then(function deleteAttachments (storageInfo) {
|
||||||
|
attachmentManagement.deleteAttachmentFolder(storageInfo.storageKey, storageInfo.noteKey)
|
||||||
|
return storageInfo
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = deleteNote
|
module.exports = deleteNote
|
||||||
|
|||||||
17
browser/main/lib/dataApi/deleteSnippet.js
Normal file
17
browser/main/lib/dataApi/deleteSnippet.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet'
|
||||||
|
|
||||||
|
function deleteSnippet (snippet, snippetFile) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fetchSnippet(null, snippetFile).then((snippets) => {
|
||||||
|
snippets = snippets.filter(currentSnippet => currentSnippet.id !== snippet.id)
|
||||||
|
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
resolve(snippet)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = deleteSnippet
|
||||||
@@ -1,13 +1,9 @@
|
|||||||
import copyFile from 'browser/main/lib/dataApi/copyFile'
|
import copyFile from 'browser/main/lib/dataApi/copyFile'
|
||||||
import {findStorage} from 'browser/lib/findStorage'
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
import filenamify from 'filenamify'
|
|
||||||
|
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
const LOCAL_STORED_REGEX = /!\[(.*?)]\(\s*?\/:storage\/(.*\.\S*?)\)/gi
|
|
||||||
const IMAGES_FOLDER_NAME = 'images'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export note together with images
|
* Export note together with images
|
||||||
*
|
*
|
||||||
@@ -28,21 +24,7 @@ function exportNote (storageKey, noteContent, targetPath, outputFormatter) {
|
|||||||
throw new Error('Storage path is not found')
|
throw new Error('Storage path is not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
let exportedData = noteContent.replace(LOCAL_STORED_REGEX, (match, dstFilename, srcFilename) => {
|
let exportedData = noteContent
|
||||||
dstFilename = filenamify(dstFilename, {replacement: '_'})
|
|
||||||
if (!path.extname(dstFilename)) {
|
|
||||||
dstFilename += path.extname(srcFilename)
|
|
||||||
}
|
|
||||||
|
|
||||||
const dstRelativePath = path.join(IMAGES_FOLDER_NAME, dstFilename)
|
|
||||||
|
|
||||||
exportTasks.push({
|
|
||||||
src: path.join(IMAGES_FOLDER_NAME, srcFilename),
|
|
||||||
dst: dstRelativePath
|
|
||||||
})
|
|
||||||
|
|
||||||
return ``
|
|
||||||
})
|
|
||||||
|
|
||||||
if (outputFormatter) {
|
if (outputFormatter) {
|
||||||
exportedData = outputFormatter(exportedData, exportTasks)
|
exportedData = outputFormatter(exportedData, exportTasks)
|
||||||
|
|||||||
20
browser/main/lib/dataApi/fetchSnippet.js
Normal file
20
browser/main/lib/dataApi/fetchSnippet.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
|
||||||
|
function fetchSnippet (id, snippetFile) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readFile(snippetFile || consts.SNIPPET_FILE, 'utf8', (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
const snippets = JSON.parse(data)
|
||||||
|
if (id) {
|
||||||
|
const snippet = snippets.find(snippet => { return snippet.id === id })
|
||||||
|
resolve(snippet)
|
||||||
|
}
|
||||||
|
resolve(snippets)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = fetchSnippet
|
||||||
@@ -13,6 +13,10 @@ const dataApi = {
|
|||||||
deleteNote: require('./deleteNote'),
|
deleteNote: require('./deleteNote'),
|
||||||
moveNote: require('./moveNote'),
|
moveNote: require('./moveNote'),
|
||||||
migrateFromV5Storage: require('./migrateFromV5Storage'),
|
migrateFromV5Storage: require('./migrateFromV5Storage'),
|
||||||
|
createSnippet: require('./createSnippet'),
|
||||||
|
deleteSnippet: require('./deleteSnippet'),
|
||||||
|
updateSnippet: require('./updateSnippet'),
|
||||||
|
fetchSnippet: require('./fetchSnippet'),
|
||||||
|
|
||||||
_migrateFromV6Storage: require('./migrateFromV6Storage'),
|
_migrateFromV6Storage: require('./migrateFromV6Storage'),
|
||||||
_resolveStorageData: require('./resolveStorageData'),
|
_resolveStorageData: require('./resolveStorageData'),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const CSON = require('@rokt33r/season')
|
|||||||
const keygen = require('browser/lib/keygen')
|
const keygen = require('browser/lib/keygen')
|
||||||
const sander = require('sander')
|
const sander = require('sander')
|
||||||
const { findStorage } = require('browser/lib/findStorage')
|
const { findStorage } = require('browser/lib/findStorage')
|
||||||
const copyImage = require('./copyImage')
|
const attachmentManagement = require('./attachmentManagement')
|
||||||
|
|
||||||
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
|
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
|
||||||
let oldStorage, newStorage
|
let oldStorage, newStorage
|
||||||
@@ -64,32 +64,20 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
|
|||||||
noteData.key = newNoteKey
|
noteData.key = newNoteKey
|
||||||
noteData.storage = newStorageKey
|
noteData.storage = newStorageKey
|
||||||
noteData.updatedAt = new Date()
|
noteData.updatedAt = new Date()
|
||||||
|
noteData.oldContent = noteData.content
|
||||||
|
|
||||||
return noteData
|
return noteData
|
||||||
})
|
})
|
||||||
.then(function moveImages (noteData) {
|
.then(function moveAttachments (noteData) {
|
||||||
const searchImagesRegex = /!\[.*?]\(\s*?\/:storage\/(.*\.\S*?)\)/gi
|
if (oldStorage.path === newStorage.path) {
|
||||||
let match = searchImagesRegex.exec(noteData.content)
|
return noteData
|
||||||
|
|
||||||
const moveTasks = []
|
|
||||||
while (match != null) {
|
|
||||||
const [, filename] = match
|
|
||||||
const oldPath = path.join(oldStorage.path, 'images', filename)
|
|
||||||
moveTasks.push(
|
|
||||||
copyImage(oldPath, noteData.storage, false)
|
|
||||||
.then(() => {
|
|
||||||
fs.unlinkSync(oldPath)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// find next occurence
|
|
||||||
match = searchImagesRegex.exec(noteData.content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(moveTasks).then(() => noteData)
|
noteData.content = attachmentManagement.moveAttachments(oldStorage.path, newStorage.path, noteKey, newNoteKey, noteData.content)
|
||||||
|
return noteData
|
||||||
})
|
})
|
||||||
.then(function writeAndReturn (noteData) {
|
.then(function writeAndReturn (noteData) {
|
||||||
CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage']))
|
CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage', 'oldContent']))
|
||||||
return noteData
|
return noteData
|
||||||
})
|
})
|
||||||
.then(function deleteOldNote (data) {
|
.then(function deleteOldNote (data) {
|
||||||
|
|||||||
33
browser/main/lib/dataApi/updateSnippet.js
Normal file
33
browser/main/lib/dataApi/updateSnippet.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
|
||||||
|
function updateSnippet (snippet, snippetFile) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const snippets = JSON.parse(fs.readFileSync(snippetFile || consts.SNIPPET_FILE, 'utf-8'))
|
||||||
|
|
||||||
|
for (let i = 0; i < snippets.length; i++) {
|
||||||
|
const currentSnippet = snippets[i]
|
||||||
|
|
||||||
|
if (currentSnippet.id === snippet.id) {
|
||||||
|
if (
|
||||||
|
currentSnippet.name === snippet.name &&
|
||||||
|
currentSnippet.prefix === snippet.prefix &&
|
||||||
|
currentSnippet.content === snippet.content
|
||||||
|
) {
|
||||||
|
// if everything is the same then don't write to disk
|
||||||
|
resolve(snippets)
|
||||||
|
} else {
|
||||||
|
currentSnippet.name = snippet.name
|
||||||
|
currentSnippet.prefix = snippet.prefix
|
||||||
|
currentSnippet.content = snippet.content
|
||||||
|
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
resolve(snippets)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = updateSnippet
|
||||||
7
browser/main/lib/shortcut.js
Normal file
7
browser/main/lib/shortcut.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
'toggleMode': () => {
|
||||||
|
ee.emit('topbar:togglemodebutton')
|
||||||
|
}
|
||||||
|
}
|
||||||
40
browser/main/lib/shortcutManager.js
Normal file
40
browser/main/lib/shortcutManager.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import Mousetrap from 'mousetrap'
|
||||||
|
import CM from 'browser/main/lib/ConfigManager'
|
||||||
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
import { isObjectEqual } from 'browser/lib/utils'
|
||||||
|
require('mousetrap-global-bind')
|
||||||
|
import functions from './shortcut'
|
||||||
|
|
||||||
|
let shortcuts = CM.get().hotkey
|
||||||
|
|
||||||
|
ee.on('config-renew', function () {
|
||||||
|
// only update if hotkey changed !
|
||||||
|
const newHotkey = CM.get().hotkey
|
||||||
|
if (!isObjectEqual(newHotkey, shortcuts)) {
|
||||||
|
updateShortcut(newHotkey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function updateShortcut (newHotkey) {
|
||||||
|
Mousetrap.reset()
|
||||||
|
shortcuts = newHotkey
|
||||||
|
applyShortcuts(newHotkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatShortcut (shortcut) {
|
||||||
|
return shortcut.toLowerCase().replace(/ /g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyShortcuts (shortcuts) {
|
||||||
|
for (const shortcut in shortcuts) {
|
||||||
|
const toggler = formatShortcut(shortcuts[shortcut])
|
||||||
|
// only bind if the function for that shortcut exists
|
||||||
|
if (functions[shortcut]) {
|
||||||
|
Mousetrap.bindGlobal(toggler, functions[shortcut])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyShortcuts(CM.get().hotkey)
|
||||||
|
|
||||||
|
module.exports = applyShortcuts
|
||||||
@@ -102,3 +102,29 @@ body[data-theme="solarized-dark"]
|
|||||||
|
|
||||||
.control-confirmButton
|
.control-confirmButton
|
||||||
colorSolarizedDarkPrimaryButton()
|
colorSolarizedDarkPrimaryButton()
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
modalMonokai()
|
||||||
|
width 500px
|
||||||
|
height 270px
|
||||||
|
overflow hidden
|
||||||
|
position relative
|
||||||
|
|
||||||
|
.header
|
||||||
|
background-color transparent
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.control-folder-label
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.control-folder-input
|
||||||
|
border 1px solid $ui-input--create-folder-modal
|
||||||
|
color white
|
||||||
|
|
||||||
|
.description
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.control-confirmButton
|
||||||
|
colorMonokaiPrimaryButton()
|
||||||
|
|||||||
@@ -81,3 +81,19 @@ body[data-theme="solarized-dark"]
|
|||||||
.description
|
.description
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
background-color transparent
|
||||||
|
|
||||||
|
.header
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.control-button
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
background-color transparent
|
||||||
|
&:focus
|
||||||
|
colorDarkPrimaryButton()
|
||||||
|
|
||||||
|
.description
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|||||||
@@ -133,6 +133,11 @@ colorSolarizedDarkControl()
|
|||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
colorMonokaiControl()
|
||||||
|
border none
|
||||||
|
background-color $ui-monokai-button-backgroundColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
@@ -189,4 +194,29 @@ body[data-theme="solarized-dark"]
|
|||||||
select, .group-section-control-input
|
select, .group-section-control-input
|
||||||
colorSolarizedDarkControl()
|
colorSolarizedDarkControl()
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.group-header
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
|
||||||
|
.group-header2
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.group-section-control-input
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
|
||||||
|
.group-control
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
.group-control-leftButton
|
||||||
|
colorDarkDefaultButton()
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
.group-control-rightButton
|
||||||
|
colorMonokaiPrimaryButton()
|
||||||
|
.group-hint
|
||||||
|
colorMonokaiControl()
|
||||||
|
.group-section-control
|
||||||
|
select, .group-section-control-input
|
||||||
|
colorMonokaiControl()
|
||||||
|
|||||||
@@ -11,11 +11,12 @@ p
|
|||||||
font-size 16px
|
font-size 16px
|
||||||
|
|
||||||
.cf-link
|
.cf-link
|
||||||
width 250px
|
|
||||||
height 35px
|
height 35px
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
border none
|
border none
|
||||||
background-color alpha(#1EC38B, 90%)
|
background-color alpha(#1EC38B, 90%)
|
||||||
|
padding-left 20px
|
||||||
|
padding-right 20px
|
||||||
&:hover
|
&:hover
|
||||||
background-color #1EC38B
|
background-color #1EC38B
|
||||||
transition 0.2s
|
transition 0.2s
|
||||||
@@ -33,4 +34,10 @@ body[data-theme="solarized-dark"]
|
|||||||
.root
|
.root
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
p
|
p
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
p
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|||||||
@@ -126,3 +126,26 @@ body[data-theme="solarized-dark"]
|
|||||||
|
|
||||||
.folderItem-right-dangerButton
|
.folderItem-right-dangerButton
|
||||||
colorSolarizedDarkPrimaryButton()
|
colorSolarizedDarkPrimaryButton()
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.folderItem
|
||||||
|
&:hover
|
||||||
|
background-color $ui-monokai-button-backgroundColor
|
||||||
|
|
||||||
|
.folderItem-left-danger
|
||||||
|
color $danger-color
|
||||||
|
|
||||||
|
.folderItem-left-key
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
|
.folderItem-left-colorButton
|
||||||
|
colorMonokaiPrimaryButton()
|
||||||
|
|
||||||
|
.folderItem-right-button
|
||||||
|
colorMonokaiPrimaryButton()
|
||||||
|
|
||||||
|
.folderItem-right-confirmButton
|
||||||
|
colorMonokaiPrimaryButton()
|
||||||
|
|
||||||
|
.folderItem-right-dangerButton
|
||||||
|
colorMonokaiPrimaryButton()
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ class HotkeyTab extends React.Component {
|
|||||||
handleHotkeyChange (e) {
|
handleHotkeyChange (e) {
|
||||||
const { config } = this.state
|
const { config } = this.state
|
||||||
config.hotkey = {
|
config.hotkey = {
|
||||||
toggleMain: this.refs.toggleMain.value
|
toggleMain: this.refs.toggleMain.value,
|
||||||
|
toggleMode: this.refs.toggleMode.value
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
config
|
config
|
||||||
@@ -115,6 +116,17 @@ class HotkeyTab extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>{i18n.__('Toggle editor mode')}</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input styleName='group-section-control-input'
|
||||||
|
onChange={(e) => this.handleHotkeyChange(e)}
|
||||||
|
ref='toggleMode'
|
||||||
|
value={config.hotkey.toggleMode}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div styleName='group-control'>
|
<div styleName='group-control'>
|
||||||
<button styleName='group-control-leftButton'
|
<button styleName='group-control-leftButton'
|
||||||
onClick={(e) => this.handleHintToggleButtonClick(e)}
|
onClick={(e) => this.handleHintToggleButtonClick(e)}
|
||||||
|
|||||||
@@ -68,3 +68,10 @@ body[data-theme="solarized-dark"]
|
|||||||
.list
|
.list
|
||||||
a
|
a
|
||||||
color $ui-solarized-dark-active-color
|
color $ui-solarized-dark-active-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
.list
|
||||||
|
a
|
||||||
|
color $ui-monokai-active-color
|
||||||
|
|||||||
@@ -116,3 +116,26 @@ body[data-theme="solarized-dark"]
|
|||||||
&:hover
|
&:hover
|
||||||
color white
|
color white
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
background-color transparent
|
||||||
|
.top-bar
|
||||||
|
background-color transparent
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
p
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
.nav
|
||||||
|
background-color transparent
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
.nav-button
|
||||||
|
background-color transparent
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
&:hover
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.nav-button--active
|
||||||
|
@extend .nav-button
|
||||||
|
color $ui-monokai-button--active-color
|
||||||
|
background-color $ui-monokai-button--active-backgroundColor
|
||||||
|
&:hover
|
||||||
|
color white
|
||||||
|
|||||||
90
browser/main/modals/PreferencesModal/SnippetEditor.js
Normal file
90
browser/main/modals/PreferencesModal/SnippetEditor.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import CodeMirror from 'codemirror'
|
||||||
|
import React from 'react'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import styles from './SnippetTab.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
|
||||||
|
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
||||||
|
const buildCMRulers = (rulers, enableRulers) =>
|
||||||
|
enableRulers ? rulers.map(ruler => ({ column: ruler })) : []
|
||||||
|
|
||||||
|
class SnippetEditor extends React.Component {
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.props.onRef(this)
|
||||||
|
const { rulers, enableRulers } = this.props
|
||||||
|
this.cm = CodeMirror(this.refs.root, {
|
||||||
|
rulers: buildCMRulers(rulers, enableRulers),
|
||||||
|
lineNumbers: this.props.displayLineNumbers,
|
||||||
|
lineWrapping: true,
|
||||||
|
theme: this.props.theme,
|
||||||
|
indentUnit: this.props.indentSize,
|
||||||
|
tabSize: this.props.indentSize,
|
||||||
|
indentWithTabs: this.props.indentType !== 'space',
|
||||||
|
keyMap: this.props.keyMap,
|
||||||
|
scrollPastEnd: this.props.scrollPastEnd,
|
||||||
|
dragDrop: false,
|
||||||
|
foldGutter: true,
|
||||||
|
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||||
|
autoCloseBrackets: true,
|
||||||
|
mode: 'null'
|
||||||
|
})
|
||||||
|
this.cm.setSize('100%', '100%')
|
||||||
|
let changeDelay = null
|
||||||
|
|
||||||
|
this.cm.on('change', () => {
|
||||||
|
this.snippet.content = this.cm.getValue()
|
||||||
|
|
||||||
|
clearTimeout(changeDelay)
|
||||||
|
changeDelay = setTimeout(() => {
|
||||||
|
this.saveSnippet()
|
||||||
|
}, 500)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
this.props.onRef(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
onSnippetChanged (newSnippet) {
|
||||||
|
this.snippet = newSnippet
|
||||||
|
this.cm.setValue(this.snippet.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
onSnippetNameOrPrefixChanged (newSnippet) {
|
||||||
|
this.snippet.name = newSnippet.name
|
||||||
|
this.snippet.prefix = newSnippet.prefix.toString().replace(/\s/g, '').split(',')
|
||||||
|
this.saveSnippet()
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSnippet () {
|
||||||
|
dataApi.updateSnippet(this.snippet).catch((err) => { throw err })
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { fontSize } = this.props
|
||||||
|
let fontFamily = this.props.fontFamily
|
||||||
|
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
||||||
|
? [fontFamily].concat(defaultEditorFontFamily)
|
||||||
|
: defaultEditorFontFamily
|
||||||
|
return (
|
||||||
|
<div styleName='SnippetEditor' ref='root' tabIndex='-1' style={{
|
||||||
|
fontFamily: fontFamily.join(', '),
|
||||||
|
fontSize: fontSize
|
||||||
|
}} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SnippetEditor.defaultProps = {
|
||||||
|
readOnly: false,
|
||||||
|
theme: 'xcode',
|
||||||
|
keyMap: 'sublime',
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'Monaco, Consolas',
|
||||||
|
indentSize: 4,
|
||||||
|
indentType: 'space'
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(SnippetEditor, styles)
|
||||||
87
browser/main/modals/PreferencesModal/SnippetList.js
Normal file
87
browser/main/modals/PreferencesModal/SnippetList.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styles from './SnippetTab.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
const { remote } = require('electron')
|
||||||
|
const { Menu, MenuItem } = remote
|
||||||
|
|
||||||
|
class SnippetList extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
snippets: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.reloadSnippetList()
|
||||||
|
eventEmitter.on('snippetList:reload', this.reloadSnippetList.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadSnippetList () {
|
||||||
|
dataApi.fetchSnippet().then(snippets => this.setState({snippets}))
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSnippetContextMenu (snippet) {
|
||||||
|
const menu = new Menu()
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: i18n.__('Delete snippet'),
|
||||||
|
click: () => {
|
||||||
|
this.deleteSnippet(snippet)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
menu.popup()
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSnippet (snippet) {
|
||||||
|
dataApi.deleteSnippet(snippet).then(() => {
|
||||||
|
this.reloadSnippetList()
|
||||||
|
this.props.onSnippetDeleted(snippet)
|
||||||
|
}).catch(err => { throw err })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSnippetClick (snippet) {
|
||||||
|
this.props.onSnippetClick(snippet)
|
||||||
|
}
|
||||||
|
|
||||||
|
createSnippet () {
|
||||||
|
dataApi.createSnippet().then(() => {
|
||||||
|
this.reloadSnippetList()
|
||||||
|
// scroll to end of list when added new snippet
|
||||||
|
const snippetList = document.getElementById('snippets')
|
||||||
|
snippetList.scrollTop = snippetList.scrollHeight
|
||||||
|
}).catch(err => { throw err })
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { snippets } = this.state
|
||||||
|
return (
|
||||||
|
<div styleName='snippet-list'>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<button styleName='group-control-button' onClick={() => this.createSnippet()}>
|
||||||
|
<i className='fa fa-plus' /> {i18n.__('New Snippet')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul id='snippets' styleName='snippets'>
|
||||||
|
{
|
||||||
|
snippets.map((snippet) => (
|
||||||
|
<li
|
||||||
|
styleName='snippet-item'
|
||||||
|
key={snippet.id}
|
||||||
|
onContextMenu={() => this.handleSnippetContextMenu(snippet)}
|
||||||
|
onClick={() => this.handleSnippetClick(snippet)}>
|
||||||
|
{snippet.name}
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(SnippetList, styles)
|
||||||
116
browser/main/modals/PreferencesModal/SnippetTab.js
Normal file
116
browser/main/modals/PreferencesModal/SnippetTab.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './SnippetTab.styl'
|
||||||
|
import SnippetEditor from './SnippetEditor'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
import SnippetList from './SnippetList'
|
||||||
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
|
||||||
|
class SnippetTab extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
currentSnippet: null
|
||||||
|
}
|
||||||
|
this.changeDelay = null
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSnippetNameOrPrefixChange () {
|
||||||
|
clearTimeout(this.changeDelay)
|
||||||
|
this.changeDelay = setTimeout(() => {
|
||||||
|
// notify the snippet editor that the name or prefix of snippet has been changed
|
||||||
|
this.snippetEditor.onSnippetNameOrPrefixChanged(this.state.currentSnippet)
|
||||||
|
eventEmitter.emit('snippetList:reload')
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSnippetClick (snippet) {
|
||||||
|
const { currentSnippet } = this.state
|
||||||
|
if (currentSnippet === null || currentSnippet.id !== snippet.id) {
|
||||||
|
dataApi.fetchSnippet(snippet.id).then(changedSnippet => {
|
||||||
|
// notify the snippet editor to load the content of the new snippet
|
||||||
|
this.snippetEditor.onSnippetChanged(changedSnippet)
|
||||||
|
this.setState({currentSnippet: changedSnippet})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSnippetNameOrPrefixChanged (e, type) {
|
||||||
|
const newSnippet = Object.assign({}, this.state.currentSnippet)
|
||||||
|
if (type === 'name') {
|
||||||
|
newSnippet.name = e.target.value
|
||||||
|
} else {
|
||||||
|
newSnippet.prefix = e.target.value
|
||||||
|
}
|
||||||
|
this.setState({ currentSnippet: newSnippet })
|
||||||
|
this.handleSnippetNameOrPrefixChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeleteSnippet (snippet) {
|
||||||
|
// prevent old snippet still display when deleted
|
||||||
|
if (snippet.id === this.state.currentSnippet.id) {
|
||||||
|
this.setState({currentSnippet: null})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { config, storageKey } = this.props
|
||||||
|
const { currentSnippet } = this.state
|
||||||
|
|
||||||
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
|
return (
|
||||||
|
<div styleName='root'>
|
||||||
|
<div styleName='header'>{i18n.__('Snippets')}</div>
|
||||||
|
<SnippetList
|
||||||
|
onSnippetClick={this.handleSnippetClick.bind(this)}
|
||||||
|
onSnippetDeleted={this.handleDeleteSnippet.bind(this)} />
|
||||||
|
<div styleName='snippet-detail' style={{visibility: currentSnippet ? 'visible' : 'hidden'}}>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>{i18n.__('Snippet name')}</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input
|
||||||
|
styleName='group-section-control-input'
|
||||||
|
value={currentSnippet ? currentSnippet.name : ''}
|
||||||
|
onChange={e => { this.onSnippetNameOrPrefixChanged(e, 'name') }}
|
||||||
|
type='text' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>{i18n.__('Snippet prefix')}</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input
|
||||||
|
styleName='group-section-control-input'
|
||||||
|
value={currentSnippet ? currentSnippet.prefix : ''}
|
||||||
|
onChange={e => { this.onSnippetNameOrPrefixChanged(e, 'prefix') }}
|
||||||
|
type='text' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div styleName='snippet-editor-section'>
|
||||||
|
<SnippetEditor
|
||||||
|
storageKey={storageKey}
|
||||||
|
theme={config.editor.theme}
|
||||||
|
keyMap={config.editor.keyMap}
|
||||||
|
fontFamily={config.editor.fontFamily}
|
||||||
|
fontSize={editorFontSize}
|
||||||
|
indentType={config.editor.indentType}
|
||||||
|
indentSize={editorIndentSize}
|
||||||
|
enableRulers={config.editor.enableRulers}
|
||||||
|
rulers={config.editor.rulers}
|
||||||
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
|
onRef={ref => { this.snippetEditor = ref }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SnippetTab.PropTypes = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(SnippetTab, styles)
|
||||||
180
browser/main/modals/PreferencesModal/SnippetTab.styl
Normal file
180
browser/main/modals/PreferencesModal/SnippetTab.styl
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
@import('./Tab')
|
||||||
|
@import('./ConfigTab')
|
||||||
|
|
||||||
|
.root
|
||||||
|
padding 15px
|
||||||
|
white-space pre
|
||||||
|
line-height 1.4
|
||||||
|
color alpha($ui-text-color, 90%)
|
||||||
|
width 100%
|
||||||
|
font-size 14px
|
||||||
|
|
||||||
|
.group
|
||||||
|
margin-bottom 45px
|
||||||
|
|
||||||
|
.group-header
|
||||||
|
@extend .header
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.group-header2
|
||||||
|
font-size 20px
|
||||||
|
color $ui-text-color
|
||||||
|
margin-bottom 15px
|
||||||
|
margin-top 30px
|
||||||
|
|
||||||
|
.group-section
|
||||||
|
margin-bottom 20px
|
||||||
|
display flex
|
||||||
|
line-height 30px
|
||||||
|
|
||||||
|
.group-section-label
|
||||||
|
width 150px
|
||||||
|
text-align left
|
||||||
|
margin-right 10px
|
||||||
|
font-size 14px
|
||||||
|
|
||||||
|
.group-section-control
|
||||||
|
flex 1
|
||||||
|
margin-left 5px
|
||||||
|
|
||||||
|
.group-section-control select
|
||||||
|
outline none
|
||||||
|
border 1px solid $ui-borderColor
|
||||||
|
font-size 16px
|
||||||
|
height 30px
|
||||||
|
width 250px
|
||||||
|
margin-bottom 5px
|
||||||
|
background-color transparent
|
||||||
|
|
||||||
|
.group-section-control-input
|
||||||
|
height 30px
|
||||||
|
vertical-align middle
|
||||||
|
width 400px
|
||||||
|
font-size $tab--button-font-size
|
||||||
|
border solid 1px $border-color
|
||||||
|
border-radius 2px
|
||||||
|
padding 0 5px
|
||||||
|
outline none
|
||||||
|
&:disabled
|
||||||
|
background-color $ui-input--disabled-backgroundColor
|
||||||
|
|
||||||
|
.group-control-button
|
||||||
|
height 30px
|
||||||
|
border none
|
||||||
|
border-top-right-radius 2px
|
||||||
|
border-bottom-right-radius 2px
|
||||||
|
colorPrimaryButton()
|
||||||
|
vertical-align middle
|
||||||
|
padding 0 20px
|
||||||
|
|
||||||
|
.group-checkBoxSection
|
||||||
|
margin-bottom 15px
|
||||||
|
display flex
|
||||||
|
line-height 30px
|
||||||
|
padding-left 15px
|
||||||
|
|
||||||
|
.group-control
|
||||||
|
padding-top 10px
|
||||||
|
box-sizing border-box
|
||||||
|
height 40px
|
||||||
|
text-align right
|
||||||
|
:global
|
||||||
|
.alert
|
||||||
|
display inline-block
|
||||||
|
position absolute
|
||||||
|
top 60px
|
||||||
|
right 15px
|
||||||
|
font-size 14px
|
||||||
|
.success
|
||||||
|
color #1EC38B
|
||||||
|
.error
|
||||||
|
color red
|
||||||
|
.warning
|
||||||
|
color #FFA500
|
||||||
|
|
||||||
|
.snippet-list
|
||||||
|
width 30%
|
||||||
|
height calc(100% - 200px)
|
||||||
|
position absolute
|
||||||
|
|
||||||
|
.snippets
|
||||||
|
height calc(100% - 8px)
|
||||||
|
overflow scroll
|
||||||
|
background: #f5f5f5
|
||||||
|
|
||||||
|
.snippet-item
|
||||||
|
height 50px
|
||||||
|
font-size 15px
|
||||||
|
line-height 50px
|
||||||
|
padding 0 5%
|
||||||
|
cursor pointer
|
||||||
|
position relative
|
||||||
|
|
||||||
|
&::after
|
||||||
|
width 90%
|
||||||
|
height 1px
|
||||||
|
background rgba(0, 0, 0, 0.1)
|
||||||
|
position absolute
|
||||||
|
top 100%
|
||||||
|
left 5%
|
||||||
|
content ''
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background darken(#f5f5f5, 5)
|
||||||
|
|
||||||
|
.snippet-detail
|
||||||
|
width 70%
|
||||||
|
height calc(100% - 200px)
|
||||||
|
position absolute
|
||||||
|
left 33%
|
||||||
|
|
||||||
|
.SnippetEditor
|
||||||
|
position absolute
|
||||||
|
width 100%
|
||||||
|
height 90%
|
||||||
|
|
||||||
|
body[data-theme="default"], body[data-theme="white"]
|
||||||
|
.snippets
|
||||||
|
background $ui-backgroundColor
|
||||||
|
.snippet-item
|
||||||
|
color black
|
||||||
|
&::after
|
||||||
|
background $ui-borderColor
|
||||||
|
&:hover
|
||||||
|
background darken($ui-backgroundColor, 5)
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.snippets
|
||||||
|
background $ui-dark-backgroundColor
|
||||||
|
.snippet-item
|
||||||
|
color white
|
||||||
|
&::after
|
||||||
|
background $ui-dark-borderColor
|
||||||
|
&:hover
|
||||||
|
background darken($ui-dark-backgroundColor, 5)
|
||||||
|
.snippet-detail
|
||||||
|
color white
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.snippets
|
||||||
|
background $ui-solarized-dark-backgroundColor
|
||||||
|
.snippet-item
|
||||||
|
color white
|
||||||
|
&::after
|
||||||
|
background $ui-solarized-dark-borderColor
|
||||||
|
&:hover
|
||||||
|
background darken($ui-solarized-dark-backgroundColor, 5)
|
||||||
|
.snippet-detail
|
||||||
|
color white
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.snippets
|
||||||
|
background $ui-monokai-backgroundColor
|
||||||
|
.snippet-item
|
||||||
|
color White
|
||||||
|
&::after
|
||||||
|
background $ui-monokai-borderColor
|
||||||
|
&:hover
|
||||||
|
background darken($ui-monokai-backgroundColor, 5)
|
||||||
|
.snippet-detail
|
||||||
|
color white
|
||||||
@@ -182,7 +182,7 @@ class StoragesTab extends React.Component {
|
|||||||
<div styleName='addStorage-body-section-path'>
|
<div styleName='addStorage-body-section-path'>
|
||||||
<input styleName='addStorage-body-section-path-input'
|
<input styleName='addStorage-body-section-path-input'
|
||||||
ref='addStoragePath'
|
ref='addStoragePath'
|
||||||
placeholder='Select Folder'
|
placeholder={i18n.__('Select Folder')}
|
||||||
value={this.state.newStorage.path}
|
value={this.state.newStorage.path}
|
||||||
onChange={(e) => this.handleAddStorageChange(e)}
|
onChange={(e) => this.handleAddStorageChange(e)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -199,3 +199,40 @@ body[data-theme="solarized-dark"]
|
|||||||
colorDarkDefaultButton()
|
colorDarkDefaultButton()
|
||||||
border-color $ui-solarized-dark-borderColor
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.folderList-item
|
||||||
|
border-bottom $ui-monokai-borderColor
|
||||||
|
|
||||||
|
.folderList-empty
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.list-empty
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
.list-control-addStorageButton
|
||||||
|
border-color $ui-monokai-button-backgroundColor
|
||||||
|
background-color $ui-monokai-button-backgroundColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.addStorage-header
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
|
||||||
|
.addStorage-body-section-name-input
|
||||||
|
border-color $$ui-monokai-borderColor
|
||||||
|
|
||||||
|
.addStorage-body-section-type-description
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.addStorage-body-section-path-button
|
||||||
|
colorPrimaryButton()
|
||||||
|
.addStorage-body-control
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
|
||||||
|
.addStorage-body-control-createButton
|
||||||
|
colorDarkPrimaryButton()
|
||||||
|
.addStorage-body-control-cancelButton
|
||||||
|
colorDarkDefaultButton()
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import CodeMirror from 'codemirror'
|
|||||||
import 'codemirror-mode-elixir'
|
import 'codemirror-mode-elixir'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import { getLanguages } from 'browser/lib/Languages'
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
|
||||||
@@ -27,6 +28,8 @@ class UiTab extends React.Component {
|
|||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript')
|
CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript')
|
||||||
|
CodeMirror.autoLoadMode(this.customCSSCM.getCodeMirror(), 'css')
|
||||||
|
this.customCSSCM.getCodeMirror().setSize('400px', '400px')
|
||||||
this.handleSettingDone = () => {
|
this.handleSettingDone = () => {
|
||||||
this.setState({UiAlert: {
|
this.setState({UiAlert: {
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@@ -65,6 +68,7 @@ class UiTab extends React.Component {
|
|||||||
language: this.refs.uiLanguage.value,
|
language: this.refs.uiLanguage.value,
|
||||||
showCopyNotification: this.refs.showCopyNotification.checked,
|
showCopyNotification: this.refs.showCopyNotification.checked,
|
||||||
confirmDeletion: this.refs.confirmDeletion.checked,
|
confirmDeletion: this.refs.confirmDeletion.checked,
|
||||||
|
showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked,
|
||||||
disableDirectWrite: this.refs.uiD2w != null
|
disableDirectWrite: this.refs.uiD2w != null
|
||||||
? this.refs.uiD2w.checked
|
? this.refs.uiD2w.checked
|
||||||
: false
|
: false
|
||||||
@@ -92,9 +96,14 @@ class UiTab extends React.Component {
|
|||||||
latexInlineClose: this.refs.previewLatexInlineClose.value,
|
latexInlineClose: this.refs.previewLatexInlineClose.value,
|
||||||
latexBlockOpen: this.refs.previewLatexBlockOpen.value,
|
latexBlockOpen: this.refs.previewLatexBlockOpen.value,
|
||||||
latexBlockClose: this.refs.previewLatexBlockClose.value,
|
latexBlockClose: this.refs.previewLatexBlockClose.value,
|
||||||
|
plantUMLServerAddress: this.refs.previewPlantUMLServerAddress.value,
|
||||||
scrollPastEnd: this.refs.previewScrollPastEnd.checked,
|
scrollPastEnd: this.refs.previewScrollPastEnd.checked,
|
||||||
smartQuotes: this.refs.previewSmartQuotes.checked,
|
smartQuotes: this.refs.previewSmartQuotes.checked,
|
||||||
sanitize: this.refs.previewSanitize.value
|
breaks: this.refs.previewBreaks.checked,
|
||||||
|
smartArrows: this.refs.previewSmartArrows.checked,
|
||||||
|
sanitize: this.refs.previewSanitize.value,
|
||||||
|
allowCustomCSS: this.refs.previewAllowCustomCSS.checked,
|
||||||
|
customCSS: this.customCSSCM.getCodeMirror().getValue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +164,7 @@ class UiTab extends React.Component {
|
|||||||
const { config, codemirrorTheme } = this.state
|
const { config, codemirrorTheme } = this.state
|
||||||
const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};'
|
const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};'
|
||||||
const enableEditRulersStyle = config.editor.enableRulers ? 'block' : 'none'
|
const enableEditRulersStyle = config.editor.enableRulers ? 'block' : 'none'
|
||||||
|
const customCSS = config.preview.customCSS
|
||||||
return (
|
return (
|
||||||
<div styleName='root'>
|
<div styleName='root'>
|
||||||
<div styleName='group'>
|
<div styleName='group'>
|
||||||
@@ -170,6 +180,7 @@ class UiTab extends React.Component {
|
|||||||
<option value='default'>{i18n.__('Default')}</option>
|
<option value='default'>{i18n.__('Default')}</option>
|
||||||
<option value='white'>{i18n.__('White')}</option>
|
<option value='white'>{i18n.__('White')}</option>
|
||||||
<option value='solarized-dark'>{i18n.__('Solarized Dark')}</option>
|
<option value='solarized-dark'>{i18n.__('Solarized Dark')}</option>
|
||||||
|
<option value='monokai'>{i18n.__('Monokai')}</option>
|
||||||
<option value='dark'>{i18n.__('Dark')}</option>
|
<option value='dark'>{i18n.__('Dark')}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -182,21 +193,9 @@ class UiTab extends React.Component {
|
|||||||
onChange={(e) => this.handleUIChange(e)}
|
onChange={(e) => this.handleUIChange(e)}
|
||||||
ref='uiLanguage'
|
ref='uiLanguage'
|
||||||
>
|
>
|
||||||
<option value='sq'>{i18n.__('Albanian')}</option>
|
{
|
||||||
<option value='zh-CN'>{i18n.__('Chinese (zh-CN)')}</option>
|
getLanguages().map((language) => <option value={language.locale} key={language.locale}>{i18n.__(language.name)}</option>)
|
||||||
<option value='zh-TW'>{i18n.__('Chinese (zh-TW)')}</option>
|
}
|
||||||
<option value='da'>{i18n.__('Danish')}</option>
|
|
||||||
<option value='en'>{i18n.__('English')}</option>
|
|
||||||
<option value='fr'>{i18n.__('French')}</option>
|
|
||||||
<option value='de'>{i18n.__('German')}</option>
|
|
||||||
<option value='hu'>{i18n.__('Hungarian')}</option>
|
|
||||||
<option value='ja'>{i18n.__('Japanese')}</option>
|
|
||||||
<option value='ko'>{i18n.__('Korean')}</option>
|
|
||||||
<option value='no'>{i18n.__('Norwegian')}</option>
|
|
||||||
<option value='pl'>{i18n.__('Polish')}</option>
|
|
||||||
<option value='pt'>{i18n.__('Portuguese')}</option>
|
|
||||||
<option value='ru'>{i18n.__('Russian')}</option>
|
|
||||||
<option value='es'>{i18n.__('Spanish')}</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -221,6 +220,16 @@ class UiTab extends React.Component {
|
|||||||
{i18n.__('Show a confirmation dialog when deleting notes')}
|
{i18n.__('Show a confirmation dialog when deleting notes')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.ui.showOnlyRelatedTags}
|
||||||
|
ref='showOnlyRelatedTags'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('Show only related tags')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
{
|
{
|
||||||
global.process.platform === 'win32'
|
global.process.platform === 'win32'
|
||||||
? <div styleName='group-checkBoxSection'>
|
? <div styleName='group-checkBoxSection'>
|
||||||
@@ -231,7 +240,7 @@ class UiTab extends React.Component {
|
|||||||
disabled={OSX}
|
disabled={OSX}
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
/>
|
/>
|
||||||
Disable Direct Write(It will be applied after restarting)
|
{i18n.__('Disable Direct Write (It will be applied after restarting)')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
@@ -471,7 +480,27 @@ class UiTab extends React.Component {
|
|||||||
ref='previewSmartQuotes'
|
ref='previewSmartQuotes'
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
/>
|
/>
|
||||||
Enable smart quotes
|
{i18n.__('Enable smart quotes')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.preview.breaks}
|
||||||
|
ref='previewBreaks'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('Render newlines in Markdown paragraphs as <br>')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.preview.smartArrows}
|
||||||
|
ref='previewSmartArrows'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -543,6 +572,33 @@ class UiTab extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>
|
||||||
|
{i18n.__('PlantUML Server')}
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input styleName='group-section-control-input'
|
||||||
|
ref='previewPlantUMLServerAddress'
|
||||||
|
value={config.preview.plantUMLServerAddress}
|
||||||
|
onChange={(e) => this.handleUIChange(e)}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>
|
||||||
|
{i18n.__('Custom CSS')}
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={config.preview.allowCustomCSS}
|
||||||
|
ref='previewAllowCustomCSS'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('Allow custom CSS for preview')}
|
||||||
|
<ReactCodeMirror onChange={e => this.handleUIChange(e)} ref={e => (this.customCSSCM = e)} value={config.preview.customCSS} options={{ lineNumbers: true, mode: 'css', theme: codemirrorTheme }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div styleName='group-control'>
|
<div styleName='group-control'>
|
||||||
<button styleName='group-control-rightButton'
|
<button styleName='group-control-rightButton'
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import UiTab from './UiTab'
|
|||||||
import InfoTab from './InfoTab'
|
import InfoTab from './InfoTab'
|
||||||
import Crowdfunding from './Crowdfunding'
|
import Crowdfunding from './Crowdfunding'
|
||||||
import StoragesTab from './StoragesTab'
|
import StoragesTab from './StoragesTab'
|
||||||
|
import SnippetTab from './SnippetTab'
|
||||||
import Blog from './Blog'
|
import Blog from './Blog'
|
||||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
@@ -86,6 +87,14 @@ class Preferences extends React.Component {
|
|||||||
haveToSave={alert => this.setState({BlogAlert: alert})}
|
haveToSave={alert => this.setState({BlogAlert: alert})}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
case 'SNIPPET':
|
||||||
|
return (
|
||||||
|
<SnippetTab
|
||||||
|
dispatch={dispatch}
|
||||||
|
config={config}
|
||||||
|
data={data}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case 'STORAGES':
|
case 'STORAGES':
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
@@ -123,7 +132,8 @@ class Preferences extends React.Component {
|
|||||||
{target: 'UI', label: i18n.__('Interface'), UI: this.state.UIAlert},
|
{target: 'UI', label: i18n.__('Interface'), UI: this.state.UIAlert},
|
||||||
{target: 'INFO', label: i18n.__('About')},
|
{target: 'INFO', label: i18n.__('About')},
|
||||||
{target: 'CROWDFUNDING', label: i18n.__('Crowdfunding')},
|
{target: 'CROWDFUNDING', label: i18n.__('Crowdfunding')},
|
||||||
{target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert}
|
{target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert},
|
||||||
|
{target: 'SNIPPET', label: i18n.__('Snippets')}
|
||||||
]
|
]
|
||||||
|
|
||||||
const navButtons = tabs.map((tab) => {
|
const navButtons = tabs.map((tab) => {
|
||||||
|
|||||||
@@ -38,29 +38,13 @@ function data (state = defaultDataMap(), action) {
|
|||||||
if (note.isTrashed) {
|
if (note.isTrashed) {
|
||||||
state.trashedSet.add(uniqueKey)
|
state.trashedSet.add(uniqueKey)
|
||||||
}
|
}
|
||||||
|
const storageNoteList = getOrInitItem(state.storageNoteMap, note.storage)
|
||||||
let storageNoteList = state.storageNoteMap.get(note.storage)
|
|
||||||
if (storageNoteList == null) {
|
|
||||||
storageNoteList = new Set(storageNoteList)
|
|
||||||
state.storageNoteMap.set(note.storage, storageNoteList)
|
|
||||||
}
|
|
||||||
storageNoteList.add(uniqueKey)
|
storageNoteList.add(uniqueKey)
|
||||||
|
|
||||||
let folderNoteSet = state.folderNoteMap.get(folderKey)
|
const folderNoteSet = getOrInitItem(state.folderNoteMap, folderKey)
|
||||||
if (folderNoteSet == null) {
|
|
||||||
folderNoteSet = new Set(folderNoteSet)
|
|
||||||
state.folderNoteMap.set(folderKey, folderNoteSet)
|
|
||||||
}
|
|
||||||
folderNoteSet.add(uniqueKey)
|
folderNoteSet.add(uniqueKey)
|
||||||
|
|
||||||
note.tags.forEach((tag) => {
|
assignToTags(note.tags, state, uniqueKey)
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
if (tagNoteList == null) {
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
}
|
|
||||||
tagNoteList.add(uniqueKey)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
return state
|
return state
|
||||||
case 'UPDATE_NOTE':
|
case 'UPDATE_NOTE':
|
||||||
@@ -74,23 +58,19 @@ function data (state = defaultDataMap(), action) {
|
|||||||
state.noteMap = new Map(state.noteMap)
|
state.noteMap = new Map(state.noteMap)
|
||||||
state.noteMap.set(uniqueKey, note)
|
state.noteMap.set(uniqueKey, note)
|
||||||
|
|
||||||
if (oldNote == null || oldNote.isStarred !== note.isStarred) {
|
updateStarredChange(oldNote, note, state, uniqueKey)
|
||||||
state.starredSet = new Set(state.starredSet)
|
|
||||||
if (note.isStarred) {
|
|
||||||
state.starredSet.add(uniqueKey)
|
|
||||||
} else {
|
|
||||||
state.starredSet.delete(uniqueKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
|
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
|
||||||
state.trashedSet = new Set(state.trashedSet)
|
state.trashedSet = new Set(state.trashedSet)
|
||||||
if (note.isTrashed) {
|
if (note.isTrashed) {
|
||||||
state.trashedSet.add(uniqueKey)
|
state.trashedSet.add(uniqueKey)
|
||||||
state.starredSet.delete(uniqueKey)
|
state.starredSet.delete(uniqueKey)
|
||||||
|
removeFromTags(note.tags, state, uniqueKey)
|
||||||
} else {
|
} else {
|
||||||
state.trashedSet.delete(uniqueKey)
|
state.trashedSet.delete(uniqueKey)
|
||||||
|
|
||||||
|
assignToTags(note.tags, state, uniqueKey)
|
||||||
|
|
||||||
if (note.isStarred) {
|
if (note.isStarred) {
|
||||||
state.starredSet.add(uniqueKey)
|
state.starredSet.add(uniqueKey)
|
||||||
}
|
}
|
||||||
@@ -107,54 +87,12 @@ function data (state = defaultDataMap(), action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update foldermap if folder changed or post created
|
// Update foldermap if folder changed or post created
|
||||||
if (oldNote == null || oldNote.folder !== note.folder) {
|
updateFolderChange(oldNote, note, state, folderKey, uniqueKey)
|
||||||
state.folderNoteMap = new Map(state.folderNoteMap)
|
|
||||||
let folderNoteSet = state.folderNoteMap.get(folderKey)
|
|
||||||
folderNoteSet = new Set(folderNoteSet)
|
|
||||||
folderNoteSet.add(uniqueKey)
|
|
||||||
state.folderNoteMap.set(folderKey, folderNoteSet)
|
|
||||||
|
|
||||||
if (oldNote != null) {
|
|
||||||
const oldFolderKey = oldNote.storage + '-' + oldNote.folder
|
|
||||||
let oldFolderNoteList = state.folderNoteMap.get(oldFolderKey)
|
|
||||||
oldFolderNoteList = new Set(oldFolderNoteList)
|
|
||||||
oldFolderNoteList.delete(uniqueKey)
|
|
||||||
state.folderNoteMap.set(oldFolderKey, oldFolderNoteList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldNote != null) {
|
if (oldNote != null) {
|
||||||
const discardedTags = _.difference(oldNote.tags, note.tags)
|
updateTagChanges(oldNote, note, state, uniqueKey)
|
||||||
const addedTags = _.difference(note.tags, oldNote.tags)
|
|
||||||
if (discardedTags.length + addedTags.length > 0) {
|
|
||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
|
||||||
|
|
||||||
discardedTags.forEach((tag) => {
|
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
if (tagNoteList != null) {
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
tagNoteList.delete(uniqueKey)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
addedTags.forEach((tag) => {
|
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
tagNoteList.add(uniqueKey)
|
|
||||||
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
assignToTags(note.tags, state, uniqueKey)
|
||||||
note.tags.forEach((tag) => {
|
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
if (tagNoteList == null) {
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
}
|
|
||||||
tagNoteList.add(uniqueKey)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return state
|
return state
|
||||||
@@ -202,26 +140,10 @@ function data (state = defaultDataMap(), action) {
|
|||||||
originFolderList.delete(originKey)
|
originFolderList.delete(originKey)
|
||||||
state.folderNoteMap.set(originFolderKey, originFolderList)
|
state.folderNoteMap.set(originFolderKey, originFolderList)
|
||||||
|
|
||||||
// From tagMap
|
removeFromTags(originNote.tags, state, originKey)
|
||||||
if (originNote.tags.length > 0) {
|
|
||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
|
||||||
originNote.tags.forEach((tag) => {
|
|
||||||
let noteSet = state.tagNoteMap.get(tag)
|
|
||||||
noteSet = new Set(noteSet)
|
|
||||||
noteSet.delete(originKey)
|
|
||||||
state.tagNoteMap.set(tag, noteSet)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldNote == null || oldNote.isStarred !== note.isStarred) {
|
updateStarredChange(oldNote, note, state, uniqueKey)
|
||||||
state.starredSet = new Set(state.starredSet)
|
|
||||||
if (note.isStarred) {
|
|
||||||
state.starredSet.add(uniqueKey)
|
|
||||||
} else {
|
|
||||||
state.starredSet.delete(uniqueKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
|
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
|
||||||
state.trashedSet = new Set(state.trashedSet)
|
state.trashedSet = new Set(state.trashedSet)
|
||||||
@@ -238,59 +160,17 @@ function data (state = defaultDataMap(), action) {
|
|||||||
let noteSet = state.storageNoteMap.get(note.storage)
|
let noteSet = state.storageNoteMap.get(note.storage)
|
||||||
noteSet = new Set(noteSet)
|
noteSet = new Set(noteSet)
|
||||||
noteSet.add(uniqueKey)
|
noteSet.add(uniqueKey)
|
||||||
state.folderNoteMap.set(folderKey, noteSet)
|
state.storageNoteMap.set(folderKey, noteSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update foldermap if folder changed or post created
|
// Update foldermap if folder changed or post created
|
||||||
if (oldNote == null || oldNote.folder !== note.folder) {
|
updateFolderChange(oldNote, note, state, folderKey, uniqueKey)
|
||||||
state.folderNoteMap = new Map(state.folderNoteMap)
|
|
||||||
let folderNoteList = state.folderNoteMap.get(folderKey)
|
|
||||||
folderNoteList = new Set(folderNoteList)
|
|
||||||
folderNoteList.add(uniqueKey)
|
|
||||||
state.folderNoteMap.set(folderKey, folderNoteList)
|
|
||||||
|
|
||||||
if (oldNote != null) {
|
|
||||||
const oldFolderKey = oldNote.storage + '-' + oldNote.folder
|
|
||||||
let oldFolderNoteList = state.folderNoteMap.get(oldFolderKey)
|
|
||||||
oldFolderNoteList = new Set(oldFolderNoteList)
|
|
||||||
oldFolderNoteList.delete(uniqueKey)
|
|
||||||
state.folderNoteMap.set(oldFolderKey, oldFolderNoteList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from old folder map
|
// Remove from old folder map
|
||||||
if (oldNote != null) {
|
if (oldNote != null) {
|
||||||
const discardedTags = _.difference(oldNote.tags, note.tags)
|
updateTagChanges(oldNote, note, state, uniqueKey)
|
||||||
const addedTags = _.difference(note.tags, oldNote.tags)
|
|
||||||
if (discardedTags.length + addedTags.length > 0) {
|
|
||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
|
||||||
|
|
||||||
discardedTags.forEach((tag) => {
|
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
if (tagNoteList != null) {
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
tagNoteList.delete(uniqueKey)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
addedTags.forEach((tag) => {
|
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
tagNoteList.add(uniqueKey)
|
|
||||||
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
assignToTags(note.tags, state, uniqueKey)
|
||||||
note.tags.forEach((tag) => {
|
|
||||||
let tagNoteList = state.tagNoteMap.get(tag)
|
|
||||||
if (tagNoteList == null) {
|
|
||||||
tagNoteList = new Set(tagNoteList)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteList)
|
|
||||||
}
|
|
||||||
tagNoteList.add(uniqueKey)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return state
|
return state
|
||||||
@@ -329,16 +209,7 @@ function data (state = defaultDataMap(), action) {
|
|||||||
folderSet.delete(uniqueKey)
|
folderSet.delete(uniqueKey)
|
||||||
state.folderNoteMap.set(folderKey, folderSet)
|
state.folderNoteMap.set(folderKey, folderSet)
|
||||||
|
|
||||||
// From tagMap
|
removeFromTags(targetNote.tags, state, uniqueKey)
|
||||||
if (targetNote.tags.length > 0) {
|
|
||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
|
||||||
targetNote.tags.forEach((tag) => {
|
|
||||||
let noteSet = state.tagNoteMap.get(tag)
|
|
||||||
noteSet = new Set(noteSet)
|
|
||||||
noteSet.delete(uniqueKey)
|
|
||||||
state.tagNoteMap.set(tag, noteSet)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
state.noteMap = new Map(state.noteMap)
|
state.noteMap = new Map(state.noteMap)
|
||||||
state.noteMap.delete(uniqueKey)
|
state.noteMap.delete(uniqueKey)
|
||||||
@@ -402,9 +273,7 @@ function data (state = defaultDataMap(), action) {
|
|||||||
// Delete key from tag map
|
// Delete key from tag map
|
||||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||||
note.tags.forEach((tag) => {
|
note.tags.forEach((tag) => {
|
||||||
let tagNoteSet = state.tagNoteMap.get(tag)
|
const tagNoteSet = getOrInitItem(state.tagNoteMap, tag)
|
||||||
tagNoteSet = new Set(tagNoteSet)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteSet)
|
|
||||||
tagNoteSet.delete(noteKey)
|
tagNoteSet.delete(noteKey)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -431,11 +300,7 @@ function data (state = defaultDataMap(), action) {
|
|||||||
state.starredSet.add(uniqueKey)
|
state.starredSet.add(uniqueKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
let storageNoteList = state.storageNoteMap.get(note.storage)
|
const storageNoteList = getOrInitItem(state.tagNoteMap, note.storage)
|
||||||
if (storageNoteList == null) {
|
|
||||||
storageNoteList = new Set(storageNoteList)
|
|
||||||
state.storageNoteMap.set(note.storage, storageNoteList)
|
|
||||||
}
|
|
||||||
storageNoteList.add(uniqueKey)
|
storageNoteList.add(uniqueKey)
|
||||||
|
|
||||||
let folderNoteSet = state.folderNoteMap.get(folderKey)
|
let folderNoteSet = state.folderNoteMap.get(folderKey)
|
||||||
@@ -446,11 +311,7 @@ function data (state = defaultDataMap(), action) {
|
|||||||
folderNoteSet.add(uniqueKey)
|
folderNoteSet.add(uniqueKey)
|
||||||
|
|
||||||
note.tags.forEach((tag) => {
|
note.tags.forEach((tag) => {
|
||||||
let tagNoteSet = state.tagNoteMap.get(tag)
|
const tagNoteSet = getOrInitItem(state.tagNoteMap, tag)
|
||||||
if (tagNoteSet == null) {
|
|
||||||
tagNoteSet = new Set(tagNoteSet)
|
|
||||||
state.tagNoteMap.set(tag, tagNoteSet)
|
|
||||||
}
|
|
||||||
tagNoteSet.add(uniqueKey)
|
tagNoteSet.add(uniqueKey)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -541,6 +402,73 @@ function status (state = defaultStatus, action) {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateStarredChange (oldNote, note, state, uniqueKey) {
|
||||||
|
if (oldNote == null || oldNote.isStarred !== note.isStarred) {
|
||||||
|
state.starredSet = new Set(state.starredSet)
|
||||||
|
if (note.isStarred) {
|
||||||
|
state.starredSet.add(uniqueKey)
|
||||||
|
} else {
|
||||||
|
state.starredSet.delete(uniqueKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFolderChange (oldNote, note, state, folderKey, uniqueKey) {
|
||||||
|
if (oldNote == null || oldNote.folder !== note.folder) {
|
||||||
|
state.folderNoteMap = new Map(state.folderNoteMap)
|
||||||
|
let folderNoteList = state.folderNoteMap.get(folderKey)
|
||||||
|
folderNoteList = new Set(folderNoteList)
|
||||||
|
folderNoteList.add(uniqueKey)
|
||||||
|
state.folderNoteMap.set(folderKey, folderNoteList)
|
||||||
|
|
||||||
|
if (oldNote != null) {
|
||||||
|
const oldFolderKey = oldNote.storage + '-' + oldNote.folder
|
||||||
|
let oldFolderNoteList = state.folderNoteMap.get(oldFolderKey)
|
||||||
|
oldFolderNoteList = new Set(oldFolderNoteList)
|
||||||
|
oldFolderNoteList.delete(uniqueKey)
|
||||||
|
state.folderNoteMap.set(oldFolderKey, oldFolderNoteList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTagChanges (oldNote, note, state, uniqueKey) {
|
||||||
|
const discardedTags = _.difference(oldNote.tags, note.tags)
|
||||||
|
const addedTags = _.difference(note.tags, oldNote.tags)
|
||||||
|
if (discardedTags.length + addedTags.length > 0) {
|
||||||
|
removeFromTags(discardedTags, state, uniqueKey)
|
||||||
|
assignToTags(addedTags, state, uniqueKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignToTags (tags, state, uniqueKey) {
|
||||||
|
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||||
|
tags.forEach((tag) => {
|
||||||
|
const tagNoteList = getOrInitItem(state.tagNoteMap, tag)
|
||||||
|
tagNoteList.add(uniqueKey)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFromTags (tags, state, uniqueKey) {
|
||||||
|
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||||
|
tags.forEach(tag => {
|
||||||
|
let tagNoteList = state.tagNoteMap.get(tag)
|
||||||
|
if (tagNoteList != null) {
|
||||||
|
tagNoteList = new Set(tagNoteList)
|
||||||
|
tagNoteList.delete(uniqueKey)
|
||||||
|
state.tagNoteMap.set(tag, tagNoteList)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOrInitItem (target, key) {
|
||||||
|
let results = target.get(key)
|
||||||
|
if (results == null) {
|
||||||
|
results = new Set()
|
||||||
|
target.set(key, results)
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
const reducer = combineReducers({
|
const reducer = combineReducers({
|
||||||
data,
|
data,
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -118,6 +118,16 @@ colorSolarizedDarkPrimaryButton()
|
|||||||
&:active:hover
|
&:active:hover
|
||||||
background-color $dark-primary-button-background--active
|
background-color $dark-primary-button-background--active
|
||||||
|
|
||||||
|
colorMonokaiPrimaryButton()
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
background-color $ui-monokai-button-backgroundColor
|
||||||
|
border none
|
||||||
|
&:hover
|
||||||
|
background-color $dark-primary-button-background--hover
|
||||||
|
&:active
|
||||||
|
&:active:hover
|
||||||
|
background-color $dark-primary-button-background--active
|
||||||
|
|
||||||
|
|
||||||
// Danger button(Brand color)
|
// Danger button(Brand color)
|
||||||
$danger-button-background = #c9302c
|
$danger-button-background = #c9302c
|
||||||
@@ -348,3 +358,29 @@ modalSolarizedDark()
|
|||||||
background-color $ui-solarized-dark-backgroundColor
|
background-color $ui-solarized-dark-backgroundColor
|
||||||
overflow hidden
|
overflow hidden
|
||||||
border-radius $modal-border-radius
|
border-radius $modal-border-radius
|
||||||
|
|
||||||
|
/******* Monokai theme ********/
|
||||||
|
$ui-monokai-backgroundColor = #272822
|
||||||
|
$ui-monokai-noteList-backgroundColor = #272822
|
||||||
|
$ui-monokai-noteDetail-backgroundColor = #272822
|
||||||
|
|
||||||
|
$ui-monokai-text-color = #f8f8f2
|
||||||
|
$ui-monokai-active-color = #f92672
|
||||||
|
|
||||||
|
$ui-monokai-borderColor = #373831
|
||||||
|
|
||||||
|
$ui-monokai-tag-backgroundColor = #f92672
|
||||||
|
|
||||||
|
$ui-monokai-button-backgroundColor = #373831
|
||||||
|
$ui-monokai-button--active-color = white
|
||||||
|
$ui-monokai-button--active-backgroundColor = #f92672
|
||||||
|
$ui-monokai-button--hover-backgroundColor = lighten($ui-dark-backgroundColor, 10%)
|
||||||
|
$ui-monokai-button--focus-borderColor = lighten(#369DCD, 25%)
|
||||||
|
|
||||||
|
modalmonokai()
|
||||||
|
position relative
|
||||||
|
z-index $modal-z-index
|
||||||
|
width 100%
|
||||||
|
background-color $ui-monokai-backgroundColor
|
||||||
|
overflow hidden
|
||||||
|
border-radius $modal-border-radius
|
||||||
@@ -90,7 +90,7 @@ app.on('ready', function () {
|
|||||||
mainWindow.setMenu(menu)
|
mainWindow.setMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check update every hour
|
// Check update every day
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
checkUpdate()
|
checkUpdate()
|
||||||
}, 1000 * 60 * 60 * 24)
|
}, 1000 * 60 * 60 * 24)
|
||||||
@@ -106,7 +106,7 @@ app.on('ready', function () {
|
|||||||
checkUpdate()
|
checkUpdate()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, 10000)
|
}, 10 * 1000)
|
||||||
ipcServer = require('./ipcServer')
|
ipcServer = require('./ipcServer')
|
||||||
ipcServer.server.start()
|
ipcServer.server.start()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -252,10 +252,27 @@ const view = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Focus Search',
|
label: 'Focus Search',
|
||||||
accelerator: 'Control+S',
|
accelerator: 'CommandOrControl+Shift+L',
|
||||||
click () {
|
click () {
|
||||||
mainWindow.webContents.send('top:focus-search')
|
mainWindow.webContents.send('top:focus-search')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Toggle Full Screen',
|
||||||
|
accelerator: macOS ? 'Command+Control+F' : 'F11',
|
||||||
|
click () {
|
||||||
|
mainWindow.setFullScreen(!mainWindow.isFullScreen())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'zoomin',
|
||||||
|
accelerator: macOS ? 'CommandOrControl+Plus' : 'Control+='
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'zoomout'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -265,7 +282,7 @@ let editorFocused
|
|||||||
// Define extra shortcut keys
|
// Define extra shortcut keys
|
||||||
mainWindow.webContents.on('before-input-event', (event, input) => {
|
mainWindow.webContents.on('before-input-event', (event, input) => {
|
||||||
// Synonyms for Search (Find)
|
// Synonyms for Search (Find)
|
||||||
if (input.control && input.key === 'f' && input.type === 'keyDown') {
|
if (input.control && input.key === 'l' && input.type === 'keyDown') {
|
||||||
if (!editorFocused) {
|
if (!editorFocused) {
|
||||||
mainWindow.webContents.send('top:focus-search')
|
mainWindow.webContents.send('top:focus-search')
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -285,11 +302,6 @@ const window = {
|
|||||||
accelerator: 'Command+M',
|
accelerator: 'Command+M',
|
||||||
selector: 'performMiniaturize:'
|
selector: 'performMiniaturize:'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Toggle Full Screen',
|
|
||||||
accelerator: 'Command+Control+F',
|
|
||||||
selector: 'toggleFullScreen:'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Close',
|
label: 'Close',
|
||||||
accelerator: 'Command+W',
|
accelerator: 'Command+W',
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const mainWindow = new BrowserWindow({
|
|||||||
autoHideMenuBar: showMenu,
|
autoHideMenuBar: showMenu,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
zoomFactor: 1.0,
|
zoomFactor: 1.0,
|
||||||
blinkFeatures: 'OverlayScrollbars'
|
enableBlinkFeatures: 'OverlayScrollbars'
|
||||||
},
|
},
|
||||||
icon: path.resolve(__dirname, '../resources/app.png')
|
icon: path.resolve(__dirname, '../resources/app.png')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -77,6 +77,7 @@
|
|||||||
<script src="../node_modules/codemirror/addon/mode/overlay.js"></script>
|
<script src="../node_modules/codemirror/addon/mode/overlay.js"></script>
|
||||||
<script src="../node_modules/codemirror/addon/mode/loadmode.js"></script>
|
<script src="../node_modules/codemirror/addon/mode/loadmode.js"></script>
|
||||||
<script src="../node_modules/codemirror/addon/mode/simple.js"></script>
|
<script src="../node_modules/codemirror/addon/mode/simple.js"></script>
|
||||||
|
<script src="../node_modules/codemirror/addon/mode/multiplex.js"></script>
|
||||||
<script src="../node_modules/codemirror/keymap/sublime.js"></script>
|
<script src="../node_modules/codemirror/keymap/sublime.js"></script>
|
||||||
<script src="../node_modules/codemirror/keymap/vim.js"></script>
|
<script src="../node_modules/codemirror/keymap/vim.js"></script>
|
||||||
<script src="../node_modules/codemirror/keymap/emacs.js"></script>
|
<script src="../node_modules/codemirror/keymap/emacs.js"></script>
|
||||||
@@ -114,7 +115,7 @@
|
|||||||
<script src="../node_modules/react-redux/dist/react-redux.min.js"></script>
|
<script src="../node_modules/react-redux/dist/react-redux.min.js"></script>
|
||||||
<script type='text/javascript'>
|
<script type='text/javascript'>
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
electron.webFrame.setZoomLevelLimits(1, 1)
|
electron.webFrame.setVisualZoomLevelLimits(1, 1)
|
||||||
var scriptUrl = window._.find(electron.remote.process.argv, (a) => a === '--hot')
|
var scriptUrl = window._.find(electron.remote.process.argv, (a) => a === '--hot')
|
||||||
? 'http://localhost:8080/assets/main.js'
|
? 'http://localhost:8080/assets/main.js'
|
||||||
: '../compiled/main.js'
|
: '../compiled/main.js'
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Community",
|
"Community": "Community",
|
||||||
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
@@ -149,5 +150,7 @@
|
|||||||
"Sanitization": "Sanitization",
|
"Sanitization": "Sanitization",
|
||||||
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
||||||
"Allow styles": "Allow styles",
|
"Allow styles": "Allow styles",
|
||||||
"Allow dangerous html tags": "Allow dangerous html tags"
|
"Allow dangerous html tags": "Allow dangerous html tags",
|
||||||
|
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
|
||||||
|
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Ende Kennzeichen",
|
"LaTeX Inline Close Delimiter": "LaTeX Inline Ende Kennzeichen",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX Block Beginn Kennzeichen",
|
"LaTeX Block Open Delimiter": "LaTeX Block Beginn Kennzeichen",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX Block Ende Kennzeichen",
|
"LaTeX Block Close Delimiter": "LaTeX Block Ende Kennzeichen",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Community",
|
"Community": "Community",
|
||||||
"Subscribe to Newsletter": "Newsletter abonnieren",
|
"Subscribe to Newsletter": "Newsletter abonnieren",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
@@ -204,5 +205,7 @@
|
|||||||
"Unnamed": "Unbenannt",
|
"Unnamed": "Unbenannt",
|
||||||
"Rename": "Umbenennen",
|
"Rename": "Umbenennen",
|
||||||
"Folder Name": "Ordnername",
|
"Folder Name": "Ordnername",
|
||||||
"No tags": "Keine Tags"
|
"No tags": "Keine Tags",
|
||||||
|
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
|
||||||
|
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
"Preferences": "Preferences",
|
"Preferences": "Preferences",
|
||||||
"Make a note": "Make a note",
|
"Make a note": "Make a note",
|
||||||
"Ctrl": "Ctrl",
|
"Ctrl": "Ctrl",
|
||||||
"Ctrl(^)": "Ctrl",
|
"Ctrl(^)": "Ctrl(^)",
|
||||||
"to create a new note": "to create a new note",
|
"to create a new note": "to create a new note",
|
||||||
"Toggle Mode": "Toggle Mode",
|
"Toggle Mode": "Toggle Mode",
|
||||||
|
"Add tag...": "Add tag...",
|
||||||
"Trash": "Trash",
|
"Trash": "Trash",
|
||||||
"MODIFICATION DATE": "MODIFICATION DATE",
|
"MODIFICATION DATE": "MODIFICATION DATE",
|
||||||
"Words": "Words",
|
"Words": "Words",
|
||||||
@@ -20,9 +21,12 @@
|
|||||||
".html": ".html",
|
".html": ".html",
|
||||||
"Print": "Print",
|
"Print": "Print",
|
||||||
"Your preferences for Boostnote": "Your preferences for Boostnote",
|
"Your preferences for Boostnote": "Your preferences for Boostnote",
|
||||||
|
"Help": "Help",
|
||||||
|
"Hide Help": "Hide Help",
|
||||||
"Storages": "Storages",
|
"Storages": "Storages",
|
||||||
"Add Storage Location": "Add Storage Location",
|
"Add Storage Location": "Add Storage Location",
|
||||||
"Add Folder": "Add Folder",
|
"Add Folder": "Add Folder",
|
||||||
|
"Select Folder": "Select Folder",
|
||||||
"Open Storage folder": "Open Storage folder",
|
"Open Storage folder": "Open Storage folder",
|
||||||
"Unlink": "Unlink",
|
"Unlink": "Unlink",
|
||||||
"Edit": "Edit",
|
"Edit": "Edit",
|
||||||
@@ -34,6 +38,8 @@
|
|||||||
"Solarized Dark": "Solarized Dark",
|
"Solarized Dark": "Solarized Dark",
|
||||||
"Dark": "Dark",
|
"Dark": "Dark",
|
||||||
"Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes",
|
"Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes",
|
||||||
|
"Disable Direct Write (It will be applied after restarting)": "Disable Direct Write (It will be applied after restarting)",
|
||||||
|
"Show only related tags": "Show only related tags",
|
||||||
"Editor Theme": "Editor Theme",
|
"Editor Theme": "Editor Theme",
|
||||||
"Editor Font Size": "Editor Font Size",
|
"Editor Font Size": "Editor Font Size",
|
||||||
"Editor Font Family": "Editor Font Family",
|
"Editor Font Family": "Editor Font Family",
|
||||||
@@ -51,6 +57,7 @@
|
|||||||
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Please restart boostnote after you change the keymap",
|
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Please restart boostnote after you change the keymap",
|
||||||
"Show line numbers in the editor": "Show line numbers in the editor",
|
"Show line numbers in the editor": "Show line numbers in the editor",
|
||||||
"Allow editor to scroll past the last line": "Allow editor to scroll past the last line",
|
"Allow editor to scroll past the last line": "Allow editor to scroll past the last line",
|
||||||
|
"Enable smart quotes": "Enable smart quotes",
|
||||||
"Bring in web page title when pasting URL on editor": "Bring in web page title when pasting URL on editor",
|
"Bring in web page title when pasting URL on editor": "Bring in web page title when pasting URL on editor",
|
||||||
"Preview": "Preview",
|
"Preview": "Preview",
|
||||||
"Preview Font Size": "Preview Font Size",
|
"Preview Font Size": "Preview Font Size",
|
||||||
@@ -62,6 +69,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Community",
|
"Community": "Community",
|
||||||
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
@@ -126,6 +134,7 @@
|
|||||||
"Storage": "Storage",
|
"Storage": "Storage",
|
||||||
"Hotkeys": "Hotkeys",
|
"Hotkeys": "Hotkeys",
|
||||||
"Show/Hide Boostnote": "Show/Hide Boostnote",
|
"Show/Hide Boostnote": "Show/Hide Boostnote",
|
||||||
|
"Toggle editor mode": "Toggle editor mode",
|
||||||
"Restore": "Restore",
|
"Restore": "Restore",
|
||||||
"Permanent Delete": "Permanent Delete",
|
"Permanent Delete": "Permanent Delete",
|
||||||
"Confirm note deletion": "Confirm note deletion",
|
"Confirm note deletion": "Confirm note deletion",
|
||||||
@@ -145,12 +154,26 @@
|
|||||||
"UserName": "UserName",
|
"UserName": "UserName",
|
||||||
"Password": "Password",
|
"Password": "Password",
|
||||||
"Russian": "Russian",
|
"Russian": "Russian",
|
||||||
|
"Hungarian": "Hungarian",
|
||||||
"Command(⌘)": "Command(⌘)",
|
"Command(⌘)": "Command(⌘)",
|
||||||
|
"Add Storage": "Add Storage",
|
||||||
|
"Name": "Name",
|
||||||
|
"Type": "Type",
|
||||||
|
"File System": "File System",
|
||||||
|
"Setting up 3rd-party cloud storage integration:": "Setting up 3rd-party cloud storage integration:",
|
||||||
|
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
|
||||||
|
"Location": "Location",
|
||||||
|
"Add": "Add",
|
||||||
|
"Unlink Storage": "Unlink Storage",
|
||||||
|
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.",
|
||||||
"Editor Rulers": "Editor Rulers",
|
"Editor Rulers": "Editor Rulers",
|
||||||
"Enable": "Enable",
|
"Enable": "Enable",
|
||||||
"Disable": "Disable",
|
"Disable": "Disable",
|
||||||
"Sanitization": "Sanitization",
|
"Sanitization": "Sanitization",
|
||||||
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
||||||
|
"Render newlines in Markdown paragraphs as <br>": "Render newlines in Markdown paragraphs as <br>",
|
||||||
"Allow styles": "Allow styles",
|
"Allow styles": "Allow styles",
|
||||||
"Allow dangerous html tags": "Allow dangerous html tags"
|
"Allow dangerous html tags": "Allow dangerous html tags",
|
||||||
|
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
|
||||||
|
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "Delimitador de cierre LaTeX en línea",
|
"LaTeX Inline Close Delimiter": "Delimitador de cierre LaTeX en línea",
|
||||||
"LaTeX Block Open Delimiter": "Delimitado de apertura bloque LaTeX",
|
"LaTeX Block Open Delimiter": "Delimitado de apertura bloque LaTeX",
|
||||||
"LaTeX Block Close Delimiter": "Delimitador de cierre bloque LaTeX",
|
"LaTeX Block Close Delimiter": "Delimitador de cierre bloque LaTeX",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Comunidad",
|
"Community": "Comunidad",
|
||||||
"Subscribe to Newsletter": "Suscribirse al boletín",
|
"Subscribe to Newsletter": "Suscribirse al boletín",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
@@ -87,7 +88,7 @@
|
|||||||
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote es utilizado en alrededor de 200 países y regiones diferentes por una increíble comunidad de desarrolladores.",
|
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote es utilizado en alrededor de 200 países y regiones diferentes por una increíble comunidad de desarrolladores.",
|
||||||
"To continue supporting this growth, and to satisfy community expectations,": "Para continuar apoyando este crecimiento y satisfacer las expectativas de la comunidad,",
|
"To continue supporting this growth, and to satisfy community expectations,": "Para continuar apoyando este crecimiento y satisfacer las expectativas de la comunidad,",
|
||||||
"we would like to invest more time and resources in this project.": "nos gustaría invertir más tiempo y recursos en este proyecto.",
|
"we would like to invest more time and resources in this project.": "nos gustaría invertir más tiempo y recursos en este proyecto.",
|
||||||
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Si te gusta este proyecto y ves potencial en él, ¡puedes ayudar apoyándonos en OpenCollective!",
|
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Si te gusta este proyecto y ves su potencial, ¡puedes ayudar apoyándonos en OpenCollective!",
|
||||||
"Thanks,": "Gracias,",
|
"Thanks,": "Gracias,",
|
||||||
"Boostnote maintainers": "Equipo de Boostnote",
|
"Boostnote maintainers": "Equipo de Boostnote",
|
||||||
"Support via OpenCollective": "Contribuir vía OpenCollective",
|
"Support via OpenCollective": "Contribuir vía OpenCollective",
|
||||||
@@ -149,5 +150,7 @@
|
|||||||
"Sanitization": "Saneamiento",
|
"Sanitization": "Saneamiento",
|
||||||
"Only allow secure html tags (recommended)": "Solo permitir etiquetas html seguras (recomendado)",
|
"Only allow secure html tags (recommended)": "Solo permitir etiquetas html seguras (recomendado)",
|
||||||
"Allow styles": "Permitir estilos",
|
"Allow styles": "Permitir estilos",
|
||||||
"Allow dangerous html tags": "Permitir etiques html peligrosas"
|
"Allow dangerous html tags": "Permitir etiquetas html peligrosas",
|
||||||
|
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
|
||||||
|
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown."
|
||||||
}
|
}
|
||||||
|
|||||||
159
locales/fa.json
Normal file
159
locales/fa.json
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
{
|
||||||
|
"Notes": "یادداشت ها",
|
||||||
|
"Tags": "تگ ها",
|
||||||
|
"Preferences": "تنظیمات",
|
||||||
|
"Make a note": "یک یادداشت بنویس",
|
||||||
|
"Ctrl": "Ctrl",
|
||||||
|
"Ctrl(^)": "Ctrl",
|
||||||
|
"to create a new note": "برای ساخت یک یادداشت",
|
||||||
|
"Toggle Mode": "تغییر حالت نمایش",
|
||||||
|
"Trash": "سطل آشغال",
|
||||||
|
"MODIFICATION DATE": "تاریخ تغییر",
|
||||||
|
"Words": "کلمات",
|
||||||
|
"Letters": "حروف",
|
||||||
|
"STORAGE": "ذخیره سازی",
|
||||||
|
"FOLDER": "پوشه",
|
||||||
|
"CREATION DATE": "تاریخ ایجاد",
|
||||||
|
"NOTE LINK": "لینک یادداشت",
|
||||||
|
".md": ".md",
|
||||||
|
".txt": ".txt",
|
||||||
|
".html": ".html",
|
||||||
|
"Print": "پرینت",
|
||||||
|
"Your preferences for Boostnote": "تنظیمات شما برای boostnote",
|
||||||
|
"Storages": "ذخیره سازی",
|
||||||
|
"Add Storage Location": "افزودن محل ذخیره سازی",
|
||||||
|
"Add Folder": "ساخت پوشه",
|
||||||
|
"Open Storage folder": "بازکردن پوشه ذخیره سازی",
|
||||||
|
"Unlink": "حذف لینک",
|
||||||
|
"Edit": "ویرایش",
|
||||||
|
"Delete": "حذف",
|
||||||
|
"Interface": "رابط کاربری",
|
||||||
|
"Interface Theme": "تم رابط کاربری",
|
||||||
|
"Default": "پیش فرض",
|
||||||
|
"White": "روشن",
|
||||||
|
"Solarized Dark": "سولارایز",
|
||||||
|
"Dark": "تاریک",
|
||||||
|
"Show a confirmation dialog when deleting notes": "هنگام حذف یادداشت ها یک پیام تایید نمایش بده.",
|
||||||
|
"Editor Theme": "تم ویرایشگر",
|
||||||
|
"Editor Font Size": "اندازه فونت ویرایشگر",
|
||||||
|
"Editor Font Family": "فونت ویرایشگر",
|
||||||
|
"Editor Indent Style": "حالت فاصله گذاری ویرایشگر",
|
||||||
|
"Spaces": "Spaces",
|
||||||
|
"Tabs": "Tabs",
|
||||||
|
"Switch to Preview": "دیدن پیش نمایش",
|
||||||
|
"When Editor Blurred": "وقتی ویرایشگر از حالت ویرایش خارج شد ",
|
||||||
|
"When Editor Blurred, Edit On Double Click": "وقتی ویرایشگر از حالت ویرایش خارج شد و با دبل کلیک ویرایش کنید.",
|
||||||
|
"On Right Click": "راست کلیک",
|
||||||
|
"Editor Keymap": "ویرایشگر Keymap",
|
||||||
|
"default": "پیش فرض",
|
||||||
|
"vim": "vim",
|
||||||
|
"emacs": "emacs",
|
||||||
|
"⚠️ Please restart boostnote after you change the keymap": "⚠️ برنامه را دوباره راه اندازی کنید keymap لطفا بعد از تغییر",
|
||||||
|
"Show line numbers in the editor": "شماره خطوط در ویرایشگر را نمایش بده.",
|
||||||
|
"Allow editor to scroll past the last line": "اجازه بده ویرایشگر بعد از آخرین خط اسکرول کند.",
|
||||||
|
"Bring in web page title when pasting URL on editor": "هنگامی که آدرس اینترنتی در ویرایشگر اضافه شد عنوان آنرا نمایش بده",
|
||||||
|
"Preview": "پیش نمایش",
|
||||||
|
"Preview Font Size": "اندازه فونتِ پیش نمایش",
|
||||||
|
"Preview Font Family": " فونتِ پیش نمایش",
|
||||||
|
"Code block Theme": "تم بخش کد",
|
||||||
|
"Allow preview to scroll past the last line": "اجازه بده پیش نمایش بعد از آخرین خط اسکرول کند.",
|
||||||
|
"Show line numbers for preview code blocks": "شماره خطوط در پیش نمایش را نمایش بده.",
|
||||||
|
"LaTeX Inline Open Delimiter": "جداکننده آغازین لاتکس خطی",
|
||||||
|
"LaTeX Inline Close Delimiter": "جداکننده پایانی لاتکس خطی",
|
||||||
|
"LaTeX Block Open Delimiter": "جداکننده آغازین بلوک لاتکس ",
|
||||||
|
"LaTeX Block Close Delimiter": "جداکننده آغازین بلوک لاتکس ",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
|
"Community": "کامینیتی",
|
||||||
|
"Subscribe to Newsletter": "اشتراک در خبرنامه",
|
||||||
|
"GitHub": "گیت هاب",
|
||||||
|
"Blog": "بلاگ",
|
||||||
|
"Facebook Group": "گروه فیسبوک",
|
||||||
|
"Twitter": "توییتر",
|
||||||
|
"About": "درباره",
|
||||||
|
"Boostnote": "Boostnote",
|
||||||
|
"An open source note-taking app made for programmers just like you.": "یک دفترچه یادداشت متن باز ساخته شده برای برنامه نویسانی مثل تو.",
|
||||||
|
"Website": "وبسایت",
|
||||||
|
"Development": "توسعه",
|
||||||
|
" : Development configurations for Boostnote.": " : پیکربندی توسعه برای Boostnote.",
|
||||||
|
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
|
||||||
|
"License: GPL v3": "لایسنس: GPL v3",
|
||||||
|
"Analytics": "تجزیه و تحلیل",
|
||||||
|
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Bosstnote اطلاعات ناشناس را برای بهبود عملکرد برنامه جمع آوری میکند.اطلاعات شخصی شما مثل محتوای یادداشت ها هرگز برای هیچ هدفی جمع آوری نمیشوند",
|
||||||
|
"You can see how it works on ": "میتوانید ببینید چگونه کار میکند. ",
|
||||||
|
"You can choose to enable or disable this option.": "میتوانید این گزینه را فعال یا غیرفعال کنید.",
|
||||||
|
"Enable analytics to help improve Boostnote":".تجزیه تحلیل داده ها را برای کمک به بهبود برنامه فعال کن",
|
||||||
|
"Crowdfunding": "جمع سپاری (سرمایه گذاری جمعی )",
|
||||||
|
"Dear everyone,": "عزیزان,",
|
||||||
|
"Thank you for using Boostnote!": "از شما بخاطر استفاده از boostnote ممنونیم!",
|
||||||
|
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "در ۲۰۰ کشور مختلف دنیا مورد توسط جمعی از برنامه نویسان بی نظیر مورد استفاده قرار میگیرد. Boostnote",
|
||||||
|
"To continue supporting this growth, and to satisfy community expectations,": "برای حمایت از این رشد ، و برآورده شدن انتظارات کامینیتی,",
|
||||||
|
"we would like to invest more time and resources in this project.": "ما می خواهیم زمان و منابع بیشتری را در این پروژه سرمایه گذاری کنیم.",
|
||||||
|
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "اگر این پروژه را دوست دارید و پتانسیلی در آن میبینید، میتوانید مارا در اوپن کالکتیو حمایت کنید.",
|
||||||
|
"Thanks,": "با تشکر,",
|
||||||
|
"Boostnote maintainers": "Boostnote نگهدارندگان",
|
||||||
|
"Support via OpenCollective": "حمایت کنید OpenCollective از طریق",
|
||||||
|
"Language": "زبان",
|
||||||
|
"English": "انگلیسی",
|
||||||
|
"German": "آلمانی",
|
||||||
|
"French": "فرانسوی",
|
||||||
|
"Show \"Saved to Clipboard\" notification when copying": "نمایش \"ذخیره در کلیپبورد\" اطلاع رسانی هنگام کپی کردن",
|
||||||
|
"All Notes": "همه یادداشت ها",
|
||||||
|
"Starred": "ستاره دار",
|
||||||
|
"Are you sure to ": " مطمئن هستید که",
|
||||||
|
" delete": "حذف ",
|
||||||
|
"this folder?": "این پوشه ؟",
|
||||||
|
"Confirm": "تایید",
|
||||||
|
"Cancel": "انصراف",
|
||||||
|
"Markdown Note": "Markdown یادداشتِ",
|
||||||
|
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "این قالب برای ساخت سند های متنی است. چک لیست ها و تکه کد ها و بلاک های لاتکس قابل استفاده اند.",
|
||||||
|
"Snippet Note": "Snippet یادداشتِ",
|
||||||
|
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "این قالب برای ساخت تکه کد هاست. چند تکه کد میتوانند تبدیل به یک یادداشت شوند.",
|
||||||
|
"Tab to switch format": "را بزنید Tab برای تغییر فرمت",
|
||||||
|
"Updated": "بروزرسانی شد",
|
||||||
|
"Created": "ایجاد شد",
|
||||||
|
"Alphabetically": "بر اساس حروف الفبا",
|
||||||
|
"Counter": "شمارشگر",
|
||||||
|
"Default View": "نمایش پیشفرض",
|
||||||
|
"Compressed View": "نمایش فشرده",
|
||||||
|
"Search": "جستجو",
|
||||||
|
"Blog Type": "نوع وبلاگ",
|
||||||
|
"Blog Address": "آدرس وبلاگ",
|
||||||
|
"Save": "ذخیره",
|
||||||
|
"Auth": "هویت",
|
||||||
|
"Authentication Method": "متد احراز هویت",
|
||||||
|
"JWT": "JWT",
|
||||||
|
"USER": "کاربر",
|
||||||
|
"Token": "توکن",
|
||||||
|
"Storage": "ذخیره سازی",
|
||||||
|
"Hotkeys": "کلید های میانبر",
|
||||||
|
"Show/Hide Boostnote": "Boostnote نمایش/پنهان کردن",
|
||||||
|
"Restore": "بازگرداندن به حالت اول",
|
||||||
|
"Permanent Delete": "حذف بدون بازگشت",
|
||||||
|
"Confirm note deletion": ".حذف یادداشت را تایید کنید",
|
||||||
|
"This will permanently remove this note.": ".این کار یادداشت را بطور دائمی حذف خواهد کرد",
|
||||||
|
"Successfully applied!": "!با موفقیت اجرا شد",
|
||||||
|
"Albanian": "آلبانی",
|
||||||
|
"Chinese (zh-CN)": "چینی (zh-CN)",
|
||||||
|
"Chinese (zh-TW)": "چینی (zh-TW)",
|
||||||
|
"Danish": "دانمارکی",
|
||||||
|
"Japanese": "ژاپنی",
|
||||||
|
"Korean": "کره ای",
|
||||||
|
"Norwegian": "نروژی",
|
||||||
|
"Polish": "لهستانی",
|
||||||
|
"Portuguese": "پرتغالی",
|
||||||
|
"Spanish": "اسپانیایی",
|
||||||
|
"You have to save!": "!باید ذخیره کنید",
|
||||||
|
"UserName": "نام کاربری",
|
||||||
|
"Password": "رمز عبور",
|
||||||
|
"Russian": "روسی",
|
||||||
|
"Command(⌘)": "Command(⌘)",
|
||||||
|
"Editor Rulers": "Editor Rulers",
|
||||||
|
"Enable": "فعال",
|
||||||
|
"Disable": "غیرفعال",
|
||||||
|
"Sanitization": "پاکسازی کردن",
|
||||||
|
"Only allow secure html tags (recommended)": "(فقط تگ های امن اچ تی ام ال مجاز اند.(پیشنهاد میشود",
|
||||||
|
"Allow styles": "حالت های مجاز",
|
||||||
|
"Allow dangerous html tags": "تگ های خطرناک اچ تی ام ال مجاز اند",
|
||||||
|
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
|
||||||
|
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
|
||||||
|
}
|
||||||
@@ -61,6 +61,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Communauté",
|
"Community": "Communauté",
|
||||||
"Subscribe to Newsletter": "Souscrire à la newsletter",
|
"Subscribe to Newsletter": "Souscrire à la newsletter",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
@@ -140,14 +141,16 @@
|
|||||||
"Portuguese": "Portugais",
|
"Portuguese": "Portugais",
|
||||||
"Spanish": "Espagnol",
|
"Spanish": "Espagnol",
|
||||||
"You have to save!": "Il faut sauvegarder !",
|
"You have to save!": "Il faut sauvegarder !",
|
||||||
"Russian": "Russian",
|
"Russian": "Russe",
|
||||||
"Command(⌘)": "Command(⌘)",
|
"Command(⌘)": "Command(⌘)",
|
||||||
"Editor Rulers": "Editor Rulers",
|
"Editor Rulers": "Règles dans l'éditeur",
|
||||||
"Enable": "Enable",
|
"Enable": "Activer",
|
||||||
"Disable": "Disable",
|
"Disable": "Désactiver",
|
||||||
"Allow preview to scroll past the last line": "Allow preview to scroll past the last line",
|
"Allow preview to scroll past the last line": "Permettre de scroller après la dernière ligne dans l'aperçu",
|
||||||
"Sanitization": "Sanitization",
|
"Sanitization": "Sanitization",
|
||||||
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
"Only allow secure html tags (recommended)": "N'accepter que les tags html sécurisés (recommandé)",
|
||||||
"Allow styles": "Allow styles",
|
"Allow styles": "Accepter les styles",
|
||||||
"Allow dangerous html tags": "Allow dangerous html tags"
|
"Allow dangerous html tags": "Accepter les tags html dangereux",
|
||||||
|
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convertir des flèches textuelles en jolis signes. ⚠ Cela va interferérer avec les éventuels commentaires HTML dans votre Markdown.",
|
||||||
|
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Vous avez collé un lien qui référence une pièce-jointe qui n'a pas pu être récupéré dans le dossier de stockage de la note. Coller des liens qui font référence à des pièces-jointes ne fonctionne que si la source et la destination et la même. Veuillez plutôt utiliser du Drag & Drop ! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"Ctrl(^)": "Ctrl",
|
"Ctrl(^)": "Ctrl",
|
||||||
"to create a new note": "hogy létrehozz egy jegyzetet",
|
"to create a new note": "hogy létrehozz egy jegyzetet",
|
||||||
"Toggle Mode": "Mód Váltás",
|
"Toggle Mode": "Mód Váltás",
|
||||||
|
"Add tag...": "Tag hozzáadása...",
|
||||||
"Trash": "Lomtár",
|
"Trash": "Lomtár",
|
||||||
"MODIFICATION DATE": "MÓDOSÍTÁS DÁTUMA",
|
"MODIFICATION DATE": "MÓDOSÍTÁS DÁTUMA",
|
||||||
"Words": "Szó",
|
"Words": "Szó",
|
||||||
@@ -20,9 +21,12 @@
|
|||||||
".html": ".html",
|
".html": ".html",
|
||||||
"Print": "Nyomtatás",
|
"Print": "Nyomtatás",
|
||||||
"Your preferences for Boostnote": "Boostnote beállításaid",
|
"Your preferences for Boostnote": "Boostnote beállításaid",
|
||||||
|
"Help": "Súgó",
|
||||||
|
"Hide Help": "Súgó Elrejtése",
|
||||||
"Storages": "Tárolók",
|
"Storages": "Tárolók",
|
||||||
"Add Storage Location": "Tároló Hozzáadása",
|
"Add Storage Location": "Tároló Hozzáadása",
|
||||||
"Add Folder": "Könyvtár Hozzáadása",
|
"Add Folder": "Könyvtár Hozzáadása",
|
||||||
|
"Select Folder": "Könyvtár Kiválasztása",
|
||||||
"Open Storage folder": "Tároló Megnyitása",
|
"Open Storage folder": "Tároló Megnyitása",
|
||||||
"Unlink": "Tároló Leválasztása",
|
"Unlink": "Tároló Leválasztása",
|
||||||
"Edit": "Szerkesztés",
|
"Edit": "Szerkesztés",
|
||||||
@@ -34,6 +38,8 @@
|
|||||||
"Solarized Dark": "Solarized Dark",
|
"Solarized Dark": "Solarized Dark",
|
||||||
"Dark": "Sötét",
|
"Dark": "Sötét",
|
||||||
"Show a confirmation dialog when deleting notes": "Kérjen megerősítést a jegyzetek törlése előtt",
|
"Show a confirmation dialog when deleting notes": "Kérjen megerősítést a jegyzetek törlése előtt",
|
||||||
|
"Disable Direct Write (It will be applied after restarting)": "Jegyzet Azonnali Mentésének Tiltása (Újraindítás igényel)",
|
||||||
|
"Show only related tags": "Csak a kapcsolódó tag-ek megjelenítése",
|
||||||
"Editor Theme": "Szerkesztő Témája",
|
"Editor Theme": "Szerkesztő Témája",
|
||||||
"Editor Font Size": "Szerkesztő Betűmérete",
|
"Editor Font Size": "Szerkesztő Betűmérete",
|
||||||
"Editor Font Family": "Szerkesztő Betűtípusa",
|
"Editor Font Family": "Szerkesztő Betűtípusa",
|
||||||
@@ -51,6 +57,7 @@
|
|||||||
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Kérlek, indítsd újra a programot a kiosztás megváltoztatása után",
|
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Kérlek, indítsd újra a programot a kiosztás megváltoztatása után",
|
||||||
"Show line numbers in the editor": "Mutatassa a sorszámokat a szerkesztőben",
|
"Show line numbers in the editor": "Mutatassa a sorszámokat a szerkesztőben",
|
||||||
"Allow editor to scroll past the last line": "A szerkesztőben az utolsó sor alá is lehessen görgetni",
|
"Allow editor to scroll past the last line": "A szerkesztőben az utolsó sor alá is lehessen görgetni",
|
||||||
|
"Enable smart quotes": "Idézőjelek párjának automatikus beírása",
|
||||||
"Bring in web page title when pasting URL on editor": "Weboldal főcímének lekérdezése URL cím beillesztésekor",
|
"Bring in web page title when pasting URL on editor": "Weboldal főcímének lekérdezése URL cím beillesztésekor",
|
||||||
"Preview": "Megtekintés",
|
"Preview": "Megtekintés",
|
||||||
"Preview Font Size": "Megtekintés Betűmérete",
|
"Preview Font Size": "Megtekintés Betűmérete",
|
||||||
@@ -62,6 +69,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Záró Határolója",
|
"LaTeX Inline Close Delimiter": "LaTeX Inline Záró Határolója",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX Blokk Nyitó Határolója",
|
"LaTeX Block Open Delimiter": "LaTeX Blokk Nyitó Határolója",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX Blokk Záró Határolója",
|
"LaTeX Block Close Delimiter": "LaTeX Blokk Záró Határolója",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Közösség",
|
"Community": "Közösség",
|
||||||
"Subscribe to Newsletter": "Feliratkozás a Hírlevélre",
|
"Subscribe to Newsletter": "Feliratkozás a Hírlevélre",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
@@ -126,6 +134,7 @@
|
|||||||
"Storage": "Tároló",
|
"Storage": "Tároló",
|
||||||
"Hotkeys": "Gyorsbillentyűk",
|
"Hotkeys": "Gyorsbillentyűk",
|
||||||
"Show/Hide Boostnote": "Boostnote Megjelenítése/Elrejtése",
|
"Show/Hide Boostnote": "Boostnote Megjelenítése/Elrejtése",
|
||||||
|
"Toggle editor mode": "Szerkesztő mód váltása",
|
||||||
"Restore": "Visszaállítás",
|
"Restore": "Visszaállítás",
|
||||||
"Permanent Delete": "Végleges Törlés",
|
"Permanent Delete": "Végleges Törlés",
|
||||||
"Confirm note deletion": "Törlés megerősítése",
|
"Confirm note deletion": "Törlés megerősítése",
|
||||||
@@ -145,8 +154,8 @@
|
|||||||
"UserName": "FelhasznaloNev",
|
"UserName": "FelhasznaloNev",
|
||||||
"Password": "Jelszo",
|
"Password": "Jelszo",
|
||||||
"Russian": "Russian",
|
"Russian": "Russian",
|
||||||
"Command(⌘)": "Command(⌘)",
|
|
||||||
"Hungarian": "Hungarian",
|
"Hungarian": "Hungarian",
|
||||||
|
"Command(⌘)": "Command(⌘)",
|
||||||
"Add Storage": "Tároló hozzáadása",
|
"Add Storage": "Tároló hozzáadása",
|
||||||
"Name": "Név",
|
"Name": "Név",
|
||||||
"Type": "Típus",
|
"Type": "Típus",
|
||||||
@@ -155,6 +164,17 @@
|
|||||||
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
|
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
|
||||||
"Location": "Hely",
|
"Location": "Hely",
|
||||||
"Add": "Hozzáadás",
|
"Add": "Hozzáadás",
|
||||||
|
"Select Folder": "Könyvtár Kiválasztása",
|
||||||
"Unlink Storage": "Tároló Leválasztása",
|
"Unlink Storage": "Tároló Leválasztása",
|
||||||
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "A leválasztás eltávolítja ezt a tárolót a Boostnote-ból. Az adatok nem lesznek törölve, kérlek manuálisan töröld a könyvtárat a merevlemezről, ha szükséges."
|
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "A leválasztás eltávolítja ezt a tárolót a Boostnote-ból. Az adatok nem lesznek törölve, kérlek manuálisan töröld a könyvtárat a merevlemezről, ha szükséges.",
|
||||||
|
"Editor Rulers": "Szerkesztő Margók",
|
||||||
|
"Enable": "Engedélyezés",
|
||||||
|
"Disable": "Tiltás",
|
||||||
|
"Sanitization": "Tisztítás",
|
||||||
|
"Only allow secure html tags (recommended)": "Csak a biztonságos html tag-ek engedélyezése (ajánlott)",
|
||||||
|
"Render newlines in Markdown paragraphs as <br>": "Az újsor karaktert <br> soremelésként jelenítse meg a Markdown jegyzetekben",
|
||||||
|
"Allow styles": "Stílusok engedélyezése",
|
||||||
|
"Allow dangerous html tags": "Veszélyes html tag-ek engedélyezése",
|
||||||
|
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
|
||||||
|
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
159
locales/it.json
Normal file
159
locales/it.json
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
{
|
||||||
|
"Notes": "Note",
|
||||||
|
"Tags": "Tags",
|
||||||
|
"Preferences": "Preferenze",
|
||||||
|
"Make a note": "Crea una nota",
|
||||||
|
"Ctrl": "Ctrl",
|
||||||
|
"Ctrl(^)": "Ctrl",
|
||||||
|
"to create a new note": "per creare una nuova nota",
|
||||||
|
"Toggle Mode": "Cambia Modalità",
|
||||||
|
"Trash": "Cestino",
|
||||||
|
"MODIFICATION DATE": "DATA DI MODIFICA",
|
||||||
|
"Words": "Parole",
|
||||||
|
"Letters": "Lettere",
|
||||||
|
"STORAGE": "POSIZIONE",
|
||||||
|
"FOLDER": "CARTELLA",
|
||||||
|
"CREATION DATE": "DATA DI CREAZIONE",
|
||||||
|
"NOTE LINK": "LINK NOTA",
|
||||||
|
".md": ".md",
|
||||||
|
".txt": ".txt",
|
||||||
|
".html": ".html",
|
||||||
|
"Print": "Stampa",
|
||||||
|
"Your preferences for Boostnote": "Le tue preferenze per Boostnote",
|
||||||
|
"Storages": "Posizioni",
|
||||||
|
"Add Storage Location": "Aggiungi posizione",
|
||||||
|
"Add Folder": "Aggiungi cartella",
|
||||||
|
"Open Storage folder": "Apri cartella di memoria",
|
||||||
|
"Unlink": "Scollega",
|
||||||
|
"Edit": "Modifica",
|
||||||
|
"Delete": "Elimina",
|
||||||
|
"Interface": "Interfaccia",
|
||||||
|
"Interface Theme": "Tema interfaccia",
|
||||||
|
"Default": "Default",
|
||||||
|
"White": "White",
|
||||||
|
"Solarized Dark": "Solarized Dark",
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Show a confirmation dialog when deleting notes": "Mostra finestra di conferma quando elimini delle note",
|
||||||
|
"Editor Theme": "Tema dell'Editor",
|
||||||
|
"Editor Font Size": "Dimensione font dell'editor",
|
||||||
|
"Editor Font Family": "Famiglia del font dell'editor",
|
||||||
|
"Editor Indent Style": "Stile di indentazione dell'editor",
|
||||||
|
"Spaces": "Spazi",
|
||||||
|
"Tabs": "Tabs",
|
||||||
|
"Switch to Preview": "Passa all'anteprima",
|
||||||
|
"When Editor Blurred": "Quando l'editor è sfocato",
|
||||||
|
"When Editor Blurred, Edit On Double Click": "Quando l'Editor è sfocato, Modifica facendo doppio click",
|
||||||
|
"On Right Click": "Cliccando con il tasto destro",
|
||||||
|
"Editor Keymap": "keymapping dell'editor",
|
||||||
|
"default": "default",
|
||||||
|
"vim": "vim",
|
||||||
|
"emacs": "emacs",
|
||||||
|
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Riavvia Boostnote dopo aver cambiato il keymapping",
|
||||||
|
"Show line numbers in the editor": "Mostra numero di linea nell'editor",
|
||||||
|
"Allow editor to scroll past the last line": "Consenti scrolling oltre l'ultima linea nell'editor",
|
||||||
|
"Bring in web page title when pasting URL on editor": "Mostra il titolo della pagina web quando incolli un URL nell'editor",
|
||||||
|
"Preview": "Anteprima",
|
||||||
|
"Preview Font Size": "Dimensione font nell'anteprima",
|
||||||
|
"Preview Font Family": "Famiglia del font dell'anteprima",
|
||||||
|
"Code block Theme": "Tema blocco di codice",
|
||||||
|
"Allow preview to scroll past the last line": "Consenti scrolling oltre l'ultima linea",
|
||||||
|
"Show line numbers for preview code blocks": "Mostra numero di linea per i blocchi di codice nell'Anteprima",
|
||||||
|
"LaTeX Inline Open Delimiter": "Delimitatore inline per apertura LaTex",
|
||||||
|
"LaTeX Inline Close Delimiter": "Delimitatore inline per chiusura LaTex",
|
||||||
|
"LaTeX Block Open Delimiter": "Delimitatore apertura LaTex",
|
||||||
|
"LaTeX Block Close Delimiter": "Delimitatore chiusura LaTex",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
|
"Community": "Community",
|
||||||
|
"Subscribe to Newsletter": "Iscriviti alla Newsletter",
|
||||||
|
"GitHub": "GitHub",
|
||||||
|
"Blog": "Blog",
|
||||||
|
"Facebook Group": "Gruppo Facebook",
|
||||||
|
"Twitter": "Twitter",
|
||||||
|
"About": "About",
|
||||||
|
"Boostnote": "Boostnote",
|
||||||
|
"An open source note-taking app made for programmers just like you.": "Un'app open-source per prendere appunti, fatta per sviluppatori come te.",
|
||||||
|
"Website": "Sito Web",
|
||||||
|
"Development": "Sviluppo",
|
||||||
|
" : Development configurations for Boostnote.": " : Configurazioni di sviluppo per Boostnote.",
|
||||||
|
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
|
||||||
|
"License: GPL v3": "Licenza: GPL v3",
|
||||||
|
"Analytics": "Statistiche",
|
||||||
|
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote raccoglie dati anonimi al solo scopo di migliorare l'applicazione, e non raccoglie nessuna informazione personale rigurado il contenuto delle note.",
|
||||||
|
"You can see how it works on ": "Ypuoi vedere come su ",
|
||||||
|
"You can choose to enable or disable this option.": "Puoi scegliere se attivare o disattivare questa opzione.",
|
||||||
|
"Enable analytics to help improve Boostnote": "Attiva raccolta dati per aiutare a migliorare Boostnote",
|
||||||
|
"Crowdfunding": "Crowdfunding",
|
||||||
|
"Dear everyone,": "Cari utenti,",
|
||||||
|
"Thank you for using Boostnote!": "Grazie per stare utilizzando Boostnote!",
|
||||||
|
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote è usato in circa 200 Paesi da una fantastica community di sviluppatori.",
|
||||||
|
"To continue supporting this growth, and to satisfy community expectations,": "Per continuare a supportarne la crescita, e per soddisfare le aspettative della comunità,",
|
||||||
|
"we would like to invest more time and resources in this project.": "ci piacerebbe investire più tempo e risorse in questo progetto.",
|
||||||
|
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Se ti piace questo progetto e ci vedi del potenziale, puoi aiutarci dandodci supporto su OpenCollective!",
|
||||||
|
"Thanks,": "Grazie,",
|
||||||
|
"Boostnote maintainers": "I mantainers di Boostnote",
|
||||||
|
"Support via OpenCollective": "Supporta su OpenCollective",
|
||||||
|
"Language": "Lingua",
|
||||||
|
"English": "Inglese",
|
||||||
|
"German": "Tedesco",
|
||||||
|
"French": "Francese",
|
||||||
|
"Show \"Saved to Clipboard\" notification when copying": "Mostra la notifica \"Salvato negli Appunti\" quando copi:",
|
||||||
|
"All Notes": "Tutte le note",
|
||||||
|
"Starred": "Contribuite",
|
||||||
|
"Are you sure to ": "Sei sicuro di ",
|
||||||
|
" delete": " eliminare",
|
||||||
|
"this folder?": "questa cartella?",
|
||||||
|
"Confirm": "Conferma",
|
||||||
|
"Cancel": "Cancella",
|
||||||
|
"Markdown Note": "Nota in Markdown",
|
||||||
|
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "Questo formato è per creare documenti di testo. Sono disponibili checklist, blocchi di codice and blocchi in Latex",
|
||||||
|
"Snippet Note": "Nota Snippet",
|
||||||
|
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "Questo formato è per creare snippets. Più snippet possono essere raccolti in un'unica nota.",
|
||||||
|
"Tab to switch format": "Premi Tab per cambiare formato",
|
||||||
|
"Updated": "Aggiornato",
|
||||||
|
"Created": "Creato",
|
||||||
|
"Alphabetically": "Ordine alfabetico",
|
||||||
|
"Counter": "Contatore",
|
||||||
|
"Default View": "Visione di default",
|
||||||
|
"Compressed View": "Visione compressa",
|
||||||
|
"Search": "Cerca",
|
||||||
|
"Blog Type": "Tipo di blog",
|
||||||
|
"Blog Address": "Indirizzo del blog",
|
||||||
|
"Save": "Salva",
|
||||||
|
"Auth": "Autorizzazione",
|
||||||
|
"Authentication Method": "Metodo di autenticazione",
|
||||||
|
"JWT": "JWT",
|
||||||
|
"USER": "USER",
|
||||||
|
"Token": "Token",
|
||||||
|
"Storage": "Storage",
|
||||||
|
"Hotkeys": "Hotkeys",
|
||||||
|
"Show/Hide Boostnote": "Mostra/Nascondi Boostnote",
|
||||||
|
"Restore": "Ripristina",
|
||||||
|
"Permanent Delete": "Elimina permanentemente",
|
||||||
|
"Confirm note deletion": "Conferma eliiminazione della nota",
|
||||||
|
"This will permanently remove this note.": "Questo eliminerà permanentemente questa nota.",
|
||||||
|
"Successfully applied!": "Applicato con successo!",
|
||||||
|
"Albanian": "Albanese",
|
||||||
|
"Chinese (zh-CN)": "Cinese (zh-CN)",
|
||||||
|
"Chinese (zh-TW)": "Cinese (zh-TW)",
|
||||||
|
"Danish": "Danese",
|
||||||
|
"Japanese": "Giapponese",
|
||||||
|
"Korean": "Koreano",
|
||||||
|
"Norwegian": "Novergese",
|
||||||
|
"Polish": "Polacco",
|
||||||
|
"Portuguese": "Portoghese",
|
||||||
|
"Spanish": "Spagnolo",
|
||||||
|
"You have to save!": "Devi salvare!",
|
||||||
|
"UserName": "UserName",
|
||||||
|
"Password": "Password",
|
||||||
|
"Russian": "Russo",
|
||||||
|
"Command(⌘)": "Comando(⌘)",
|
||||||
|
"Editor Rulers": "Regole dell'editor",
|
||||||
|
"Enable": "Abilita",
|
||||||
|
"Disable": "Disabilia",
|
||||||
|
"Sanitization": "Bonifica",
|
||||||
|
"Only allow secure html tags (recommended)": "Consenti solo tag HTML sicuri (raccomandato)",
|
||||||
|
"Allow styles": "Consenti stili",
|
||||||
|
"Allow dangerous html tags": "Consenti tag HTML pericolosi",
|
||||||
|
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
|
||||||
|
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
|
||||||
|
}
|
||||||
304
locales/ja.json
304
locales/ja.json
@@ -1,153 +1,179 @@
|
|||||||
{
|
{
|
||||||
"Notes": "Notes",
|
"Notes": "ノート",
|
||||||
"Tags": "Tags",
|
"Tags": "タグ",
|
||||||
"Preferences": "Preferences",
|
"Preferences": "設定",
|
||||||
"Make a note": "Make a note",
|
"Make a note": "ノート作成",
|
||||||
"Ctrl": "Ctrl",
|
"Ctrl": "Ctrl",
|
||||||
"Ctrl(^)": "Ctrl",
|
"Ctrl(^)": "Ctrl",
|
||||||
"to create a new note": "to create a new note",
|
"to create a new note": "ノートを新規に作成",
|
||||||
"Toggle Mode": "Toggle Mode",
|
"Toggle Mode": "モード切替",
|
||||||
"Trash": "Trash",
|
"Add tag...": "タグを追加...",
|
||||||
"MODIFICATION DATE": "MODIFICATION DATE",
|
"Trash": "ゴミ箱",
|
||||||
"Words": "Words",
|
"MODIFICATION DATE": "修正日",
|
||||||
"Letters": "Letters",
|
"Words": "ワード",
|
||||||
"STORAGE": "STORAGE",
|
"Letters": "文字",
|
||||||
"FOLDER": "FOLDER",
|
"STORAGE": "ストレージ",
|
||||||
"CREATION DATE": "CREATION DATE",
|
"FOLDER": "フォルダ",
|
||||||
"NOTE LINK": "NOTE LINK",
|
"CREATION DATE": "作成日",
|
||||||
|
"NOTE LINK": "リンク",
|
||||||
".md": ".md",
|
".md": ".md",
|
||||||
".txt": ".txt",
|
".txt": ".txt",
|
||||||
".html": ".html",
|
".html": ".html",
|
||||||
"Print": "Print",
|
"Print": "印刷",
|
||||||
"Your preferences for Boostnote": "Your preferences for Boostnote",
|
"Your preferences for Boostnote": "Boostnoteの個人設定",
|
||||||
"Storages": "Storages",
|
"Help": "ヘルプ",
|
||||||
"Add Storage Location": "Add Storage Location",
|
"Hide Help": "ヘルプを隠す",
|
||||||
"Add Folder": "Add Folder",
|
"Storages": "ストレージ",
|
||||||
"Open Storage folder": "Open Storage folder",
|
"Add Storage Location": "ストレージロケーションを追加",
|
||||||
"Unlink": "Unlink",
|
"Add Folder": "フォルダを追加",
|
||||||
"Edit": "Edit",
|
"Select Folder": "フォルダを選択",
|
||||||
"Delete": "Delete",
|
"Open Storage folder": "ストレージフォルダを開く",
|
||||||
"Interface": "Interface",
|
"Unlink": "リンク解除",
|
||||||
"Interface Theme": "Interface Theme",
|
"Edit": "編集",
|
||||||
"Default": "Default",
|
"Delete": "削除",
|
||||||
"White": "White",
|
"Interface": "インターフェース",
|
||||||
"Solarized Dark": "Solarized Dark",
|
"Interface Theme": "インターフェーステーマ",
|
||||||
"Dark": "Dark",
|
"Default": "デフォルト",
|
||||||
"Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes",
|
"White": "白",
|
||||||
"Editor Theme": "Editor Theme",
|
"Solarized Dark": "明灰",
|
||||||
"Editor Font Size": "Editor Font Size",
|
"Dark": "暗灰",
|
||||||
"Editor Font Family": "Editor Font Family",
|
"Show a confirmation dialog when deleting notes": "ノートを削除する時に確認ダイアログを表示する",
|
||||||
"Editor Indent Style": "Editor Indent Style",
|
"Disable Direct Write (It will be applied after restarting)": "Disable Direct Write (It will be applied after restarting)",
|
||||||
"Spaces": "Spaces",
|
"Show only related tags": "関連するタグのみ表示する",
|
||||||
"Tabs": "Tabs",
|
"Editor Theme": "エディタのテーマ",
|
||||||
"Switch to Preview": "Switch to Preview",
|
"Editor Font Size": "エディタのフォントサイズ",
|
||||||
"When Editor Blurred": "When Editor Blurred",
|
"Editor Font Family": "エディタのフォント",
|
||||||
"When Editor Blurred, Edit On Double Click": "When Editor Blurred, Edit On Double Click",
|
"Editor Indent Style": "エディタのインデント方法",
|
||||||
"On Right Click": "On Right Click",
|
"Spaces": "スペース",
|
||||||
"Editor Keymap": "Editor Keymap",
|
"Tabs": "タブ",
|
||||||
"default": "default",
|
"Switch to Preview": "プレビューへ移動",
|
||||||
|
"When Editor Blurred": "エディタがフォーカスを失った時",
|
||||||
|
"When Editor Blurred, Edit On Double Click": "エディタがフォーカスを失った時、ダブルクリックで編集",
|
||||||
|
"On Right Click": "右クリック",
|
||||||
|
"Editor Keymap": "エディタのキーマップ",
|
||||||
|
"default": "デフォルト",
|
||||||
"vim": "vim",
|
"vim": "vim",
|
||||||
"emacs": "emacs",
|
"emacs": "emacs",
|
||||||
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Please restart boostnote after you change the keymap",
|
"⚠️ Please restart boostnote after you change the keymap": "⚠️ キーマップ変更後は Boostnote を再起動してください",
|
||||||
"Show line numbers in the editor": "Show line numbers in the editor",
|
"Show line numbers in the editor": "エディタ内に行番号を表示",
|
||||||
"Allow editor to scroll past the last line": "Allow editor to scroll past the last line",
|
"Allow editor to scroll past the last line": "エディタが最終行以降にスクロールできるようにする",
|
||||||
"Bring in web page title when pasting URL on editor": "Bring in web page title when pasting URL on editor",
|
"Enable smart quotes": "スマートクォートを有効にする",
|
||||||
"Preview": "Preview",
|
"Bring in web page title when pasting URL on editor": "URLを貼り付けた時にWebページのタイトルを取得する",
|
||||||
"Preview Font Size": "Preview Font Size",
|
"Preview": "プレビュー",
|
||||||
"Preview Font Family": "Preview Font Family",
|
"Preview Font Size": "プレビュー時フォントサイズ",
|
||||||
"Code block Theme": "Code block Theme",
|
"Preview Font Family": "プレビュー時フォント",
|
||||||
"Allow preview to scroll past the last line": "Allow preview to scroll past the last line",
|
"Code block Theme": "コードブロックのテーマ",
|
||||||
"Show line numbers for preview code blocks": "Show line numbers for preview code blocks",
|
"Allow preview to scroll past the last line": "プレビュー時に最終行以降にスクロールできるようにする",
|
||||||
"LaTeX Inline Open Delimiter": "LaTeX Inline Open Delimiter",
|
"Show line numbers for preview code blocks": "プレビュー時のコードブロック内に行番号を表示する",
|
||||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
"LaTeX Inline Open Delimiter": "LaTeX 開始デリミタ(インライン)",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
"LaTeX Inline Close Delimiter": "LaTeX 終了デリミタ(インライン)",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
"LaTeX Block Open Delimiter": "LaTeX 開始デリミタ(ブロック)",
|
||||||
"Community": "Community",
|
"LaTeX Block Close Delimiter": "LaTeX 終了デリミタ(ブロック)",
|
||||||
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
"PlantUML Server": "PlantUML サーバー",
|
||||||
|
"Community": "コミュニティ",
|
||||||
|
"Subscribe to Newsletter": "ニュースレターを購読する",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
"Blog": "Blog",
|
"Blog": "ブログ",
|
||||||
"Facebook Group": "Facebook Group",
|
"Facebook Group": "Facebook グループ",
|
||||||
"Twitter": "Twitter",
|
"Twitter": "Twitter",
|
||||||
"About": "About",
|
"About": "Boostnote について",
|
||||||
"Boostnote": "Boostnote",
|
"Boostnote": "Boostnote",
|
||||||
"An open source note-taking app made for programmers just like you.": "An open source note-taking app made for programmers just like you.",
|
"An open source note-taking app made for programmers just like you.": "あなたのようなプログラマー向けのオープンソースメモ書きアプリケーション",
|
||||||
"Website": "Website",
|
"Website": "ウェブサイト",
|
||||||
"Development": "Development",
|
"Development": "開発",
|
||||||
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
|
" : Development configurations for Boostnote.": " : Boostnote の開発構成",
|
||||||
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
|
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
|
||||||
"License: GPL v3": "License: GPL v3",
|
"License: GPL v3": "ライセンス: GPL v3",
|
||||||
"Analytics": "Analytics",
|
"Analytics": "解析",
|
||||||
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.",
|
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote はアプリケーションの機能向上だけを目的に匿名データを収集します。ノートの内容を含めた個人の情報は一切収集しません。",
|
||||||
"You can see how it works on ": "You can see how it works on ",
|
"You can see how it works on ": "どのように動くかをこちらで確認できます ",
|
||||||
"You can choose to enable or disable this option.": "You can choose to enable or disable this option.",
|
"You can choose to enable or disable this option.": "このオプションは有効/無効を選択できます。",
|
||||||
"Enable analytics to help improve Boostnote": "Enable analytics to help improve Boostnote",
|
"Enable analytics to help improve Boostnote": "Boostnote の機能向上のための解析機能を有効にする",
|
||||||
"Crowdfunding": "Crowdfunding",
|
"Crowdfunding": "クラウドファンディング",
|
||||||
"Dear everyone,": "Dear everyone,",
|
"Dear everyone,": "みなさまへ",
|
||||||
"Thank you for using Boostnote!": "Thank you for using Boostnote!",
|
"Thank you for using Boostnote!": "Boostnote を利用いただき、ありがとうございます!",
|
||||||
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote is used in about 200 different countries and regions by an awesome community of developers.",
|
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote はおよそ 200 の国と地域において、開発者コミュニティを中心に利用されています。",
|
||||||
"To continue supporting this growth, and to satisfy community expectations,": "To continue supporting this growth, and to satisfy community expectations,",
|
"To continue supporting this growth, and to satisfy community expectations,": "この成長を持続し、またコミュニティからの要望に答えるため、",
|
||||||
"we would like to invest more time and resources in this project.": "we would like to invest more time and resources in this project.",
|
"we would like to invest more time and resources in this project.": "私達はこのプロジェクトにより多くの時間とリソースを投資したいと考えています。",
|
||||||
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "If you like this project and see its potential, you can help by supporting us on OpenCollective!",
|
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "もしあなたがこのプロジェクトとそのポテンシャルを気に入っていただけたのであれば、OpenCollective を通じて支援いただくことができます!",
|
||||||
"Thanks,": "Thanks,",
|
"Thanks,": "ありがとうございます。",
|
||||||
"Boostnote maintainers": "Boostnote maintainers",
|
"Boostnote maintainers": "Boostnote メンテナンスチーム",
|
||||||
"Support via OpenCollective": "Support via OpenCollective",
|
"Support via OpenCollective": "OpenCollective を通じて支援します",
|
||||||
"Language": "Language",
|
"Language": "言語",
|
||||||
"English": "English",
|
"English": "英語",
|
||||||
"German": "German",
|
"German": "ドイツ語",
|
||||||
"French": "French",
|
"French": "フランス語",
|
||||||
"Show \"Saved to Clipboard\" notification when copying": "Show \"Saved to Clipboard\" notification when copying",
|
"Show \"Saved to Clipboard\" notification when copying": "クリップボードコピー時に \"クリップボードに保存\" 通知を表示する",
|
||||||
"All Notes": "All Notes",
|
"All Notes": "すべてのノート",
|
||||||
"Starred": "Starred",
|
"Starred": "スター付き",
|
||||||
"Are you sure to ": "Are you sure to ",
|
"Are you sure to ": "本当に ",
|
||||||
" delete": " delete",
|
" delete": "このフォルダを",
|
||||||
"this folder?": "this folder?",
|
"this folder?": "削除しますか?",
|
||||||
"Confirm": "Confirm",
|
"Confirm": "確認",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "キャンセル",
|
||||||
"Markdown Note": "Markdown Note",
|
"Markdown Note": "マークダウン",
|
||||||
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.",
|
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "このフォーマットはテキスト文書を作成することを目的としています。チェックリストや比較的長いコード、LaTeX にも向いています。",
|
||||||
"Snippet Note": "Snippet Note",
|
"Snippet Note": "スニペット",
|
||||||
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "This format is for creating code snippets. Multiple snippets can be grouped into a single note.",
|
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "このフォーマットは短いコードスニペットを作成することを目的としています。複数のコードスニペットを1つのグループにまとめて1つのノートとして扱うことも可能です。",
|
||||||
"Tab to switch format": "Tab to switch format",
|
"Tab to switch format": "フォーマット切り替えタブ",
|
||||||
"Updated": "Updated",
|
"Updated": "更新日時",
|
||||||
"Created": "Created",
|
"Created": "作成日時",
|
||||||
"Alphabetically": "Alphabetically",
|
"Alphabetically": "アルファベット順",
|
||||||
"Default View": "Default View",
|
"Counter": "数順",
|
||||||
"Compressed View": "Compressed View",
|
"Default View": "デフォルトビュー",
|
||||||
"Search": "Search",
|
"Compressed View": "圧縮ビュー",
|
||||||
"Blog Type": "Blog Type",
|
"Search": "検索",
|
||||||
"Blog Address": "Blog Address",
|
"Blog Type": "ブログの種類",
|
||||||
"Save": "Save",
|
"Blog Address": "ブログのアドレス",
|
||||||
"Auth": "Auth",
|
"Save": "保存",
|
||||||
"Authentication Method": "Authentication Method",
|
"Auth": "認証",
|
||||||
|
"Authentication Method": "認証方法",
|
||||||
"JWT": "JWT",
|
"JWT": "JWT",
|
||||||
"USER": "USER",
|
"USER": "ユーザー",
|
||||||
"Token": "Token",
|
"Token": "トークン",
|
||||||
"Storage": "Storage",
|
"Storage": "ストレージ",
|
||||||
"Hotkeys": "Hotkeys",
|
"Hotkeys": "ホットキー",
|
||||||
"Show/Hide Boostnote": "Show/Hide Boostnote",
|
"Show/Hide Boostnote": "Boostnote の表示/非表示",
|
||||||
"Restore": "Restore",
|
"Toggle editor mode": "エディタモードの切替",
|
||||||
"Permanent Delete": "Permanent Delete",
|
"Restore": "リストア",
|
||||||
"Confirm note deletion": "Confirm note deletion",
|
"Permanent Delete": "永久に削除",
|
||||||
"This will permanently remove this note.": "This will permanently remove this note.",
|
"Confirm note deletion": "ノート削除確認",
|
||||||
"Successfully applied!": "Successfully applied!",
|
"This will permanently remove this note.": "本当にこのノートを削除します。",
|
||||||
"Albanian": "Albanian",
|
"Successfully applied!": "成功しました!",
|
||||||
"Chinese (zh-CN)": "Chinese (zh-CN)",
|
"Albanian": "アルバニア語",
|
||||||
"Chinese (zh-TW)": "Chinese (zh-TW)",
|
"Chinese (zh-CN)": "簡体字中国語 (zh-CN)",
|
||||||
"Danish": "Danish",
|
"Chinese (zh-TW)": "繁体字中国語 (zh-TW)",
|
||||||
"Japanese": "Japanese",
|
"Danish": "デンマーク語",
|
||||||
"Korean": "Korean",
|
"Japanese": "日本語",
|
||||||
"Norwegian": "Norwegian",
|
"Korean": "韓国語",
|
||||||
"Polish": "Polish",
|
"Norwegian": "ノルウェー語",
|
||||||
"Portuguese": "Portuguese",
|
"Polish": "ポーランド語",
|
||||||
"Spanish": "Spanish",
|
"Portuguese": "ポルトガル語",
|
||||||
"You have to save!": "You have to save!",
|
"Spanish": "スペイン語",
|
||||||
"Russian": "Russian",
|
"You have to save!": "保存してください!",
|
||||||
"Command(⌘)": "Command(⌘)",
|
"UserName": "ユーザー名",
|
||||||
"Editor Rulers": "Editor Rulers",
|
"Password": "パスワード",
|
||||||
"Enable": "Enable",
|
"Russian": "ロシア語",
|
||||||
"Disable": "Disable",
|
"Hungarian": "ハンガリー語",
|
||||||
"Sanitization": "Sanitization",
|
"Command(⌘)": "コマンド(⌘)",
|
||||||
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
"Add Storage": "ストレージを追加",
|
||||||
"Allow styles": "Allow styles",
|
"Name": "名前",
|
||||||
"Allow dangerous html tags": "Allow dangerous html tags"
|
"Type": "種類",
|
||||||
|
"File System": "ファイルシステム",
|
||||||
|
"Setting up 3rd-party cloud storage integration:": "サードパーティのクラウドストレージとの統合を設定する:",
|
||||||
|
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
|
||||||
|
"Location": "ロケーション",
|
||||||
|
"Add": "追加",
|
||||||
|
"Unlink Storage": "ストレージのリンクを解除",
|
||||||
|
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "リンクの解除ではBoostnoteからリンクされたストレージを削除しますが、データは削除されません。データを削除する場合はご自身でハードドライブからフォルダを削除してください。",
|
||||||
|
"Editor Rulers": "罫線",
|
||||||
|
"Enable": "有効",
|
||||||
|
"Disable": "無効",
|
||||||
|
"Sanitization": "サニタイズ",
|
||||||
|
"Only allow secure html tags (recommended)": "安全なHTMLタグのみ利用を許可する(推奨)",
|
||||||
|
"Render newlines in Markdown paragraphs as <br>": "Markdown 中の改行でプレビューも改行する",
|
||||||
|
"Allow styles": "スタイルを許可する",
|
||||||
|
"Allow dangerous html tags": "安全でないHTMLタグの利用を許可する",
|
||||||
|
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "テキストの矢印を綺麗な記号に変換する ⚠ この設定はMarkdown内でのHTMLコメントに干渉します。",
|
||||||
|
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ このノートのストレージに存在しない添付ファイルへのリンクを貼り付けました。添付ファイルへのリンクの貼り付けは同一ストレージ内でのみサポートされています。代わりに添付ファイルをドラッグアンドドロップしてください! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX 인라인 블록 닫기 기호",
|
"LaTeX Inline Close Delimiter": "LaTeX 인라인 블록 닫기 기호",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX 블록 열기 기호",
|
"LaTeX Block Open Delimiter": "LaTeX 블록 열기 기호",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX 블록 닫기 기호",
|
"LaTeX Block Close Delimiter": "LaTeX 블록 닫기 기호",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "커뮤니티",
|
"Community": "커뮤니티",
|
||||||
"Subscribe to Newsletter": "뉴스레터 구독",
|
"Subscribe to Newsletter": "뉴스레터 구독",
|
||||||
"GitHub": "깃허브",
|
"GitHub": "깃허브",
|
||||||
@@ -143,7 +144,7 @@
|
|||||||
"You have to save!": "저장해주세요!",
|
"You have to save!": "저장해주세요!",
|
||||||
"Russian": "Russian",
|
"Russian": "Russian",
|
||||||
"Command(⌘)": "Command(⌘)",
|
"Command(⌘)": "Command(⌘)",
|
||||||
"Delete Folder": "폴더 삭게",
|
"Delete Folder": "폴더 삭제",
|
||||||
"This will delete all notes in the folder and can not be undone.": "폴더의 모든 노트를 지우게 되고, 되돌릴 수 없습니다.",
|
"This will delete all notes in the folder and can not be undone.": "폴더의 모든 노트를 지우게 되고, 되돌릴 수 없습니다.",
|
||||||
"UserName": "유저명",
|
"UserName": "유저명",
|
||||||
"Password": "패스워드",
|
"Password": "패스워드",
|
||||||
@@ -155,5 +156,7 @@
|
|||||||
"Sanitization": "허용 태그 범위",
|
"Sanitization": "허용 태그 범위",
|
||||||
"Only allow secure html tags (recommended)": "안전한 HTML 태그만 허용 (추천)",
|
"Only allow secure html tags (recommended)": "안전한 HTML 태그만 허용 (추천)",
|
||||||
"Allow styles": "style 태그, 속성까지 허용",
|
"Allow styles": "style 태그, 속성까지 허용",
|
||||||
"Allow dangerous html tags": "모든 위험한 태그 허용"
|
"Allow dangerous html tags": "모든 위험한 태그 허용",
|
||||||
|
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
|
||||||
|
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Community",
|
"Community": "Community",
|
||||||
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
@@ -148,5 +149,7 @@
|
|||||||
"Sanitization": "Sanitization",
|
"Sanitization": "Sanitization",
|
||||||
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
|
||||||
"Allow styles": "Allow styles",
|
"Allow styles": "Allow styles",
|
||||||
"Allow dangerous html tags": "Allow dangerous html tags"
|
"Allow dangerous html tags": "Allow dangerous html tags",
|
||||||
|
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
|
||||||
|
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user