1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-14 18:26:26 +00:00

Compare commits

..

5 Commits

Author SHA1 Message Date
Masahide Morio
9d98f0cb03 for v0.9 2018-02-04 05:51:39 +09:00
Masahide Morio
3503233631 Merge remote-tracking branch 'upstream/master' 2018-02-04 05:40:24 +09:00
Masahide Morio
c39393c453 for Windows 32bit 2018-02-04 05:40:15 +09:00
Masahide Morio
564cc80ef7 for Windows 32bit 2018-01-17 01:01:45 +09:00
Masahide.MORIO
77f7144fbf test 2018-01-16 10:01:20 +09:00
308 changed files with 5036 additions and 245098 deletions

View File

@@ -5,9 +5,9 @@
"presets": ["react-hmre"] "presets": ["react-hmre"]
}, },
"test": { "test": {
"presets": ["env" ,"react", "es2015"], "presets": ["react", "es2015"],
"plugins": [ "plugins": [
[ "babel-plugin-webpack-alias", { "config": "<rootDir>/webpack.config.js" } ] [ "babel-plugin-webpack-alias", { "config": "${PWD}/webpack.config.js" } ]
] ]
} }
} }

View File

@@ -22,10 +22,7 @@
"fontSize": "14", "fontSize": "14",
"lineNumber": true "lineNumber": true
}, },
"sortBy": { "sortBy": "UPDATED_AT",
"default": "UPDATED_AT"
},
"sortTagsBy": "ALPHABETICAL",
"ui": { "ui": {
"defaultNote": "ALWAYS_ASK", "defaultNote": "ALWAYS_ASK",
"disableDirectWrite": false, "disableDirectWrite": false,

View File

@@ -1,16 +0,0 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Space indentation
[*]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
# The indent size used in the `package.json` file cannot be changed
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
[{*.yml,*.yaml,package.json}]
indent_style = space
indent_size = 2

View File

@@ -1,4 +1,3 @@
node_modules/ node_modules/
compiled/ compiled/
dist/ dist/
extra_scripts/

View File

@@ -3,9 +3,7 @@
"plugins": ["react"], "plugins": ["react"],
"rules": { "rules": {
"no-useless-escape": 0, "no-useless-escape": 0,
"prefer-const": ["warn", { "prefer-const": "warn",
"destructuring": "all"
}],
"no-unused-vars": "warn", "no-unused-vars": "warn",
"no-undef": "warn", "no-undef": "warn",
"no-lone-blocks": "warn", "no-lone-blocks": "warn",
@@ -18,11 +16,6 @@
"globals": { "globals": {
"FileReader": true, "FileReader": true,
"localStorage": true, "localStorage": true,
"fetch": true, "fetch": true
"Image": true,
"MutationObserver": true
},
"env": {
"jest": true
} }
} }

1
.github/FUNDING.yml vendored
View File

@@ -1 +0,0 @@
issuehunt: BoostIo/Boostnote

3
.gitignore vendored
View File

@@ -9,5 +9,4 @@ node_modules/*
/secret /secret
*.log *.log
.idea .idea
.vscode .vscode
package-lock.json

View File

@@ -1,9 +1,9 @@
language: node_js language: node_js
node_js: node_js:
- 8 - 6
script: script:
- npm run lint && npm run test - npm run lint && npm run test
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@6.4 && grunt pre-build; fi' - 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
after_success: after_success:
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv - openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
-in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d -in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d
@@ -17,3 +17,4 @@ deploy:
script: if [ ${TRAVIS_NODE_VERSION} = "stable" ];then docker run -v $(pwd):$(pwd) -t snapcore/snapcraft sh -c "apt update -qq script: if [ ${TRAVIS_NODE_VERSION} = "stable" ];then docker run -v $(pwd):$(pwd) -t snapcore/snapcraft sh -c "apt update -qq
&& cd $(pwd) && snapcraft && snapcraft push *.snap --release edge"; fi && cd $(pwd) && snapcraft && snapcraft push *.snap --release edge"; fi
skip_cleanup: true skip_cleanup: true

41
.vscode/launch.json vendored
View File

@@ -1,41 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "BoostNote Main",
"protocol": "inspector",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"runtimeArgs": [
"--remote-debugging-port=9223",
"--hot",
"${workspaceFolder}/index.js"
],
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
}
},
{
"type": "chrome",
"request": "attach",
"name": "BoostNote Renderer",
"port": 9223,
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack:///./~/*": "${webRoot}/node_modules/*",
"webpack:///*": "${webRoot}/*"
}
}
],
"compounds": [
{
"name": "BostNote All",
"configurations": ["BoostNote Main", "BoostNote Renderer"]
}
]
}

27
.vscode/tasks.json vendored
View File

@@ -1,27 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Build Boostnote",
"group": "build",
"type": "npm",
"script": "watch",
"isBackground": true,
"presentation": {
"reveal": "always",
},
"problemMatcher": {
"pattern":[
{
"regexp": "^([^\\\\s].*)\\\\((\\\\d+,\\\\d+)\\\\):\\\\s*(.*)$",
"file": 1,
"location": 2,
"message": 3
}
]
}
}
]
}

72
Backers.md Normal file
View File

@@ -0,0 +1,72 @@
<h1 align="center">Sponsors &amp; Backers</h1>
Boostnote is an open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome backers. If you'd like to join them, please consider:
- [Become a backer or sponsor on Open Collective.](https://opencollective.com/boostnoteio)
---
## Backers via OpenCollective
### [Gold Sponsors / $1,000 per month](https://opencollective.com/boostnoteio/order/2259)
- Get your logo on our Readme.md on GitHub and the frontpage of https://boostnote.io/.
### [Silver Sponsors / $250 per month](https://opencollective.com/boostnoteio/order/2257)
- Get your logo on our Readme.md on GitHub and the frontpage of https://boostnote.io/.
### [Bronze Sponsors / $50 per month](https://opencollective.com/boostnoteio/order/2258)
- Get your name and Url (or E-mail) on Readme.md on GitHub.
### [Backers3 / $10 per month](https://opencollective.com/boostnoteio/order/2176)
- [Ralph03](https://opencollective.com/ralph03)
- [Nikolas Dan](https://opencollective.com/nikolas-dan)
### [Backers2 / $5 per month](https://opencollective.com/boostnoteio/order/2175)
- [Yeojong Kim](https://twitter.com/yeojoy)
- [Scotia Draven](https://opencollective.com/scotia-draven)
- [A. J. Vargas](https://opencollective.com/aj-vargas)
### [Backers1](https://opencollective.com/boostnoteio/order/2563) and One-time sponsors
- Ryosuke Tamura - $30
- tatoosh11 - $10
- Alexander Borovkov - $10
- spoonhoop - $5
- Drew Williams - $2
- Andy Shaw - $2
- mysafesky -$2
---
## Backers via Bountysource
https://salt.bountysource.com/teams/boostnote
- Kuzz - $65
- Intense Raiden - $45
- ravy22 - $25
- trentpolack - $20
- hikariru - $10
- kolchan11 - $10
- RonWalker22 - $10
- hocchuc - $5
- Adam - $5
- Steve - $5
- evmin - $5

29
FAQ.md
View File

@@ -1,29 +0,0 @@
# Frequently Asked Questions
<details><summary>Allowing dangerous HTML tags</summary>
Sometimes it is useful to allow dangerous HTML tags to add interactivity to your notebook. One of the example is to use details/summary as a way to expand/collaps your todo-list.
* How to enable:
* Go to **Preferences****Interface****Sanitization****Allow dangerous html tags**
* Example note: Multiple todo-list
* Create new notes
* Paste the below code, and you'll see that you can expand/collaps the todo-list, and you can have multiple todo-list in your note.
```html
<details><summary>What I want to do</summary>
- [x] Create an awesome feature X
- [ ] Do my homework
</details>
```
</details>
## Other questions
You can ask [here][ISSUES]
[ISSUES]: https://github.com/BoostIO/Boostnote/issues

View File

@@ -1,35 +1,10 @@
# Current behavior <!--
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.
-->
<!-- <!--
Let us know what is currently happening. Love Boostnote? Please consider supporting us via OpenCollective:
👉 https://opencollective.com/boostnoteio
Please include some **screenshots** with the **developer tools** open (console tab) when you report a bug.
If your issue is regarding Boostnote mobile, please open an issue in the Boostnote Mobile repo 👉 https://github.com/BoostIO/boostnote-mobile.
-->
# Expected behavior
<!--
Let us know what you think should happen!
-->
# Steps to reproduce
<!--
Please be thorough, issues we can reproduce are easier to fix!
-->
1.
2.
3.
# Environment
- Version :
- OS Version and name :
<!--
Love Boostnote? Please consider supporting us on IssueHunt:
👉 https://issuehunt.io/repos/53266139
--> -->

View File

@@ -2,7 +2,7 @@ GPL-3.0
Boostnote - an open source note-taking app made for programmers just like you. Boostnote - an open source note-taking app made for programmers just like you.
Copyright (C) 2017 - 2019 BoostIO Copyright (C) 2017 Maisin&Co., Inc.
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

View File

@@ -1,36 +0,0 @@
<!--
Before submitting this PR, please make sure that:
- You have read and understand the contributing.md
- You have checked docs/code_style.md for information on code style
-->
## Description
<!--
Tell us what your PR does.
Please attach a screenshot/ video/gif image describing your PR if possible.
-->
## Issue fixed
<!--
Please list out all issue fixed with this PR here.
-->
<!--
Please make sure you fill in these checkboxes,
your PR will be reviewed faster if we know exactly what it does.
Change :white_circle: to :radio_button: in all the options that apply
-->
## Type of changes
- :white_circle: Bug fix (Change that fixed an issue)
- :white_circle: Breaking change (Change that can cause existing functionality to change)
- :white_circle: Improvement (Change that improves the code. Maybe performance or development improvement)
- :white_circle: Feature (Change that adds new functionality)
- :white_circle: Documentation change (Change that modifies documentation. Maybe typo fixes)
## Checklist:
- :white_circle: My code follows [the project code style](docs/code_style.md)
- :white_circle: I have written test for my code and it has been tested
- :white_circle: All existing tests have been passed
- :white_circle: I have attached a screenshot/video to visualize my change if possible

View File

@@ -1,7 +0,0 @@
module.exports = {
require: jest.genMockFunction(),
match: jest.genMockFunction(),
app: jest.genMockFunction(),
remote: jest.genMockFunction(),
dialog: jest.genMockFunction()
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +0,0 @@
.codeEditor-typo
text-decoration underline wavy red
.spellcheck-select
border: none

View File

@@ -1,68 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { SketchPicker } from 'react-color'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ColorPicker.styl'
const componentHeight = 330
class ColorPicker extends React.Component {
constructor (props) {
super(props)
this.state = {
color: this.props.color || '#939395'
}
this.onColorChange = this.onColorChange.bind(this)
this.handleConfirm = this.handleConfirm.bind(this)
}
componentWillReceiveProps (nextProps) {
this.onColorChange(nextProps.color)
}
onColorChange (color) {
this.setState({
color
})
}
handleConfirm () {
this.props.onConfirm(this.state.color)
}
render () {
const { onReset, onCancel, targetRect } = this.props
const { color } = this.state
const clientHeight = document.body.clientHeight
const alignX = targetRect.right + 4
let alignY = targetRect.top
if (targetRect.top + componentHeight > clientHeight) {
alignY = targetRect.bottom - componentHeight
}
return (
<div styleName='colorPicker' style={{top: `${alignY}px`, left: `${alignX}px`}}>
<div styleName='cover' onClick={onCancel} />
<SketchPicker color={color} onChange={this.onColorChange} />
<div styleName='footer'>
<button styleName='btn-reset' onClick={onReset}>Reset</button>
<button styleName='btn-cancel' onClick={onCancel}>Cancel</button>
<button styleName='btn-confirm' onClick={this.handleConfirm}>Confirm</button>
</div>
</div>
)
}
}
ColorPicker.propTypes = {
color: PropTypes.string,
targetRect: PropTypes.object,
onConfirm: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
onReset: PropTypes.func.isRequired
}
export default CSSModules(ColorPicker, styles)

View File

@@ -1,39 +0,0 @@
.colorPicker
position fixed
z-index 2
display flex
flex-direction column
.cover
position fixed
top 0
right 0
bottom 0
left 0
.footer
display flex
justify-content center
z-index 2
align-items center
& > button + button
margin-left 10px
.btn-cancel,
.btn-confirm,
.btn-reset
vertical-align middle
height 25px
margin-top 2.5px
border-radius 2px
border none
padding 0 5px
background-color $default-button-background
&:hover
background-color $default-button-background--hover
.btn-confirm
background-color #1EC38B
&:hover
background-color darken(#1EC38B, 25%)

View File

@@ -6,8 +6,6 @@ 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'
import ConfigManager from 'browser/main/lib/ConfigManager'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
class MarkdownEditor extends React.Component { class MarkdownEditor extends React.Component {
constructor (props) { constructor (props) {
@@ -20,10 +18,10 @@ class MarkdownEditor extends React.Component {
this.supportMdSelectionBold = [16, 17, 186] this.supportMdSelectionBold = [16, 17, 186]
this.state = { this.state = {
status: props.config.editor.switchPreview === 'RIGHTCLICK' ? props.config.editor.delfaultStatus : 'CODE', status: 'PREVIEW',
renderValue: props.value, renderValue: props.value,
keyPressed: new Set(), keyPressed: new Set(),
isLocked: props.isLocked isLocked: false
} }
this.lockEditorCode = () => this.handleLockEditor() this.lockEditorCode = () => this.handleLockEditor()
@@ -32,7 +30,6 @@ class MarkdownEditor extends React.Component {
componentDidMount () { componentDidMount () {
this.value = this.refs.code.value this.value = this.refs.code.value
eventEmitter.on('editor:lock', this.lockEditorCode) eventEmitter.on('editor:lock', this.lockEditorCode)
eventEmitter.on('editor:focus', this.focusEditor.bind(this))
} }
componentDidUpdate () { componentDidUpdate () {
@@ -48,15 +45,6 @@ class MarkdownEditor extends React.Component {
componentWillUnmount () { componentWillUnmount () {
this.cancelQueue() this.cancelQueue()
eventEmitter.off('editor:lock', this.lockEditorCode) eventEmitter.off('editor:lock', this.lockEditorCode)
eventEmitter.off('editor:focus', this.focusEditor.bind(this))
}
focusEditor () {
this.setState({
status: 'CODE'
}, () => {
this.refs.code.focus()
})
} }
queueRendering (value) { queueRendering (value) {
@@ -76,20 +64,17 @@ class MarkdownEditor extends React.Component {
}) })
} }
setValue (value) {
this.refs.code.setValue(value)
}
handleChange (e) { handleChange (e) {
this.value = this.refs.code.value this.value = this.refs.code.value
this.props.onChange(e) this.props.onChange(e)
} }
handleContextMenu (e) { handleContextMenu (e) {
if (this.state.isLocked) return
const { config } = this.props const { config } = this.props
if (config.editor.switchPreview === 'RIGHTCLICK') { if (config.editor.switchPreview === 'RIGHTCLICK') {
const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW' const newStatus = this.state.status === 'PREVIEW'
? 'CODE'
: 'PREVIEW'
this.setState({ this.setState({
status: newStatus status: newStatus
}, () => { }, () => {
@@ -99,10 +84,6 @@ class MarkdownEditor extends React.Component {
this.refs.preview.focus() this.refs.preview.focus()
} }
eventEmitter.emit('topbar:togglelockbutton', this.state.status) eventEmitter.emit('topbar:togglelockbutton', this.state.status)
const newConfig = Object.assign({}, config)
newConfig.editor.delfaultStatus = newStatus
ConfigManager.set(newConfig)
}) })
} }
} }
@@ -111,9 +92,7 @@ class MarkdownEditor extends React.Component {
if (this.state.isLocked) return if (this.state.isLocked) return
this.setState({ keyPressed: new Set() }) this.setState({ keyPressed: new Set() })
const { config } = this.props const { config } = this.props
if (config.editor.switchPreview === 'BLUR' || if (config.editor.switchPreview === 'BLUR') {
(config.editor.switchPreview === 'DBL_CLICK' && this.state.status === 'CODE')
) {
const cursorPosition = this.refs.code.editor.getCursor() const cursorPosition = this.refs.code.editor.getCursor()
this.setState({ this.setState({
status: 'PREVIEW' status: 'PREVIEW'
@@ -125,20 +104,6 @@ class MarkdownEditor extends React.Component {
} }
} }
handleDoubleClick (e) {
if (this.state.isLocked) return
this.setState({keyPressed: new Set()})
const { config } = this.props
if (config.editor.switchPreview === 'DBL_CLICK') {
this.setState({
status: 'CODE'
}, () => {
this.refs.code.focus()
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
})
}
}
handlePreviewMouseDown (e) { handlePreviewMouseDown (e) {
this.previewMouseDownedAt = new Date() this.previewMouseDownedAt = new Date()
} }
@@ -159,25 +124,22 @@ class MarkdownEditor extends React.Component {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
const idMatch = /checkbox-([0-9]+)/ const idMatch = /checkbox-([0-9]+)/
const checkedMatch = /^(\s*>?)*\s*[+\-*] \[x]/i const checkedMatch = /\[x\]/i
const uncheckedMatch = /^(\s*>?)*\s*[+\-*] \[ ]/ const uncheckedMatch = /\[ \]/
const checkReplace = /\[x]/i
const uncheckReplace = /\[ ]/
if (idMatch.test(e.target.getAttribute('id'))) { if (idMatch.test(e.target.getAttribute('id'))) {
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1 const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
const lines = this.refs.code.value const lines = this.refs.code.value
.split('\n') .split('\n')
const targetLine = lines[lineIndex] const targetLine = lines[lineIndex]
let newLine = targetLine
if (targetLine.match(checkedMatch)) { if (targetLine.match(checkedMatch)) {
newLine = targetLine.replace(checkReplace, '[ ]') lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
} }
if (targetLine.match(uncheckedMatch)) { if (targetLine.match(uncheckedMatch)) {
newLine = targetLine.replace(uncheckReplace, '[x]') lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]')
} }
this.refs.code.setLineContent(lineIndex, newLine) this.refs.code.setValue(lines.join('\n'))
} }
} }
@@ -234,28 +196,6 @@ class MarkdownEditor extends React.Component {
this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`) this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`)
} }
handleDropImage (dropEvent) {
dropEvent.preventDefault()
const { storageKey, noteKey } = this.props
this.setState({
status: 'CODE'
}, () => {
this.refs.code.focus()
this.refs.code.editor.execCommand('goDocEnd')
this.refs.code.editor.execCommand('goLineEnd')
this.refs.code.editor.execCommand('newlineAndIndent')
attachmentManagement.handleAttachmentDrop(
this.refs.code,
storageKey,
noteKey,
dropEvent
)
})
}
handleKeyUp (e) { handleKeyUp (e) {
const keyPressed = this.state.keyPressed const keyPressed = this.state.keyPressed
keyPressed.delete(e.keyCode) keyPressed.delete(e.keyCode)
@@ -267,7 +207,7 @@ class MarkdownEditor extends React.Component {
} }
render () { render () {
const {className, value, config, storageKey, noteKey, linesHighlighted} = this.props const { className, value, config, storageKey } = 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
@@ -294,7 +234,7 @@ class MarkdownEditor extends React.Component {
: 'codeEditor--hide' : 'codeEditor--hide'
} }
ref='code' ref='code'
mode='Boost Flavored Markdown' mode='GitHub Flavored Markdown'
value={value} value={value}
theme={config.editor.theme} theme={config.editor.theme}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
@@ -302,29 +242,11 @@ class MarkdownEditor extends React.Component {
fontSize={editorFontSize} fontSize={editorFontSize}
indentType={config.editor.indentType} indentType={config.editor.indentType}
indentSize={editorIndentSize} indentSize={editorIndentSize}
enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
lineWrapping
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey} storageKey={storageKey}
noteKey={noteKey}
fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
linesHighlighted={linesHighlighted}
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)} onBlur={(e) => this.handleBlur(e)}
spellCheck={config.editor.spellcheck}
enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey}
switchPreview={config.editor.switchPreview}
enableMarkdownLint={config.editor.enableMarkdownLint}
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
prettierConfig={config.editor.prettierConfig}
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
/> />
<MarkdownPreview styleName={this.state.status === 'PREVIEW' <MarkdownPreview styleName={this.state.status === 'PREVIEW'
? 'preview' ? 'preview'
@@ -340,14 +262,8 @@ class MarkdownEditor extends React.Component {
lineNumber={config.preview.lineNumber} lineNumber={config.preview.lineNumber}
indentSize={editorIndentSize} indentSize={editorIndentSize}
scrollPastEnd={config.preview.scrollPastEnd} scrollPastEnd={config.preview.scrollPastEnd}
smartQuotes={config.preview.smartQuotes}
smartArrows={config.preview.smartArrows}
breaks={config.preview.breaks}
sanitize={config.preview.sanitize}
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
ref='preview' ref='preview'
onContextMenu={(e) => this.handleContextMenu(e)} onContextMenu={(e) => this.handleContextMenu(e)}
onDoubleClick={(e) => this.handleDoubleClick(e)}
tabIndex='0' tabIndex='0'
value={this.state.renderValue} value={this.state.renderValue}
onMouseUp={(e) => this.handlePreviewMouseUp(e)} onMouseUp={(e) => this.handlePreviewMouseUp(e)}
@@ -355,11 +271,6 @@ 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}
lineThroughCheckbox={config.preview.lineThroughCheckbox}
onDrop={(e) => this.handleDropImage(e)}
/> />
</div> </div>
) )

View File

@@ -16,6 +16,7 @@
.preview .preview
display block display block
absolute top bottom left right absolute top bottom left right
z-index 100
background-color white background-color white
height 100% height 100%
width 100% width 100%

1031
browser/components/MarkdownPreview.js Executable file → Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,6 @@ import React from 'react'
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 { findStorage } from 'browser/lib/findStorage' import { findStorage } from 'browser/lib/findStorage'
import _ from 'lodash'
import styles from './MarkdownSplitEditor.styl' import styles from './MarkdownSplitEditor.styl'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
@@ -13,182 +12,66 @@ class MarkdownSplitEditor extends React.Component {
this.value = props.value this.value = props.value
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.state = {
isSliderFocused: false,
codeEditorWidthInPercent: 50
}
} }
setValue (value) { handleOnChange () {
this.refs.code.setValue(value)
}
handleOnChange (e) {
this.value = this.refs.code.value this.value = this.refs.code.value
this.props.onChange(e) this.props.onChange()
}
handleScroll (e) {
if (!this.props.config.preview.scrollSync) return
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
const codeDoc = _.get(this, 'refs.code.editor.doc')
let srcTop, srcHeight, targetTop, targetHeight
if (this.userScroll) {
if (e.doc) {
srcTop = _.get(e, 'doc.scrollTop')
srcHeight = _.get(e, 'doc.height')
targetTop = _.get(previewDoc, 'body.scrollTop')
targetHeight = _.get(previewDoc, 'body.scrollHeight')
} else {
srcTop = _.get(previewDoc, 'body.scrollTop')
srcHeight = _.get(previewDoc, 'body.scrollHeight')
targetTop = _.get(codeDoc, 'scrollTop')
targetHeight = _.get(codeDoc, 'height')
}
const distance = (targetHeight * srcTop / srcHeight) - targetTop
const framerate = 1000 / 60
const frames = 20
const refractory = frames * framerate
this.userScroll = false
let frame = 0
let scrollPos, time
const timer = setInterval(() => {
time = frame / frames
scrollPos = time < 0.5
? 2 * time * time // ease in
: -1 + (4 - 2 * time) * time // ease out
if (e.doc) _.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
else _.get(this, 'refs.code.editor').scrollTo(0, targetTop + scrollPos * distance)
if (frame >= frames) {
clearInterval(timer)
setTimeout(() => { this.userScroll = true }, refractory)
}
frame++
}, framerate)
}
} }
handleCheckboxClick (e) { handleCheckboxClick (e) {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
const idMatch = /checkbox-([0-9]+)/ const idMatch = /checkbox-([0-9]+)/
const checkedMatch = /^(\s*>?)*\s*[+\-*] \[x]/i const checkedMatch = /\[x\]/i
const uncheckedMatch = /^(\s*>?)*\s*[+\-*] \[ ]/ const uncheckedMatch = /\[ \]/
const checkReplace = /\[x]/i
const uncheckReplace = /\[ ]/
if (idMatch.test(e.target.getAttribute('id'))) { if (idMatch.test(e.target.getAttribute('id'))) {
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1 const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
const lines = this.refs.code.value const lines = this.refs.code.value
.split('\n') .split('\n')
const targetLine = lines[lineIndex] const targetLine = lines[lineIndex]
let newLine = targetLine
if (targetLine.match(checkedMatch)) { if (targetLine.match(checkedMatch)) {
newLine = targetLine.replace(checkReplace, '[ ]') lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
} }
if (targetLine.match(uncheckedMatch)) { if (targetLine.match(uncheckedMatch)) {
newLine = targetLine.replace(uncheckReplace, '[x]') lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]')
} }
this.refs.code.setLineContent(lineIndex, newLine) this.refs.code.setValue(lines.join('\n'))
} }
} }
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, noteKey, linesHighlighted} = this.props const { config, value, storageKey } = this.props
const storage = findStorage(storageKey) const storage = findStorage(storageKey)
let editorFontSize = parseInt(config.editor.fontSize, 10) let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
let editorIndentSize = parseInt(config.editor.indentSize, 10) let editorIndentSize = parseInt(config.editor.indentSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4 if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
const previewStyle = {} const previewStyle = {}
previewStyle.width = (100 - this.state.codeEditorWidthInPercent) + '%' if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused) previewStyle.pointerEvents = 'none'
return ( return (
<div styleName='root' ref='root' <div styleName='root'>
onMouseMove={e => this.handleMouseMove(e)}
onMouseUp={e => this.handleMouseUp(e)}>
<CodeEditor <CodeEditor
styleName='codeEditor'
ref='code' ref='code'
width={this.state.codeEditorWidthInPercent + '%'} mode='GitHub Flavored Markdown'
mode='Boost Flavored Markdown'
value={value} value={value}
theme={config.editor.theme} theme={config.editor.theme}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
fontFamily={config.editor.fontFamily} fontFamily={config.editor.fontFamily}
fontSize={editorFontSize} fontSize={editorFontSize}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
lineWrapping
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
indentType={config.editor.indentType} indentType={config.editor.indentType}
indentSize={editorIndentSize} indentSize={editorIndentSize}
enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
storageKey={storageKey} storageKey={storageKey}
noteKey={noteKey} onChange={this.handleOnChange.bind(this)}
linesHighlighted={linesHighlighted}
onChange={(e) => this.handleOnChange(e)}
onScroll={this.handleScroll.bind(this)}
spellCheck={config.editor.spellcheck}
enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey}
switchPreview={config.editor.switchPreview}
enableMarkdownLint={config.editor.enableMarkdownLint}
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
/> />
<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'
theme={config.ui.theme} theme={config.ui.theme}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
fontSize={config.preview.fontSize} fontSize={config.preview.fontSize}
@@ -197,22 +80,12 @@ class MarkdownSplitEditor extends React.Component {
codeBlockFontFamily={config.editor.fontFamily} codeBlockFontFamily={config.editor.fontFamily}
lineNumber={config.preview.lineNumber} lineNumber={config.preview.lineNumber}
scrollPastEnd={config.preview.scrollPastEnd} scrollPastEnd={config.preview.scrollPastEnd}
smartQuotes={config.preview.smartQuotes}
smartArrows={config.preview.smartArrows}
breaks={config.preview.breaks}
sanitize={config.preview.sanitize}
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
ref='preview' ref='preview'
tabInde='0' tabInde='0'
value={value} value={value}
onCheckboxClick={(e) => this.handleCheckboxClick(e)} onCheckboxClick={(e) => this.handleCheckboxClick(e)}
onScroll={this.handleScroll.bind(this)}
showCopyNotification={config.ui.showCopyNotification} showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path} storagePath={storage.path}
noteKey={noteKey}
customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS}
lineThroughCheckbox={config.preview.lineThroughCheckbox}
/> />
</div> </div>
) )

View File

@@ -3,35 +3,7 @@
height 100% height 100%
font-size 30px font-size 30px
display flex display flex
.slider .codeEditor
absolute top bottom width 50%
top -2px .preview
width 0 width 50%
z-index 0
border-left 1px solid $ui-borderColor
.slider-hitbox
absolute top bottom left right
width 7px
left -3px
z-index 10
cursor col-resize
body[data-theme="dark"]
.root
.slider
border-left 1px solid $ui-dark-borderColor
body[data-theme="solarized-dark"]
.root
.slider
border-left 1px solid $ui-solarized-dark-borderColor
body[data-theme="monokai"]
.root
.slider
border-left 1px solid $ui-monokai-borderColor
body[data-theme="dracula"]
.root
.slider
border-left 1px solid $ui-dracula-borderColor

View File

@@ -8,7 +8,7 @@ const ModalEscButton = ({
}) => ( }) => (
<button styleName='escButton' onClick={handleEscButtonClick}> <button styleName='escButton' onClick={handleEscButtonClick}>
<div styleName='esc-mark'>×</div> <div styleName='esc-mark'>×</div>
<div>esc</div> <div styleName='esc-text'>esc</div>
</button> </button>
) )

View File

@@ -16,8 +16,8 @@ const NavToggleButton = ({isFolded, handleToggleButtonClick}) => (
onClick={(e) => handleToggleButtonClick(e)} onClick={(e) => handleToggleButtonClick(e)}
> >
{isFolded {isFolded
? <i className='fa fa-angle-double-right fa-2x' /> ? <i className='fa fa-angle-double-right' />
: <i className='fa fa-angle-double-left fa-2x' /> : <i className='fa fa-angle-double-left' />
} }
</button> </button>
) )

View File

@@ -7,7 +7,7 @@
border-radius 16.5px border-radius 16.5px
height 34px height 34px
width 34px width 34px
line-height 100% line-height 32px
padding 0 padding 0
&:hover &:hover
border: 1px solid #1EC38B; border: 1px solid #1EC38B;

View File

@@ -3,50 +3,38 @@
*/ */
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import { isArray, sortBy } from 'lodash' import { isArray } from 'lodash'
import invertColor from 'invert-color'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import { getTodoStatus } from 'browser/lib/getTodoStatus' import { getTodoStatus } from 'browser/lib/getTodoStatus'
import styles from './NoteItem.styl' import styles from './NoteItem.styl'
import TodoProcess from './TodoProcess' import TodoProcess from './TodoProcess'
import i18n from 'browser/lib/i18n'
/** /**
* @description Tag element component. * @description Tag element component.
* @param {string} tagName * @param {string} tagName
* @param {string} color
* @return {React.Component} * @return {React.Component}
*/ */
const TagElement = ({ tagName, color }) => { const TagElement = ({ tagName }) => (
const style = {} <span styleName='item-bottom-tagList-item' key={tagName}>
if (color) { #{tagName}
style.backgroundColor = color </span>
style.color = invertColor(color, { black: '#222', white: '#f1f1f1', threshold: 0.3 }) )
}
return (
<span styleName='item-bottom-tagList-item' key={tagName} style={style}>
#{tagName}
</span>
)
}
/** /**
* @description Tag element list component. * @description Tag element list component.
* @param {Array|null} tags * @param {Array|null} tags
* @param {boolean} showTagsAlphabetically
* @param {Object} coloredTags
* @return {React.Component} * @return {React.Component}
*/ */
const TagElementList = (tags, showTagsAlphabetically, coloredTags) => { const TagElementList = (tags) => {
if (!isArray(tags)) { if (!isArray(tags)) {
return [] return []
} }
if (showTagsAlphabetically) { const tagElements = tags.map(tag => (
return sortBy(tags).map(tag => TagElement({ tagName: tag, color: coloredTags[tag] })) TagElement({tagName: tag})
} else { ))
return tags.map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
} return tagElements
} }
/** /**
@@ -56,80 +44,48 @@ const TagElementList = (tags, showTagsAlphabetically, coloredTags) => {
* @param {Function} handleNoteClick * @param {Function} handleNoteClick
* @param {Function} handleNoteContextMenu * @param {Function} handleNoteContextMenu
* @param {Function} handleDragStart * @param {Function} handleDragStart
* @param {Object} coloredTags
* @param {string} dateDisplay * @param {string} dateDisplay
*/ */
const NoteItem = ({ const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
isActive, <div styleName={isActive
note, ? 'item--active'
dateDisplay, : 'item'
handleNoteClick, }
handleNoteContextMenu, key={`${note.storage}-${note.key}`}
handleDragStart, onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
pathname, onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
storageName,
folderName,
viewType,
showTagsAlphabetically,
coloredTags
}) => (
<div
styleName={isActive ? 'item--active' : 'item'}
key={note.key}
onClick={e => handleNoteClick(e, note.key)}
onContextMenu={e => handleNoteContextMenu(e, note.key)}
onDragStart={e => handleDragStart(e, note)} onDragStart={e => handleDragStart(e, note)}
draggable='true' draggable='true'
> >
<div styleName='item-wrapper'> <div styleName='item-wrapper'>
{note.type === 'SNIPPET_NOTE' {note.type === 'SNIPPET_NOTE'
? <i styleName='item-title-icon' className='fa fa-fw fa-code' /> ? <i styleName='item-title-icon' className='fa fa-fw fa-code' />
: <i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />} : <i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />
}
<div styleName='item-title'> <div styleName='item-title'>
{note.title.trim().length > 0 {note.title.trim().length > 0
? note.title ? note.title
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>} : <span styleName='item-title-empty'>Empty</span>
</div> }
<div styleName='item-middle'>
<div styleName='item-middle-time'>{dateDisplay}</div>
<div styleName='item-middle-app-meta'>
<div
title={
viewType === 'ALL'
? storageName
: viewType === 'STORAGE' ? folderName : null
}
styleName='item-middle-app-meta-label'
>
{viewType === 'ALL' && storageName}
{viewType === 'STORAGE' && folderName}
</div>
</div>
</div> </div>
<div styleName='item-bottom-time'>{dateDisplay}</div>
{note.isStarred
? <img styleName='item-star' src='../resources/icon/icon-starred.svg' /> : ''
}
{note.isPinned && !pathname.match(/\/home|\/starred|\/trash/)
? <i styleName='item-pin' className='fa fa-thumb-tack' /> : ''
}
{note.type === 'MARKDOWN_NOTE'
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
: ''
}
<div styleName='item-bottom'> <div styleName='item-bottom'>
<div styleName='item-bottom-tagList'> <div styleName='item-bottom-tagList'>
{note.tags.length > 0 {note.tags.length > 0
? TagElementList(note.tags, showTagsAlphabetically, coloredTags) ? TagElementList(note.tags)
: <span : <span styleName='item-bottom-tagList-empty' />
style={{ fontStyle: 'italic', opacity: 0.5 }} }
styleName='item-bottom-tagList-empty'
>
{i18n.__('No tags')}
</span>}
</div>
<div>
{note.isStarred
? <img
styleName='item-star'
src='../resources/icon/icon-starred.svg'
/>
: ''}
{note.isPinned && !pathname.match(/\/starred|\/trash/)
? <i styleName='item-pin' className='fa fa-thumb-tack' />
: ''}
{note.type === 'MARKDOWN_NOTE'
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
: ''}
</div> </div>
</div> </div>
</div> </div>
@@ -139,7 +95,6 @@ const NoteItem = ({
NoteItem.propTypes = { NoteItem.propTypes = {
isActive: PropTypes.bool.isRequired, isActive: PropTypes.bool.isRequired,
dateDisplay: PropTypes.string.isRequired, dateDisplay: PropTypes.string.isRequired,
coloredTags: PropTypes.object,
note: PropTypes.shape({ note: PropTypes.shape({
storage: PropTypes.string.isRequired, storage: PropTypes.string.isRequired,
key: PropTypes.string.isRequired, key: PropTypes.string.isRequired,
@@ -147,15 +102,12 @@ NoteItem.propTypes = {
title: PropTypes.string.isrequired, title: PropTypes.string.isrequired,
tags: PropTypes.array, tags: PropTypes.array,
isStarred: PropTypes.bool.isRequired, isStarred: PropTypes.bool.isRequired,
isTrashed: PropTypes.bool.isRequired, isTrashed: PropTypes.bool.isRequired
blog: PropTypes.shape({
blogLink: PropTypes.string,
blogId: PropTypes.number
})
}), }),
handleNoteClick: PropTypes.func.isRequired, handleNoteClick: PropTypes.func.isRequired,
handleNoteContextMenu: PropTypes.func.isRequired, handleNoteContextMenu: PropTypes.func.isRequired,
handleDragStart: PropTypes.func.isRequired handleDragStart: PropTypes.func.isRequired,
handleDragEnd: PropTypes.func.isRequired
} }
export default CSSModules(NoteItem, styles) export default CSSModules(NoteItem, styles)

View File

@@ -90,26 +90,6 @@ $control-height = 30px
font-weight normal font-weight normal
color $ui-inactive-text-color color $ui-inactive-text-color
.item-middle
font-size 13px
padding-left 2px
padding-bottom 2px
.item-middle-time
color $ui-inactive-text-color
display inline-block
.item-middle-app-meta
float right
.item-middle-app-meta-label
opacity 0.4
color $ui-inactive-text-color
padding 0 3px
white-space nowrap
text-overflow ellipsis
overflow hidden
max-width 200px
.item-bottom .item-bottom
position relative position relative
bottom 0px bottom 0px
@@ -117,7 +97,7 @@ $control-height = 30px
font-size 12px font-size 12px
line-height 20px line-height 20px
overflow ellipsis overflow ellipsis
display block display flex
.item-bottom-tagList .item-bottom-tagList
flex 1 flex 1
@@ -145,8 +125,10 @@ $control-height = 30px
.item-star .item-star
position absolute position absolute
right 2px right -6px
top 5px bottom 23px
width 16px
height 16px
color alpha($ui-favorite-star-button-color, 60%) color alpha($ui-favorite-star-button-color, 60%)
font-size 12px font-size 12px
padding 0 padding 0
@@ -154,8 +136,10 @@ $control-height = 30px
.item-pin .item-pin
position absolute position absolute
right 25px right 0px
top 7px bottom 2px
width 34px
height 34px
color #E54D42 color #E54D42
font-size 14px font-size 14px
padding 0 padding 0
@@ -208,7 +192,7 @@ body[data-theme="dark"]
.item-bottom-tagList-item .item-bottom-tagList-item
transition 0.15s transition 0.15s
background-color alpha(white, 10%) background-color alpha(white, 10%)
color $ui-dark-text-color color $ui-dark-text-color
.item-wrapper .item-wrapper
border-color alpha($ui-dark-button--active-backgroundColor, 60%) border-color alpha($ui-dark-button--active-backgroundColor, 60%)
@@ -282,7 +266,7 @@ body[data-theme="solarized-dark"]
.item-bottom-tagList-item .item-bottom-tagList-item
transition 0.15s transition 0.15s
background-color alpha($ui-solarized-dark-noteList-backgroundColor, 10%) background-color alpha($ui-solarized-dark-noteList-backgroundColor, 10%)
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
.item-wrapper .item-wrapper
border-color alpha($ui-solarized-dark-button--active-backgroundColor, 60%) border-color alpha($ui-solarized-dark-button--active-backgroundColor, 60%)
@@ -318,152 +302,6 @@ body[data-theme="solarized-dark"]
background-color alpha($ui-dark-button--active-backgroundColor, 40%) background-color alpha($ui-dark-button--active-backgroundColor, 40%)
color $ui-inactive-text-color color $ui-inactive-text-color
.item-bottom-tagList-empty
color $ui-inactive-text-color
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-active-color
.item-bottom-tagList-item
background-color alpha(white, 10%)
color $ui-monokai-text-color
&:hover
// background-color alpha($ui-monokai-button--active-backgroundColor, 60%)
color #f92672
.item-bottom-tagList-item
background-color alpha(#fff, 20%)
.item-title
color $ui-inactive-text-color
.item-title-icon
color $ui-inactive-text-color
.item-title-empty
color $ui-inactive-text-color
.item-bottom-tagList-item
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
color $ui-inactive-text-color
.item-bottom-tagList-empty
color $ui-inactive-text-color
vertical-align middle
body[data-theme="dracula"]
.root
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
.item
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
&:hover
transition 0.15s
// background-color alpha($ui-dracula-noteList-backgroundColor, 20%)
color $ui-dracula-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-dracula-noteList-backgroundColor, 20%)
color $ui-dracula-text-color
&:active
transition 0.15s
background-color $ui-dracula-noteList-backgroundColor
color $ui-dracula-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-dracula-noteList-backgroundColor, 10%)
color $ui-dracula-text-color
.item-wrapper
border-color alpha($ui-dracula-button-backgroundColor, 60%)
.item--active
border-color $ui-dracula-borderColor
background-color $ui-dracula-button-backgroundColor
.item-wrapper
border-color transparent
.item-title
.item-title-icon
.item-bottom-time
color $ui-dracula-active-color
.item-bottom-tagList-item
background-color alpha(#f8f8f2, 10%)
color $ui-dracula-text-color
&:hover
// background-color alpha($ui-dracula-button--active-backgroundColor, 60%)
color #ff79c6
.item-bottom-tagList-item
background-color alpha(#f8f8f2, 20%)
.item-title
color $ui-inactive-text-color
.item-title-icon
color $ui-inactive-text-color
.item-title-empty
color $ui-inactive-text-color
.item-bottom-tagList-item
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
color $ui-inactive-text-color
.item-bottom-tagList-empty .item-bottom-tagList-empty
color $ui-inactive-text-color color $ui-inactive-text-color
vertical-align middle vertical-align middle

View File

@@ -5,7 +5,6 @@ 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 styles from './NoteItemSimple.styl' import styles from './NoteItemSimple.styl'
import i18n from 'browser/lib/i18n'
/** /**
* @description Note item component when using simple display mode. * @description Note item component when using simple display mode.
@@ -15,23 +14,14 @@ import i18n from 'browser/lib/i18n'
* @param {Function} handleNoteContextMenu * @param {Function} handleNoteContextMenu
* @param {Function} handleDragStart * @param {Function} handleDragStart
*/ */
const NoteItemSimple = ({ const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
isActive,
isAllNotesView,
note,
handleNoteClick,
handleNoteContextMenu,
handleDragStart,
pathname,
storage
}) => (
<div styleName={isActive <div styleName={isActive
? 'item-simple--active' ? 'item-simple--active'
: 'item-simple' : 'item-simple'
} }
key={note.key} key={`${note.storage}-${note.key}`}
onClick={e => handleNoteClick(e, note.key)} onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
onContextMenu={e => handleNoteContextMenu(e, note.key)} onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
onDragStart={e => handleDragStart(e, note)} onDragStart={e => handleDragStart(e, note)}
draggable='true' draggable='true'
> >
@@ -40,19 +30,14 @@ const NoteItemSimple = ({
? <i styleName='item-simple-title-icon' className='fa fa-fw fa-code' /> ? <i styleName='item-simple-title-icon' className='fa fa-fw fa-code' />
: <i styleName='item-simple-title-icon' className='fa fa-fw fa-file-text-o' /> : <i styleName='item-simple-title-icon' className='fa fa-fw fa-file-text-o' />
} }
{note.isPinned && !pathname.match(/\/starred|\/trash/) {note.isPinned && !pathname.match(/\/home|\/starred|\/trash/)
? <i styleName='item-pin' className='fa fa-thumb-tack' /> ? <i styleName='item-pin' className='fa fa-thumb-tack' />
: '' : ''
} }
{note.title.trim().length > 0 {note.title.trim().length > 0
? note.title ? note.title
: <span styleName='item-simple-title-empty'>{i18n.__('Empty note')}</span> : <span styleName='item-simple-title-empty'>Empty</span>
} }
{isAllNotesView && <div styleName='item-simple-right'>
<span styleName='item-simple-right-storageName'>
{storage.name}
</span>
</div>}
</div> </div>
</div> </div>
) )

View File

@@ -104,7 +104,6 @@ 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
@@ -118,7 +117,6 @@ 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
@@ -126,7 +124,7 @@ body[data-theme="dark"]
.item-simple-bottom-tagList-item .item-simple-bottom-tagList-item
transition 0.15s transition 0.15s
background-color alpha(white, 10%) background-color alpha(white, 10%)
color $ui-dark-text-color color $ui-dark-text-color
.item-simple--active .item-simple--active
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
@@ -134,7 +132,6 @@ 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
@@ -168,10 +165,9 @@ 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, 60%) // background-color alpha($ui-dark-button--active-backgroundColor, 20%)
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
@@ -182,10 +178,9 @@ 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-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
@@ -193,17 +188,15 @@ body[data-theme="solarized-dark"]
.item-simple-bottom-tagList-item .item-simple-bottom-tagList-item
transition 0.15s transition 0.15s
background-color alpha(white, 10%) background-color alpha(white, 10%)
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
.item-simple--active .item-simple--active
border-color $ui-solarized-dark-borderColor border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-tag-backgroundColor background-color $ui-solarized-dark-button--active-backgroundColor
.item-simple-wrapper .item-simple-wrapper
border-color transparent border-color transparent
.item-simple-title .item-simple-title
.item-simple-title-empty
.item-simple-title-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
@@ -213,140 +206,4 @@ body[data-theme="solarized-dark"]
// background-color alpha($ui-dark-button--active-backgroundColor, 60%) // background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color #c0392b color #c0392b
.item-simple-bottom-tagList-item .item-simple-bottom-tagList-item
background-color alpha(#fff, 20%) background-color alpha(#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
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-monokai-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
body[data-theme="dracula"]
.root
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
.item-simple
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
&:hover
transition 0.15s
background-color alpha($ui-dracula-button-backgroundColor, 60%)
color $ui-dracula-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(#f8f8f2, 20%)
color $ui-dracula-text-color
&:active
transition 0.15s
background-color $ui-dracula-button--active-backgroundColor
color $ui-dracula-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(#f8f8f2, 10%)
color $ui-dracula-text-color
.item-simple--active
border-color $ui-dracula-borderColor
background-color $ui-dracula-button--active-backgroundColor
.item-simple-wrapper
border-color transparent
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
color $ui-dracula-text-color
.item-simple-bottom-tagList-item
background-color alpha(#f8f8f2, 10%)
color $ui-dracula-text-color
&:hover
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#f8f8f2, 20%)
.item-simple-title
color $ui-dark-text-color
border-bottom $ui-dark-borderColor
.item-simple-right
float right
.item-simple-right-storageName
padding-left 4px
opacity 0.4

View File

@@ -41,25 +41,3 @@ 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
body[data-theme="dracula"]
.notification-area
background-color none
.notification-link
color $ui-dracula-text-color
border none
background-color $ui-dracula-button-backgroundColor
&:hover
color #ff79c6

View File

@@ -5,7 +5,6 @@ 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 styles from './SideNavFilter.styl' import styles from './SideNavFilter.styl'
import i18n from 'browser/lib/i18n'
/** /**
* @param {boolean} isFolded * @param {boolean} isFolded
@@ -18,7 +17,7 @@ import i18n from 'browser/lib/i18n'
const SideNavFilter = ({ const SideNavFilter = ({
isFolded, isHomeActive, handleAllNotesButtonClick, isFolded, isHomeActive, handleAllNotesButtonClick,
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote, isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote,
counterTotalNote, counterStarredNote, handleFilterButtonContextMenu counterTotalNote, counterStarredNote
}) => ( }) => (
<div styleName={isFolded ? 'menu--folded' : 'menu'}> <div styleName={isFolded ? 'menu--folded' : 'menu'}>
@@ -27,12 +26,12 @@ const SideNavFilter = ({
> >
<div styleName='iconWrap'> <div styleName='iconWrap'>
<img src={isHomeActive <img src={isHomeActive
? '../resources/icon/icon-all-active.svg' ? '../resources/icon/icon-all-active.svg'
: '../resources/icon/icon-all.svg' : '../resources/icon/icon-all.svg'
} }
/> />
</div> </div>
<span styleName='menu-button-label'>{i18n.__('All Notes')}</span> <span styleName='menu-button-label'>All Notes</span>
<span styleName='counters'>{counterTotalNote}</span> <span styleName='counters'>{counterTotalNote}</span>
</button> </button>
@@ -41,26 +40,26 @@ const SideNavFilter = ({
> >
<div styleName='iconWrap'> <div styleName='iconWrap'>
<img src={isStarredActive <img src={isStarredActive
? '../resources/icon/icon-star-active.svg' ? '../resources/icon/icon-star-active.svg'
: '../resources/icon/icon-star-sidenav.svg' : '../resources/icon/icon-star-sidenav.svg'
} }
/> />
</div> </div>
<span styleName='menu-button-label'>{i18n.__('Starred')}</span> <span styleName='menu-button-label'>Starred</span>
<span styleName='counters'>{counterStarredNote}</span> <span styleName='counters'>{counterStarredNote}</span>
</button> </button>
<button styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'} <button styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
onClick={handleTrashedButtonClick} onContextMenu={handleFilterButtonContextMenu} onClick={handleTrashedButtonClick}
> >
<div styleName='iconWrap'> <div styleName='iconWrap'>
<img src={isTrashedActive <img src={isTrashedActive
? '../resources/icon/icon-trash-active.svg' ? '../resources/icon/icon-trash-active.svg'
: '../resources/icon/icon-trash-sidenav.svg' : '../resources/icon/icon-trash-sidenav.svg'
} }
/> />
</div> </div>
<span styleName='menu-button-label'>{i18n.__('Trash')}</span> <span styleName='menu-button-label'>Trash</span>
<span styleName='counters'>{counterDelNote}</span> <span styleName='counters'>{counterDelNote}</span>
</button> </button>
@@ -74,7 +73,7 @@ SideNavFilter.propTypes = {
isStarredActive: PropTypes.bool.isRequired, isStarredActive: PropTypes.bool.isRequired,
isTrashedActive: PropTypes.bool.isRequired, isTrashedActive: PropTypes.bool.isRequired,
handleStarredButtonClick: PropTypes.func.isRequired, handleStarredButtonClick: PropTypes.func.isRequired,
handleTrashedButtonClick: PropTypes.func.isRequired handleTrashdButtonClick: PropTypes.func.isRequired
} }
export default CSSModules(SideNavFilter, styles) export default CSSModules(SideNavFilter, styles)

View File

@@ -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,9 +68,10 @@
.menu-button-label .menu-button-label
position fixed position fixed
display inline-block display inline-block
height 36px height 32px
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
@@ -221,88 +222,4 @@ 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
body[data-theme="dracula"]
.menu-button
&:active
background-color $ui-dracula-noteList-backgroundColor
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button--active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.menu-button-label
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button-label
color $ui-dracula-text-color
.menu-button-star--active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.menu-button-label
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button-label
color $ui-dracula-text-color
.menu-button-trash--active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.menu-button-label
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button-label
color $ui-dracula-text-color

View File

@@ -2,7 +2,6 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './SnippetTab.styl' import styles from './SnippetTab.styl'
import context from 'browser/lib/context' import context from 'browser/lib/context'
import i18n from 'browser/lib/i18n'
class SnippetTab extends React.Component { class SnippetTab extends React.Component {
constructor (props) { constructor (props) {
@@ -29,7 +28,7 @@ class SnippetTab extends React.Component {
handleContextMenu (e) { handleContextMenu (e) {
context.popup([ context.popup([
{ {
label: i18n.__('Rename'), label: 'Rename',
click: (e) => this.handleRenameClick(e) click: (e) => this.handleRenameClick(e)
} }
]) ])
@@ -55,10 +54,10 @@ class SnippetTab extends React.Component {
this.handleRename() this.handleRename()
break break
case 27: case 27:
this.setState((prevState, props) => ({ this.setState({
name: props.snippet.name, name: this.props.snippet.name,
isRenaming: false isRenaming: false
})) })
break break
} }
} }
@@ -114,8 +113,8 @@ class SnippetTab extends React.Component {
> >
{snippet.name.trim().length > 0 {snippet.name.trim().length > 0
? snippet.name ? snippet.name
: <span> : <span styleName='button-unnamed'>
{i18n.__('Unnamed')} Unnamed
</span> </span>
} }
</button> </button>

View File

@@ -1,44 +1,33 @@
.root .root
position relative position relative
flex 1 flex 1
min-width 70px
overflow hidden overflow hidden
border-left 1px solid $ui-borderColor
border-top 1px solid $ui-borderColor
&:hover &:hover
background-color alpha($ui-button--active-backgroundColor, 20%)
.deleteButton .deleteButton
color: $ui-text-color color $ui-inactive-text-color
visibility visible &:hover
transition 0.15s background-color darken($ui-backgroundColor, 15%)
.button &:active
color: $ui-text-color color white
transition 0.15s background-color $ui-active-color
.root--active .root--active
@extend .root @extend .root
min-width 100px min-width 100px
background-color alpha($ui-button--active-backgroundColor, 60%) border-bottom $ui-border
.deleteButton
visibility visible
color: $ui-text-color
transition 0.15s
.button
font-weight bold
color: $ui-text-color
transition 0.15s
.button .button
width 100% width 100%
height 29px height 29px
overflow ellipsis overflow ellipsis
text-align left text-align left
padding-right 23px padding-right 30px
border none border none
background-color transparent background-color transparent
transition 0.15s transition 0.15s
border-left 4px solid transparent border-left 4px solid transparent
color $ui-inactive-text-color &:hover
background-color $ui-button--hover-backgroundColor
.deleteButton .deleteButton
position absolute position absolute
@@ -52,7 +41,6 @@
color $ui-inactive-text-color color $ui-inactive-text-color
background-color transparent background-color transparent
border-radius 2px border-radius 2px
visibility hidden
.input .input
height 29px height 29px
@@ -61,66 +49,76 @@
width 100% width 100%
outline none outline none
body[data-theme="default"], body[data-theme="white"]
.root--active
&:hover
background-color alpha($ui-button--active-backgroundColor, 60%)
body[data-theme="dark"] body[data-theme="dark"]
.root .root
color $ui-dark-text-color
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
border-top 1px solid $ui-dark-borderColor
&:hover &:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%) background-color $ui-dark-button--hover-backgroundColor
transition 0.15s
.button
color $ui-dark-text-color
transition 0.15s
.deleteButton .deleteButton
color $ui-dark-text-color color $ui-dark-inactive-text-color
transition 0.15s &:hover
background-color darken($ui-dark-button--hover-backgroundColor, 15%)
&:active
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
.root--active .root--active
background-color $ui-dark-button--active-backgroundColor color $ui-dark-text-color
border-left 1px solid $ui-dark-borderColor border-color $ui-dark-borderColor
border-top 1px solid $ui-dark-borderColor &:hover
.button background-color $ui-dark-button--hover-backgroundColor
color $ui-dark-text-color .deleteButton
.deleteButton color $ui-dark-inactive-text-color
color $ui-dark-text-color &:hover
background-color darken($ui-dark-button--hover-backgroundColor, 15%)
&:active
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
.button .button
border none border none
color $ui-dark-text-color
background-color transparent background-color transparent
transition color background-color 0.15s transition color background-color 0.15s
border-left 4px solid transparent border-left 4px solid transparent
&:hover
color $ui-dark-text-color
background-color $ui-dark-button--hover-backgroundColor
.input .input
background-color $ui-dark-button--active-backgroundColor background-color $ui-dark-button--hover-backgroundColor
color $ui-dark-text-color color $ui-dark-text-color
transition 0.15s
.deleteButton
color alpha($ui-dark-text-color, 30%)
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.root .root
color $ui-solarized-dark-text-color
border-color $ui-dark-borderColor
&:hover
background-color $ui-solarized-dark-noteDetail-backgroundColor
.deleteButton
color $ui-solarized-dark-text-color
&:hover
background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%)
&:active
color $ui-solarized-dark-text-color
background-color $ui-dark-button--active-backgroundColor
.root--active
color $ui-solarized-dark-text-color
border-color $ui-solarized-dark-borderColor border-color $ui-solarized-dark-borderColor
&:hover &:hover
background-color $ui-solarized-dark-noteDetail-backgroundColor background-color $ui-solarized-dark-noteDetail-backgroundColor
transition 0.15s
.deleteButton .deleteButton
color $ui-solarized-dark-button--active-color color $ui-solarized-dark-text-color
transition 0.15s &:hover
.button background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%)
color $ui-solarized-dark-button--active-color &:active
transition 0.15s color $ui-solarized-dark-text-color
background-color $ui-dark-button--active-backgroundColor
.root--active
color $ui-solarized-dark-button--active-color
background-color $ui-solarized-dark-button-backgroundColor
border-color $ui-solarized-dark-borderColor
.deleteButton
color $ui-solarized-dark-button--active-color
.button
color $ui-solarized-dark-button--active-color
.button .button
border none border none
@@ -128,75 +126,13 @@ body[data-theme="solarized-dark"]
background-color transparent background-color transparent
transition color background-color 0.15s transition color background-color 0.15s
border-left 4px solid transparent border-left 4px solid transparent
&:hover
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-noteDetail-backgroundColor
.input .input
background-color $ui-solarized-dark-noteDetail-backgroundColor background-color $ui-solarized-dark-noteDetail-backgroundColor
color $ui-solarized-dark-button--active-color color $ui-solarized-dark-text-color
transition 0.15s
body[data-theme="monokai"] .deleteButton
.root color alpha($ui-solarized-dark-text-color, 30%)
border-color $ui-monokai-borderColor
&:hover
background-color $ui-monokai-noteDetail-backgroundColor
transition 0.15s
.deleteButton
color $ui-monokai-text-color
transition 0.15s
.button
color $ui-monokai-text-color
transition 0.15s
.root--active
color $ui-monokai-active-color
background-color $ui-monokai-button-backgroundColor
border-color $ui-monokai-borderColor
.deleteButton
color $ui-monokai-text-color
.button
color $ui-monokai-active-color
.button
border none
color $ui-inactive-text-color
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
.input
background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color
transition 0.15s
body[data-theme="dracula"]
.root
border-color $ui-dracula-borderColor
&:hover
background-color $ui-dracula-noteDetail-backgroundColor
transition 0.15s
.deleteButton
color $ui-dracula-text-color
transition 0.15s
.button
color $ui-dracula-text-color
transition 0.15s
.root--active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
border-color $ui-dracula-borderColor
.deleteButton
color $ui-dracula-text-color
.button
color $ui-dracula-active-color
.button
border none
color $ui-inactive-text-color
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
.input
background-color $ui-dracula-noteDetail-backgroundColor
color $ui-dracula-text-color

View File

@@ -6,18 +6,6 @@ import React from 'react'
import styles from './StorageItem.styl' import styles from './StorageItem.styl'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import _ from 'lodash' import _ from 'lodash'
import { SortableHandle } from 'react-sortable-hoc'
const DraggableIcon = SortableHandle(({ className }) => (
<i className={`fa ${className}`} />
))
const FolderIcon = ({ className, color, isActive }) => {
const iconStyle = isActive ? 'fa-folder-open-o' : 'fa-folder-o'
return (
<i className={`fa ${iconStyle} ${className}`} style={{ color: color }} />
)
}
/** /**
* @param {boolean} isActive * @param {boolean} isActive
@@ -33,51 +21,34 @@ const FolderIcon = ({ className, color, isActive }) => {
* @return {React.Component} * @return {React.Component}
*/ */
const StorageItem = ({ const StorageItem = ({
styles, isActive, handleButtonClick, handleContextMenu, folderName,
isActive, folderColor, isFolded, noteCount, handleDrop, handleDragEnter, handleDragLeave
handleButtonClick, }) => (
handleContextMenu, <button styleName={isActive
folderName, ? 'folderList-item--active'
folderColor, : 'folderList-item'
isFolded, }
noteCount, onClick={handleButtonClick}
handleDrop, onContextMenu={handleContextMenu}
handleDragEnter, onDrop={handleDrop}
handleDragLeave onDragEnter={handleDragEnter}
}) => { onDragLeave={handleDragLeave}
return ( >
<button <span styleName={isFolded
styleName={isActive ? 'folderList-item--active' : 'folderList-item'} ? 'folderList-item-name--folded' : 'folderList-item-name'
onClick={handleButtonClick} }>
onContextMenu={handleContextMenu} <text style={{color: folderColor, paddingRight: '10px'}}>{isActive ? <i className='fa fa-folder-open-o' /> : <i className='fa fa-folder-o' />}</text>{isFolded ? _.truncate(folderName, {length: 1, omission: ''}) : folderName}
onDrop={handleDrop} </span>
onDragEnter={handleDragEnter} {(!isFolded && _.isNumber(noteCount)) &&
onDragLeave={handleDragLeave} <span styleName='folderList-item-noteCount'>{noteCount}</span>
> }
{!isFolded && {isFolded &&
<DraggableIcon className={styles['folderList-item-reorder']} />} <span styleName='folderList-item-tooltip'>
<span {folderName}
styleName={
isFolded ? 'folderList-item-name--folded' : 'folderList-item-name'
}
>
<FolderIcon
styleName='folderList-item-icon'
color={folderColor}
isActive={isActive}
/>
{isFolded
? _.truncate(folderName, { length: 1, omission: '' })
: folderName}
</span> </span>
{!isFolded && }
_.isNumber(noteCount) && </button>
<span styleName='folderList-item-noteCount'>{noteCount}</span>} )
{isFolded &&
<span styleName='folderList-item-tooltip'>{folderName}</span>}
</button>
)
}
StorageItem.propTypes = { StorageItem.propTypes = {
isActive: PropTypes.bool.isRequired, isActive: PropTypes.bool.isRequired,

View File

@@ -13,7 +13,6 @@
border none border none
overflow ellipsis overflow ellipsis
font-size 14px font-size 14px
align-items: center
&:first-child &:first-child
margin-top 0 margin-top 0
&:hover &:hover
@@ -23,7 +22,7 @@
&:active &:active
color $$ui-button-default-color color $$ui-button-default-color
background-color alpha($ui-button-default--active-backgroundColor, 20%) background-color alpha($ui-button-default--active-backgroundColor, 20%)
.folderList-item--active .folderList-item--active
@extend .folderList-item @extend .folderList-item
color #1EC38B color #1EC38B
@@ -35,7 +34,9 @@
.folderList-item-name .folderList-item-name
display block display block
flex 1 flex 1
padding-right: 10px padding 0 12px
height 26px
line-height 26px
border-width 0 0 0 2px border-width 0 0 0 2px
border-style solid border-style solid
border-color transparent border-color transparent
@@ -58,8 +59,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 34px height 26px
line-height 32px line-height 26px
.folderList-item:hover, .folderList-item--active:hover .folderList-item:hover, .folderList-item--active:hover
.folderList-item-tooltip .folderList-item-tooltip
@@ -68,20 +69,9 @@
.folderList-item-name--folded .folderList-item-name--folded
@extend .folderList-item-name @extend .folderList-item-name
padding-left 7px padding-left 7px
.folderList-item-icon text
font-size 9px font-size 9px
.folderList-item-icon
padding-right: 10px
.folderList-item-reorder
font-size: 9px
padding: 10px 8px 10px 9px;
color: rgba(147, 147, 149, 0.3)
cursor: ns-resize
&:before
content: "\f142 \f142"
body[data-theme="white"] body[data-theme="white"]
.folderList-item .folderList-item
color $ui-inactive-text-color color $ui-inactive-text-color
@@ -137,42 +127,4 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-button-backgroundColor background-color $ui-solarized-dark-button-backgroundColor
&: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
body[data-theme="dracula"]
.folderList-item
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
&:active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.folderList-item--active
@extend .folderList-item
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
&:active
background-color $ui-dracula-button-backgroundColor
&:hover
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor

View File

@@ -7,18 +7,18 @@ import styles from './StorageList.styl'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
/** /**
* @param {Array} storageList * @param {Array} storgaeList
*/ */
const StorageList = ({storageList, isFolded}) => ( const StorageList = ({storageList}) => (
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}> <div styleName='storageList'>
{storageList.length > 0 ? storageList : ( {storageList.length > 0 ? storageList : (
<div styleName='storageList-empty'>No storage mount.</div> <div styleName='storgaeList-empty'>No storage mount.</div>
)} )}
</div> </div>
) )
StorageList.propTypes = { StorageList.propTypes = {
storageList: PropTypes.arrayOf(PropTypes.element).isRequired storgaeList: PropTypes.arrayOf(PropTypes.element).isRequired
} }
export default CSSModules(StorageList, styles) export default CSSModules(StorageList, styles)

View File

@@ -4,10 +4,6 @@
top 180px top 180px
overflow-y auto overflow-y auto
.storageList-folded
@extend .storageList
width 44px
.storageList-empty .storageList-empty
padding 0 10px padding 0 10px
margin-top 15px margin-top 15px

View File

@@ -9,34 +9,20 @@ 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 {boolean} isActive
* @param {boolean} isRelated
* @param {string} bgColor tab backgroundColor
*/ */
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count, color}) => ( const TagListItem = ({name, handleClickTagListItem, isActive}) => (
<div styleName='tagList-itemContainer' onContextMenu={e => handleContextMenu(e, name)}> <button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
{isRelated <span styleName='tagList-item-name'>
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}> {`# ${name}`}
<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-color' style={{backgroundColor: color || 'transparent'}} />
<span styleName='tagList-item-name'>
{`# ${name}`}
<span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span>
</span>
</button>
</div>
) )
TagListItem.propTypes = { TagListItem.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
handleClickTagListItem: PropTypes.func.isRequired, handleClickTagListItem: PropTypes.func.isRequired
color: PropTypes.string
} }
export default CSSModules(TagListItem, styles) export default CSSModules(TagListItem, styles)

View File

@@ -1,9 +1,5 @@
.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
@@ -24,16 +20,9 @@
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
@@ -47,16 +36,10 @@
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 8px 0 4px padding 0 15px
height 26px height 26px
line-height 26px line-height 26px
border-width 0 0 0 2px border-width 0 0 0 2px
@@ -65,17 +48,6 @@
overflow hidden overflow hidden
text-overflow ellipsis text-overflow ellipsis
.tagList-item-count
float right
line-height 26px
padding-right 15px
font-size 13px
.tagList-item-color
height 26px
width 3px
display inline-block
body[data-theme="white"] body[data-theme="white"]
.tagList-item .tagList-item
color $ui-inactive-text-color color $ui-inactive-text-color
@@ -91,8 +63,6 @@ body[data-theme="white"]
color $ui-text-color color $ui-text-color
&:hover &:hover
background-color alpha($ui-button--active-backgroundColor, 60%) background-color alpha($ui-button--active-backgroundColor, 60%)
.tagList-item-count
color $ui-text-color
body[data-theme="dark"] body[data-theme="dark"]
.tagList-item .tagList-item
@@ -111,6 +81,4 @@ body[data-theme="dark"]
background-color alpha($ui-dark-button--active-backgroundColor, 50%) background-color alpha($ui-dark-button--active-backgroundColor, 50%)
&:hover &:hover
color $ui-dark-text-color color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 50%) background-color alpha($ui-dark-button--active-backgroundColor, 50%)
.tagList-item-count
color $ui-dark-button--active-color

View File

@@ -12,7 +12,7 @@ import styles from './TodoListPercentage.styl'
*/ */
const TodoListPercentage = ({ const TodoListPercentage = ({
percentageOfTodo, onClearCheckboxClick percentageOfTodo
}) => ( }) => (
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}> <div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
<div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}> <div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}>
@@ -20,15 +20,11 @@ const TodoListPercentage = ({
<p styleName='percentageText'>{percentageOfTodo}%</p> <p styleName='percentageText'>{percentageOfTodo}%</p>
</div> </div>
</div> </div>
<div styleName='todoClear'>
<p styleName='todoClearText' onClick={(e) => onClearCheckboxClick(e)}>clear</p>
</div>
</div> </div>
) )
TodoListPercentage.propTypes = { TodoListPercentage.propTypes = {
percentageOfTodo: PropTypes.number.isRequired, percentageOfTodo: PropTypes.number.isRequired
onClearCheckboxClick: PropTypes.func.isRequired
} }
export default CSSModules(TodoListPercentage, styles) export default CSSModules(TodoListPercentage, styles)

View File

@@ -1,5 +1,4 @@
.percentageBar .percentageBar
display: flex
position absolute position absolute
top 72px top 72px
right 0px right 0px
@@ -31,20 +30,6 @@
color #f4f4f4 color #f4f4f4
font-weight 600 font-weight 600
.todoClear
display flex
justify-content: flex-end
position absolute
z-index 120
width 100%
height 100%
padding 2px 10px
.todoClearText
color #f4f4f4
cursor pointer
font-weight 500
body[data-theme="dark"] body[data-theme="dark"]
.percentageBar .percentageBar
background-color #444444 background-color #444444
@@ -54,10 +39,7 @@ body[data-theme="dark"]
.percentageText .percentageText
color $ui-dark-text-color color $ui-dark-text-color
.todoClearText
color $ui-dark-text-color
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.percentageBar .percentageBar
background-color #002b36 background-color #002b36
@@ -66,30 +48,4 @@ body[data-theme="solarized-dark"]
background-color: #2aa198 background-color: #2aa198
.percentageText .percentageText
color #fdf6e3 color #fdf6e3
.todoClearText
color #fdf6e3
body[data-theme="monokai"]
.percentageBar
background-color: $ui-monokai-borderColor
.progressBar
background-color $ui-monokai-active-color
.percentageText
color $ui-monokai-text-color
body[data-theme="dracula"]
.percentageBar
background-color $ui-dracula-borderColor
.progressBar
background-color: $ui-dracula-active-color
.percentageText
color $ui-dracula-text-color
.percentageText
color $ui-dracula-text-color

View File

@@ -25,10 +25,10 @@ const TodoProcess = ({
) )
TodoProcess.propTypes = { TodoProcess.propTypes = {
todoStatus: PropTypes.exact({ todoStatus: {
total: PropTypes.number.isRequired, total: PropTypes.number.isRequired,
completed: PropTypes.number.isRequired completed: PropTypes.number.isRequired
}) }
} }
export default CSSModules(TodoProcess, styles) export default CSSModules(TodoProcess, styles)

View File

@@ -55,14 +55,11 @@ body
line-height 1.6 line-height 1.6
overflow-x hidden overflow-x hidden
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
// do not allow display line breaks
.katex-display > .katex
white-space nowrap
// allow inline line breaks
.katex .katex
white-space initial font 400 1.2em 'KaTeX_Main'
.katex .katex-html line-height 1.2em
display inline-flex white-space nowrap
text-indent 0
.katex .mfrac>.vlist>span:nth-child(2) .katex .mfrac>.vlist>span:nth-child(2)
top 0 !important top 0 !important
.katex-error .katex-error
@@ -71,7 +68,7 @@ body
padding 5px padding 5px
margin -5px margin -5px
border-radius 5px border-radius 5px
.flowchart-error, .sequence-error .chart-error .flowchart-error, .sequence-error
background-color errorBackgroundColor background-color errorBackgroundColor
color errorTextColor color errorTextColor
padding 5px padding 5px
@@ -79,13 +76,10 @@ body
justify-content left justify-content left
li li
label.taskListItem label.taskListItem
margin-left -1.8em margin-left -2em
&.checked &.checked
text-decoration line-through text-decoration line-through
opacity 0.5 opacity 0.5
&.taskListItem.checked
text-decoration line-through
opacity 0.5
div.math-rendered div.math-rendered
text-align center text-align center
.math-failed .math-failed
@@ -165,7 +159,6 @@ p
white-space pre-line white-space pre-line
word-wrap break-word word-wrap break-word
img img
cursor zoom-in
max-width 100% max-width 100%
strong, b strong, b
font-weight bold font-weight bold
@@ -185,12 +178,6 @@ ul
margin-bottom 1em margin-bottom 1em
li li
display list-item display list-item
&.taskListItem
list-style none
&>input
margin-left -1.6em
&>p
margin-left -1.8em
p p
margin 0 margin 0
&>li>ul, &>li>ol &>li>ul, &>li>ol
@@ -210,6 +197,7 @@ 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
@@ -217,39 +205,33 @@ code
text-decoration none text-decoration none
margin-right 2px margin-right 2px
pre pre
padding 0.5rem !important padding 0.5em !important
border solid 1px #D1D1D1 border solid 1px #D1D1D1
border-radius 5px border-radius 5px
overflow-x auto overflow-x auto
margin 0 0 1rem margin 0 0 1em
display flex display flex
line-height 1.4em line-height 1.4em
&.flowchart, &.sequence
display flex
justify-content center
background-color white
&.CodeMirror
height initial
&>code
flex 1
overflow-x auto
code code
background-color inherit background-color inherit
margin 0 margin 0
padding 0 padding 0
border none border none
border-radius 0 border-radius 0
&.CodeMirror
height initial
flex-wrap wrap
&>code
flex 1
overflow-x auto
&.mermaid svg
max-width 100% !important
&>span.filename
margin -0.5rem 100% 0.5rem -0.5rem
padding 0.125rem 0.375rem
background-color #777;
color white
&:empty
display none
&>span.lineNumber &>span.lineNumber
display none display none
font-size 1em font-size 1em
padding 0.5rem 0 padding 0.5em 0
margin -0.5rem 0.5rem -0.5rem -0.5rem margin -0.5em 0.5em -0.5em -0.5em
border-right 1px solid border-right 1px solid
text-align right text-align right
border-top-left-radius 4px border-top-left-radius 4px
@@ -266,7 +248,6 @@ table
display block display block
width 100% width 100%
margin 0 0 1em margin 0 0 1em
overflow-x auto
thead thead
tr tr
background-color tableHeadBgColor background-color tableHeadBgColor
@@ -303,147 +284,6 @@ 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]
dl
margin 2rem 0
padding 0
display flex
width 100%
flex-wrap wrap
align-items flex-start
border-bottom 1px solid borderColor
background-color tableHeadBgColor
dt
border-top 1px solid borderColor
font-weight bold
text-align right
overflow hidden
flex-basis 20%
padding 0.4rem 0.9rem
box-sizing border-box
dd
border-top 1px solid borderColor
flex-basis 80%
padding 0.4rem 0.9rem
min-height 2.5rem
background-color $ui-noteDetail-backgroundColor
box-sizing border-box
dd + dd
margin-left 20%
pre.fence
flex-wrap wrap
.chart, .flowchart, .mermaid, .sequence
display flex
justify-content center
background-color white
max-width 100%
flex-grow 1
canvas, svg
max-width 100% !important
.gallery
width 100%
height 50vh
.carousel
.carousel-main img
min-width auto
max-width 100%
min-height auto
max-height 100%
.carousel-footer::-webkit-scrollbar-corner
background-color transparent
.carousel-main, .carousel-footer
background-color $ui-noteDetail-backgroundColor
.prev, .next
color $ui-text-color
background-color $ui-tag-backgroundColor
themeDarkBackground = darken(#21252B, 10%) themeDarkBackground = darken(#21252B, 10%)
themeDarkText = #f9f9f9 themeDarkText = #f9f9f9
themeDarkBorder = lighten(themeDarkBackground, 20%) themeDarkBorder = lighten(themeDarkBackground, 20%)
@@ -494,22 +334,6 @@ body[data-theme="dark"]
kbd kbd
background-color themeDarkBorder background-color themeDarkBorder
color themeDarkText color themeDarkText
dl
border-color themeDarkBorder
background-color themeDarkTableHead
dt
border-color themeDarkBorder
dd
border-color themeDarkBorder
background-color themeDarkPreview
pre.fence
.gallery
.carousel-main, .carousel-footer
background-color $ui-dark-noteDetail-backgroundColor
.prev, .next
color $ui-dark-text-color
background-color $ui-dark-tag-backgroundColor
themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor
themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%) themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%)
@@ -537,111 +361,3 @@ 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
dl
border-color themeDarkBorder
background-color themeSolarizedDarkTableHead
dt
border-color themeDarkBorder
dd
border-color themeDarkBorder
background-color $ui-solarized-dark-noteDetail-backgroundColor
pre.fence
.gallery
.carousel-main, .carousel-footer
background-color $ui-solarized-dark-noteDetail-backgroundColor
.prev, .next
color $ui-solarized-dark-button--active-color
background-color $ui-solarized-dark-button-backgroundColor
themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
themeMonokaiTableHead = themeMonokaiTableEven
themeMonokaiTableBorder = themeDarkBorder
body[data-theme="monokai"]
color $ui-monokai-text-color
border-color themeDarkBorder
background-color $ui-monokai-noteDetail-backgroundColor
table
thead
tr
background-color themeMonokaiTableHead
th
border-color themeMonokaiTableBorder
&:last-child
border-right solid 1px themeMonokaiTableBorder
tbody
tr:nth-child(2n + 1)
background-color themeMonokaiTableOdd
tr:nth-child(2n)
background-color themeMonokaiTableEven
td
border-color themeMonokaiTableBorder
&:last-child
border-right solid 1px themeMonokaiTableBorder
kbd
background-color themeDarkBackground
dl
border-color themeDarkBorder
background-color themeMonokaiTableHead
dt
border-color themeDarkBorder
dd
border-color themeDarkBorder
background-color $ui-monokai-noteDetail-backgroundColor
pre.fence
.gallery
.carousel-main, .carousel-footer
background-color $ui-monokai-noteDetail-backgroundColor
.prev, .next
color $ui-monokai-button--active-color
background-color $ui-monokai-button-backgroundColor
themeDraculaTableOdd = $ui-dracula-noteDetail-backgroundColor
themeDraculaTableEven = darken($ui-dracula-noteDetail-backgroundColor, 10%)
themeDraculaTableHead = themeDraculaTableEven
themeDraculaTableBorder = themeDarkBorder
body[data-theme="dracula"]
color $ui-dracula-text-color
border-color themeDarkBorder
background-color $ui-dracula-noteDetail-backgroundColor
table
thead
tr
background-color themeDraculaTableHead
th
border-color themeDraculaTableBorder
&:last-child
border-right solid 1px themeDraculaTableBorder
tbody
tr:nth-child(2n + 1)
background-color themeDraculaTableOdd
tr:nth-child(2n)
background-color themeDraculaTableEven
td
border-color themeDraculaTableBorder
&:last-child
border-right solid 1px themeDraculaTableBorder
kbd
background-color themeDarkBackground
dl
border-color themeDarkBorder
background-color themeDraculaTableHead
dt
border-color themeDarkBorder
dd
border-color themeDarkBorder
background-color $ui-dracula-noteDetail-backgroundColor
pre.fence
.gallery
.carousel-main, .carousel-footer
background-color $ui-dracula-noteDetail-backgroundColor
.prev, .next
color $ui-dracula-button--active-color
background-color $ui-dracula-button-backgroundColor

View File

@@ -1,44 +0,0 @@
import mermaidAPI from 'mermaid'
// fixes bad styling in the mermaid dark theme
const darkThemeStyling = `
.loopText tspan {
fill: white;
}`
function getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min)) + min
}
function getId () {
const pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
let id = 'm-'
for (let i = 0; i < 7; i++) {
id += pool[getRandomInt(0, 16)]
}
return id
}
function render (element, content, theme, enableHTMLLabel) {
try {
const height = element.attributes.getNamedItem('data-height')
if (height && height.value !== 'undefined') {
element.style.height = height.value + 'vh'
}
const isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai' || theme === 'dracula'
mermaidAPI.initialize({
theme: isDarkTheme ? 'dark' : 'default',
themeCSS: isDarkTheme ? darkThemeStyling : '',
useMaxWidth: false,
flowchart: { htmlLabels: enableHTMLLabel }
})
mermaidAPI.render(getId(), content, (svgGraph) => {
element.innerHTML = svgGraph
})
} catch (e) {
element.className = 'mermaid-error'
element.innerHTML = 'mermaid diagram parse error: ' + e.message
}
}
export default render

View File

@@ -1,78 +0,0 @@
export const languageMaps = {
brainfuck: 'Brainfuck',
cpp: 'C++',
cs: 'C#',
clojure: 'Clojure',
'clojure-repl': 'ClojureScript',
cmake: 'CMake',
coffeescript: 'CoffeeScript',
crystal: 'Crystal',
css: 'CSS',
d: 'D',
dart: 'Dart',
delphi: 'Pascal',
diff: 'Diff',
django: 'Django',
dockerfile: 'Dockerfile',
ebnf: 'EBNF',
elm: 'Elm',
erlang: 'Erlang',
'erlang-repl': 'Erlang',
fortran: 'Fortran',
fsharp: 'F#',
gherkin: 'Gherkin',
go: 'Go',
groovy: 'Groovy',
haml: 'HAML',
haskell: 'Haskell',
haxe: 'Haxe',
http: 'HTTP',
ini: 'toml',
java: 'Java',
javascript: 'JavaScript',
json: 'JSON',
julia: 'Julia',
kotlin: 'Kotlin',
less: 'LESS',
livescript: 'LiveScript',
lua: 'Lua',
markdown: 'Markdown',
mathematica: 'Mathematica',
nginx: 'Nginx',
nsis: 'NSIS',
objectivec: 'Objective-C',
ocaml: 'Ocaml',
perl: 'Perl',
php: 'PHP',
powershell: 'PowerShell',
properties: 'Properties files',
protobuf: 'ProtoBuf',
python: 'Python',
puppet: 'Puppet',
q: 'Q',
r: 'R',
ruby: 'Ruby',
rust: 'Rust',
sas: 'SAS',
scala: 'Scala',
scheme: 'Scheme',
scss: 'SCSS',
shell: 'Shell',
smalltalk: 'Smalltalk',
sml: 'SML',
sql: 'SQL',
stylus: 'Stylus',
swift: 'Swift',
tcl: 'Tcl',
tex: 'LaTex',
typescript: 'TypeScript',
twig: 'Twig',
vbnet: 'VB.NET',
vbscript: 'VBScript',
verilog: 'Verilog',
vhdl: 'VHDL',
xml: 'HTML',
xquery: 'XQuery',
yaml: 'YAML',
elixir: 'Elixir'
}

View File

@@ -1,5 +1,5 @@
import CSSModules from 'react-css-modules' import CSSModules from 'react-css-modules'
export default function (component, styles) { export default function (component, styles) {
return CSSModules(component, styles, {handleNotFoundStyleName: 'log'}) return CSSModules(component, styles, {errorWhenNotFound: false})
} }

View File

@@ -1,86 +0,0 @@
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 (PT-BR)',
locale: 'pt-BR'
},
{
name: 'Portuguese (PT-PT)',
locale: 'pt-PT'
},
{
name: 'Russian',
locale: 'ru'
},
{
name: 'Spanish',
locale: 'es-ES'
},
{
name: 'Turkish',
locale: 'tr'
},
{
name: 'Thai',
locale: 'th'
}
]
module.exports = {
getLocales () {
return languages.reduce(function (localeList, locale) {
localeList.push(locale.locale)
return localeList
}, [])
},
getLanguages () {
return languages
}
}

View File

@@ -1,91 +0,0 @@
import crypto from 'crypto'
import fs from 'fs'
import consts from './consts'
class SnippetManager {
constructor () {
this.defaultSnippet = [
{
id: crypto.randomBytes(16).toString('hex'),
name: 'Dummy text',
prefix: ['lorem', 'ipsum'],
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
}
]
this.snippets = []
this.expandSnippet = this.expandSnippet.bind(this)
this.init = this.init.bind(this)
this.assignSnippets = this.assignSnippets.bind(this)
}
init () {
if (fs.existsSync(consts.SNIPPET_FILE)) {
try {
this.snippets = JSON.parse(
fs.readFileSync(consts.SNIPPET_FILE, { encoding: 'UTF-8' })
)
} catch (error) {
console.log('Error while parsing snippet file')
}
return
}
fs.writeFileSync(
consts.SNIPPET_FILE,
JSON.stringify(this.defaultSnippet, null, 4),
'utf8'
)
this.snippets = this.defaultSnippet
}
assignSnippets (snippets) {
this.snippets = snippets
}
expandSnippet (wordBeforeCursor, cursor, cm) {
const templateCursorString = ':{}'
for (let i = 0; i < this.snippets.length; i++) {
if (this.snippets[i].prefix.indexOf(wordBeforeCursor.text) === -1) {
continue
}
if (this.snippets[i].content.indexOf(templateCursorString) !== -1) {
const snippetLines = this.snippets[i].content.split('\n')
let cursorLineNumber = 0
let cursorLinePosition = 0
let cursorIndex
for (let j = 0; j < snippetLines.length; j++) {
cursorIndex = snippetLines[j].indexOf(templateCursorString)
if (cursorIndex !== -1) {
cursorLineNumber = j
cursorLinePosition = cursorIndex
break
}
}
cm.replaceRange(
this.snippets[i].content.replace(templateCursorString, ''),
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
cm.setCursor({
line: cursor.line + cursorLineNumber,
ch: cursorLinePosition + cursor.ch - wordBeforeCursor.text.length
})
} else {
cm.replaceRange(
this.snippets[i].content,
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
}
return true
}
return false
}
}
const manager = new SnippetManager()
export default manager

View File

@@ -1,113 +0,0 @@
import { Point } from '@susisu/mte-kernel'
export default class TextEditorInterface {
constructor (editor) {
this.editor = editor
this.doc = editor.getDoc()
this.transaction = false
}
getCursorPosition () {
const { line, ch } = this.doc.getCursor()
return new Point(line, ch)
}
setCursorPosition (pos) {
this.doc.setCursor({
line: pos.row,
ch: pos.column
})
}
setSelectionRange (range) {
this.doc.setSelection(
{ line: range.start.row, ch: range.start.column },
{ line: range.end.row, ch: range.end.column }
)
}
getLastRow () {
return this.doc.lineCount() - 1
}
acceptsTableEdit () {
return true
}
getLine (row) {
return this.doc.getLine(row)
}
insertLine (row, line) {
const lastRow = this.getLastRow()
if (row > lastRow) {
const lastLine = this.getLine(lastRow)
this.doc.replaceRange(
'\n' + line,
{ line: lastRow, ch: lastLine.length },
{ line: lastRow, ch: lastLine.length }
)
} else {
this.doc.replaceRange(
line + '\n',
{ line: row, ch: 0 },
{ line: row, ch: 0 }
)
}
}
deleteLine (row) {
const lastRow = this.getLastRow()
if (row >= lastRow) {
if (lastRow > 0) {
const preLastLine = this.getLine(lastRow - 1)
const lastLine = this.getLine(lastRow)
this.doc.replaceRange(
'',
{ line: lastRow - 1, ch: preLastLine.length },
{ line: lastRow, ch: lastLine.length }
)
} else {
const lastLine = this.getLine(lastRow)
this.doc.replaceRange(
'',
{ line: lastRow, ch: 0 },
{ line: lastRow, ch: lastLine.length }
)
}
} else {
this.doc.replaceRange(
'',
{ line: row, ch: 0 },
{ line: row + 1, ch: 0 }
)
}
}
replaceLines (startRow, endRow, lines) {
const lastRow = this.getLastRow()
if (endRow > lastRow) {
const lastLine = this.getLine(lastRow)
this.doc.replaceRange(
lines.join('\n'),
{ line: startRow, ch: 0 },
{ line: lastRow, ch: lastLine.length }
)
} else {
this.doc.replaceRange(
lines.join('\n') + '\n',
{ line: startRow, ch: 0 },
{ line: endRow, ch: 0 }
)
}
}
transact (func) {
this.transaction = true
func()
this.transaction = false
if (this.onDidFinishTransaction) {
this.onDidFinishTransaction.call(undefined)
}
}
}

View File

@@ -1,23 +0,0 @@
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
}

View File

@@ -3,47 +3,14 @@ const fs = require('sander')
const { remote } = require('electron') const { remote } = require('electron')
const { app } = remote const { app } = remote
const CODEMIRROR_THEME_PATH = 'node_modules/codemirror/theme' const themePath = process.env.NODE_ENV === 'production'
const CODEMIRROR_EXTRA_THEME_PATH = 'extra_scripts/codemirror/theme' ? path.join(app.getAppPath(), './node_modules/codemirror/theme')
: require('path').resolve('./node_modules/codemirror/theme')
const isProduction = process.env.NODE_ENV === 'production' const themes = fs.readdirSync(themePath)
.map((themePath) => {
const paths = [ return themePath.substring(0, themePath.lastIndexOf('.'))
isProduction ? path.join(app.getAppPath(), CODEMIRROR_THEME_PATH) : path.resolve(CODEMIRROR_THEME_PATH), })
isProduction ? path.join(app.getAppPath(), CODEMIRROR_EXTRA_THEME_PATH) : path.resolve(CODEMIRROR_EXTRA_THEME_PATH) themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
]
const themes = paths
.map(directory => fs.readdirSync(directory).map(file => {
const name = file.substring(0, file.lastIndexOf('.'))
return {
name,
path: path.join(directory, file),
className: `cm-s-${name}`
}
}))
.reduce((accumulator, value) => accumulator.concat(value), [])
.sort((a, b) => a.name.localeCompare(b.name))
themes.splice(themes.findIndex(({ name }) => name === 'solarized'), 1, {
name: 'solarized dark',
path: path.join(paths[0], 'solarized.css'),
className: `cm-s-solarized cm-s-dark`
}, {
name: 'solarized light',
path: path.join(paths[0], 'solarized.css'),
className: `cm-s-solarized cm-s-light`
})
themes.splice(0, 0, {
name: 'default',
path: path.join(paths[0], 'elegant.css'),
className: `cm-s-default`
})
const snippetFile = process.env.NODE_ENV !== 'test'
? path.join(app.getPath('userData'), 'snippets.json')
: '' // return nothing as we specified different path to snippets.json in test
const consts = { const consts = {
FOLDER_COLORS: [ FOLDER_COLORS: [
@@ -64,16 +31,7 @@ const consts = {
'Dodger Blue', 'Dodger Blue',
'Violet Eggplant' 'Violet Eggplant'
], ],
THEMES: themes, THEMES: ['default'].concat(themes)
SNIPPET_FILE: snippetFile,
DEFAULT_EDITOR_FONT_FAMILY: [
'Monaco',
'Menlo',
'Ubuntu Mono',
'Consolas',
'source-code-pro',
'monospace'
]
} }
module.exports = consts module.exports = consts

View File

@@ -1,124 +0,0 @@
import i18n from 'browser/lib/i18n'
import fs from 'fs'
const {remote} = require('electron')
const {Menu} = remote.require('electron')
const {clipboard} = remote.require('electron')
const {shell} = remote.require('electron')
const spellcheck = require('./spellcheck')
const uri2path = require('file-uri-to-path')
/**
* Creates the context menu that is shown when there is a right click in the editor of a (not-snippet) note.
* If the word is does not contains a spelling error (determined by the 'error style'), no suggestions for corrections are requested
* => they are not visible in the context menu
* @param editor CodeMirror editor
* @param {MouseEvent} event that has triggered the creation of the context menu
* @returns {Electron.Menu} The created electron context menu
*/
const buildEditorContextMenu = function (editor, event) {
if (editor == null || event == null || event.pageX == null || event.pageY == null) {
return null
}
const cursor = editor.coordsChar({left: event.pageX, top: event.pageY})
const wordRange = editor.findWordAt(cursor)
const word = editor.getRange(wordRange.anchor, wordRange.head)
const existingMarks = editor.findMarks(wordRange.anchor, wordRange.head) || []
let isMisspelled = false
for (const mark of existingMarks) {
if (mark.className === spellcheck.getCSSClassName()) {
isMisspelled = true
break
}
}
let suggestion = []
if (isMisspelled) {
suggestion = spellcheck.getSpellingSuggestion(word)
}
const selection = {
isMisspelled: isMisspelled,
spellingSuggestions: suggestion
}
const template = [{
role: 'cut'
}, {
role: 'copy'
}, {
role: 'paste'
}, {
role: 'selectall'
}]
if (selection.isMisspelled) {
const suggestions = selection.spellingSuggestions
template.unshift.apply(template, suggestions.map(function (suggestion) {
return {
label: suggestion,
click: function (suggestion) {
if (editor != null) {
editor.replaceRange(suggestion.label, wordRange.anchor, wordRange.head)
}
}
}
}).concat({
type: 'separator'
}))
}
return Menu.buildFromTemplate(template)
}
/**
* Creates the context menu that is shown when there is a right click Markdown preview of a (not-snippet) note.
* @param {MarkdownPreview} markdownPreview
* @param {MouseEvent} event that has triggered the creation of the context menu
* @returns {Electron.Menu} The created electron context menu
*/
const buildMarkdownPreviewContextMenu = function (markdownPreview, event) {
if (markdownPreview == null || event == null || event.pageX == null || event.pageY == null) {
return null
}
// Default context menu inclusions
const template = [{
role: 'copy'
}, {
role: 'selectall'
}]
if (event.target.tagName.toLowerCase() === 'a' && event.target.getAttribute('href')) {
// Link opener for files on the local system pointed to by href
const href = event.target.href
const isLocalFile = href.startsWith('file:')
if (isLocalFile) {
const absPath = uri2path(href)
try {
if (fs.lstatSync(absPath).isFile()) {
template.push(
{
label: i18n.__('Show in explorer'),
click: (e) => shell.showItemInFolder(absPath)
}
)
}
} catch (e) {
console.log('Error while evaluating if the file is locally available', e)
}
}
// Add option to context menu to copy url
template.push(
{
label: i18n.__('Copy Url'),
click: (e) => clipboard.writeText(href)
}
)
}
return Menu.buildFromTemplate(template)
}
module.exports =
{
buildEditorContextMenu: buildEditorContextMenu,
buildMarkdownPreviewContextMenu: buildMarkdownPreviewContextMenu
}

View File

@@ -1,14 +0,0 @@
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
}
}

View File

@@ -1,10 +1,5 @@
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir' import 'codemirror-mode-elixir'
const stylusCodeInfo = CodeMirror.modeInfo.find(info => info.name === 'Stylus') CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
if (stylusCodeInfo == null) {
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
} else {
stylusCodeInfo.alias = ['styl']
}
CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']}) CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']})

View File

@@ -1,37 +1,19 @@
export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleField = 'title') { export function findNoteTitle (value) {
const splitted = value.split('\n') const splitted = value.split('\n')
let title = null let title = null
let isInsideCodeBlock = false let isInsideCodeBlock = false
if (splitted[0] === '---') { splitted.some((line, index) => {
let line = 0 const trimmedLine = line.trim()
while (++line < splitted.length) { const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
if (enableFrontMatterTitle && splitted[line].startsWith(frontMatterTitleField + ':')) { if (trimmedLine.match('```')) {
title = splitted[line].substring(frontMatterTitleField.length + 1).trim() isInsideCodeBlock = !isInsideCodeBlock
break
}
if (splitted[line] === '---') {
splitted.splice(0, line + 1)
break
}
} }
} if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
title = trimmedLine
if (title === null) { return true
splitted.some((line, index) => { }
const trimmedLine = line.trim() })
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
if (trimmedLine.match('```')) {
isInsideCodeBlock = !isInsideCodeBlock
}
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
title = trimmedLine
return true
}
})
}
if (title === null) { if (title === null) {
title = '' title = ''

View File

@@ -4,11 +4,11 @@ export function getTodoStatus (content) {
let numberOfCompletedTodo = 0 let numberOfCompletedTodo = 0
splitted.forEach((line) => { splitted.forEach((line) => {
const trimmedLine = line.trim().replace(/^(>\s*)*/, '') const trimmedLine = line.trim()
if (trimmedLine.match(/^[+\-*] \[(\s|x)] ./i)) { if (trimmedLine.match(/^[\+\-\*] \[(\s|x)\] ./)) {
numberOfTodo++ numberOfTodo++
} }
if (trimmedLine.match(/^[+\-*] \[x] ./i)) { if (trimmedLine.match(/^[\+\-\*] \[x\] ./)) {
numberOfCompletedTodo++ numberOfCompletedTodo++
} }
}) })

View File

@@ -1,17 +0,0 @@
const path = require('path')
const { remote } = require('electron')
const { app } = remote
const { getLocales } = require('./Languages.js')
// load package for localization
const i18n = new (require('i18n-2'))({
// setup some locales - other locales default to the first locale
locales: getLocales(),
extension: '.json',
directory: process.env.NODE_ENV === 'production'
? path.join(app.getAppPath(), './locales')
: path.resolve('./locales'),
devMode: false
})
export default i18n

View File

@@ -1,10 +1,7 @@
const crypto = require('crypto') const crypto = require('crypto')
const uuidv4 = require('uuid/v4') const _ = require('lodash')
module.exports = function (uuid) { module.exports = function (length) {
if (typeof uuid === typeof true && uuid) { if (!_.isFinite(length)) length = 10
return uuidv4()
}
const length = 10
return crypto.randomBytes(length).toString('hex') return crypto.randomBytes(length).toString('hex')
} }

View File

@@ -1,232 +0,0 @@
'use strict'
module.exports = function definitionListPlugin (md) {
var isSpace = md.utils.isSpace
// Search `[:~][\n ]`, returns next pos after marker on success
// or -1 on fail.
function skipMarker (state, line) {
let start = state.bMarks[line] + state.tShift[line]
const max = state.eMarks[line]
if (start >= max) { return -1 }
// Check bullet
const marker = state.src.charCodeAt(start++)
if (marker !== 0x7E/* ~ */ && marker !== 0x3A/* : */) { return -1 }
const pos = state.skipSpaces(start)
// require space after ":"
if (start === pos) { return -1 }
return start
}
function markTightParagraphs (state, idx) {
const level = state.level + 2
let i
let l
for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
state.tokens[i + 2].hidden = true
state.tokens[i].hidden = true
i += 2
}
}
}
function deflist (state, startLine, endLine, silent) {
var ch,
contentStart,
ddLine,
dtLine,
itemLines,
listLines,
listTokIdx,
max,
newEndLine,
nextLine,
offset,
oldDDIndent,
oldIndent,
oldLineMax,
oldParentType,
oldSCount,
oldTShift,
oldTight,
pos,
prevEmptyEnd,
tight,
token
if (silent) {
// quirk: validation mode validates a dd block only, not a whole deflist
if (state.ddIndent < 0) { return false }
return skipMarker(state, startLine) >= 0
}
nextLine = startLine + 1
if (nextLine >= endLine) { return false }
if (state.isEmpty(nextLine)) {
nextLine++
if (nextLine >= endLine) { return false }
}
if (state.sCount[nextLine] < state.blkIndent) { return false }
contentStart = skipMarker(state, nextLine)
if (contentStart < 0) { return false }
// Start list
listTokIdx = state.tokens.length
tight = true
token = state.push('dl_open', 'dl', 1)
token.map = listLines = [ startLine, 0 ]
//
// Iterate list items
//
dtLine = startLine
ddLine = nextLine
// One definition list can contain multiple DTs,
// and one DT can be followed by multiple DDs.
//
// Thus, there is two loops here, and label is
// needed to break out of the second one
//
/* eslint no-labels:0,block-scoped-var:0 */
OUTER:
for (;;) {
prevEmptyEnd = false
token = state.push('dt_open', 'dt', 1)
token.map = [ dtLine, dtLine ]
token = state.push('inline', '', 0)
token.map = [ dtLine, dtLine ]
token.content = state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim()
token.children = []
token = state.push('dt_close', 'dt', -1)
for (;;) {
token = state.push('dd_open', 'dd', 1)
token.map = itemLines = [ ddLine, 0 ]
pos = contentStart
max = state.eMarks[ddLine]
offset = state.sCount[ddLine] + contentStart - (state.bMarks[ddLine] + state.tShift[ddLine])
while (pos < max) {
ch = state.src.charCodeAt(pos)
if (isSpace(ch)) {
if (ch === 0x09) {
offset += 4 - offset % 4
} else {
offset++
}
} else {
break
}
pos++
}
contentStart = pos
oldTight = state.tight
oldDDIndent = state.ddIndent
oldIndent = state.blkIndent
oldTShift = state.tShift[ddLine]
oldSCount = state.sCount[ddLine]
oldParentType = state.parentType
state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2
state.tShift[ddLine] = contentStart - state.bMarks[ddLine]
state.sCount[ddLine] = offset
state.tight = true
state.parentType = 'deflist'
newEndLine = ddLine
while (++newEndLine < endLine && (state.sCount[newEndLine] >= state.sCount[ddLine] || state.isEmpty(newEndLine))) {
}
oldLineMax = state.lineMax
state.lineMax = newEndLine
state.md.block.tokenize(state, ddLine, newEndLine, true)
state.lineMax = oldLineMax
// If any of list item is tight, mark list as tight
if (!state.tight || prevEmptyEnd) {
tight = false
}
// Item become loose if finish with empty line,
// but we should filter last element, because it means list finish
prevEmptyEnd = (state.line - ddLine) > 1 && state.isEmpty(state.line - 1)
state.tShift[ddLine] = oldTShift
state.sCount[ddLine] = oldSCount
state.tight = oldTight
state.parentType = oldParentType
state.blkIndent = oldIndent
state.ddIndent = oldDDIndent
token = state.push('dd_close', 'dd', -1)
itemLines[1] = nextLine = state.line
if (nextLine >= endLine) { break OUTER }
if (state.sCount[nextLine] < state.blkIndent) { break OUTER }
contentStart = skipMarker(state, nextLine)
if (contentStart < 0) { break }
ddLine = nextLine
// go to the next loop iteration:
// insert DD tag and repeat checking
}
if (nextLine >= endLine) { break }
dtLine = nextLine
if (state.isEmpty(dtLine)) { break }
if (state.sCount[dtLine] < state.blkIndent) { break }
ddLine = dtLine + 1
if (ddLine >= endLine) { break }
if (state.isEmpty(ddLine)) { ddLine++ }
if (ddLine >= endLine) { break }
if (state.sCount[ddLine] < state.blkIndent) { break }
contentStart = skipMarker(state, ddLine)
if (contentStart < 0) { break }
// go to the next loop iteration:
// insert DT and DD tags and repeat checking
}
// Finilize list
token = state.push('dl_close', 'dl', -1)
listLines[1] = nextLine
state.line = nextLine
// mark paragraphs tight if needed
if (tight) {
markTightParagraphs(state, listTokIdx)
}
return true
}
md.block.ruler.before('paragraph', 'deflist', deflist, { alt: [ 'paragraph', 'reference' ] })
}

View File

@@ -1,136 +0,0 @@
'use strict'
module.exports = function (md, renderers, defaultRenderer) {
const paramsRE = /^[ \t]*([\w+#-]+)?(?:\(((?:\s*\w[-\w]*(?:=(?:'(?:.*?[^\\])?'|"(?:.*?[^\\])?"|(?:[^'"][^\s]*)))?)*)\))?(?::([^:]*)(?::(\d+))?)?\s*$/
function fence (state, startLine, endLine, silent) {
let pos = state.bMarks[startLine] + state.tShift[startLine]
let max = state.eMarks[startLine]
if (state.sCount[startLine] - state.blkIndent >= 4 || pos + 3 > max) {
return false
}
const marker = state.src.charCodeAt(pos)
if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) {
return false
}
let mem = pos
pos = state.skipChars(pos, marker)
let len = pos - mem
if (len < 3) {
return false
}
const markup = state.src.slice(mem, pos)
const params = state.src.slice(pos, max)
if (silent) {
return true
}
let nextLine = startLine
let haveEndMarker = false
while (true) {
nextLine++
if (nextLine >= endLine) {
break
}
pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
max = state.eMarks[nextLine]
if (pos < max && state.sCount[nextLine] < state.blkIndent) {
break
}
if (state.src.charCodeAt(pos) !== marker || state.sCount[nextLine] - state.blkIndent >= 4) {
continue
}
pos = state.skipChars(pos, marker)
if (pos - mem < len) {
continue
}
pos = state.skipSpaces(pos)
if (pos >= max) {
haveEndMarker = true
break
}
}
len = state.sCount[startLine]
state.line = nextLine + (haveEndMarker ? 1 : 0)
const parameters = {}
let langType = ''
let fileName = ''
let firstLineNumber = 1
let match = paramsRE.exec(params)
if (match) {
if (match[1]) {
langType = match[1]
}
if (match[3]) {
fileName = match[3]
}
if (match[4]) {
firstLineNumber = parseInt(match[4], 10)
}
if (match[2]) {
const params = match[2]
const regex = /(\w[-\w]*)(?:=(?:'(.*?[^\\])?'|"(.*?[^\\])?"|([^'"][^\s]*)))?/g
let name, value
while ((match = regex.exec(params))) {
name = match[1]
value = match[2] || match[3] || match[4] || null
const height = /^(\d+)h$/.exec(name)
if (height && !value) {
parameters.height = height[1]
} else {
parameters[name] = value
}
}
}
}
let token
if (renderers[langType]) {
token = state.push(`${langType}_fence`, 'div', 0)
} else {
token = state.push('_fence', 'code', 0)
}
token.langType = langType
token.fileName = fileName
token.firstLineNumber = firstLineNumber
token.parameters = parameters
token.content = state.getLines(startLine + 1, nextLine, len, true)
token.markup = markup
token.map = [startLine, state.line]
return true
}
md.block.ruler.before('fence', '_fence', fence, {
alt: ['paragraph', 'reference', 'blockquote', 'list']
})
for (const name in renderers) {
md.renderer.rules[`${name}_fence`] = (tokens, index) => renderers[name](tokens[index])
}
if (defaultRenderer) {
md.renderer.rules['_fence'] = (tokens, index) => defaultRenderer(tokens[index])
}
}

View File

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

View File

@@ -1,128 +0,0 @@
'use strict'
import sanitizeHtml from 'sanitize-html'
import { escapeHtmlCharacters } from './utils'
import url from 'url'
module.exports = function sanitizePlugin (md, options) {
options = options || {}
md.core.ruler.after('linkify', 'sanitize_inline', state => {
for (let tokenIdx = 0; tokenIdx < state.tokens.length; tokenIdx++) {
if (state.tokens[tokenIdx].type === 'html_block') {
state.tokens[tokenIdx].content = sanitizeHtml(
state.tokens[tokenIdx].content,
options
)
}
if (state.tokens[tokenIdx].type.match(/.*_fence$/)) {
// escapeHtmlCharacters has better performance
state.tokens[tokenIdx].content = escapeHtmlCharacters(
state.tokens[tokenIdx].content,
{ skipSingleQuote: true }
)
}
if (state.tokens[tokenIdx].type === 'inline') {
const inlineTokens = state.tokens[tokenIdx].children
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
if (inlineTokens[childIdx].type === 'html_inline') {
inlineTokens[childIdx].content = sanitizeInline(
inlineTokens[childIdx].content,
options
)
}
}
}
}
})
}
const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:=("|')(?:[^\3]+?)\3)?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i
const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/ig
function sanitizeInline (html, options) {
let match = tagRegex.exec(html)
if (!match) {
return ''
}
const { allowedTags, allowedAttributes, selfClosing, allowedSchemesAppliedToAttributes } = options
if (match[1] !== undefined) {
// opening tag
const tag = match[1].toLowerCase()
if (allowedTags.indexOf(tag) === -1) {
return ''
}
const attributes = match[2]
let attrs = ''
let name
let value
while ((match = attributesRegex.exec(attributes))) {
name = match[1].toLowerCase()
value = match[3]
if (allowedAttributes['*'].indexOf(name) !== -1 || (allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)) {
if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) {
if (naughtyHRef(value, options) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value, options))) {
continue
}
}
attrs += ` ${name}`
if (match[2]) {
attrs += `="${value}"`
}
}
}
if (selfClosing.indexOf(tag) === -1) {
return '<' + tag + attrs + '>'
} else {
return '<' + tag + attrs + ' />'
}
} else {
// closing tag
if (allowedTags.indexOf(match[4].toLowerCase()) !== -1) {
return html
} else {
return ''
}
}
}
function naughtyHRef (href, options) {
// href = href.replace(/[\x00-\x20]+/g, '')
if (!href) {
// No href
return false
}
href = href.replace(/<\!\-\-.*?\-\-\>/g, '')
const matches = href.match(/^([a-zA-Z]+)\:/)
if (!matches) {
if (href.match(/^[\/\\]{2}/)) {
return !options.allowProtocolRelative
}
// No scheme
return false
}
const scheme = matches[1].toLowerCase()
return options.allowedSchemes.indexOf(scheme) === -1
}
function naughtyIFrame (src, options) {
try {
const parsed = url.parse(src, false, true)
return options.allowedIframeHostnames.index(parsed.hostname) === -1
} catch (e) {
return true
}
}

View File

@@ -1,99 +0,0 @@
/**
* @fileoverview Markdown table of contents generator
*/
import { EOL } from 'os'
import toc from 'markdown-toc'
import mdlink from 'markdown-link'
import slugify from './slugify'
const hasProp = Object.prototype.hasOwnProperty
/**
* From @enyaxu/markdown-it-anchor
*/
function uniqueSlug (slug, slugs, opts) {
let uniq = slug
let i = opts.uniqueSlugStartIndex
while (hasProp.call(slugs, uniq)) uniq = `${slug}-${i++}`
slugs[uniq] = true
return uniq
}
function linkify (token) {
token.content = mdlink(token.content, `#${decodeURI(token.slug)}`)
return token
}
const TOC_MARKER_START = '<!-- toc -->'
const TOC_MARKER_END = '<!-- tocstop -->'
const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
/**
* Takes care of proper updating given editor with TOC.
* If TOC doesn't exit in the editor, it's inserted at current caret position.
* Otherwise,TOC is updated in place.
* @param editor CodeMirror editor to be updated with TOC
*/
export function generateInEditor (editor) {
function updateExistingToc () {
const toc = generate(editor.getValue())
const search = editor.getSearchCursor(tocRegex)
while (search.findNext()) {
search.replace(toc)
}
}
function addTocAtCursorPosition () {
const toc = generate(editor.getRange(editor.getCursor(), {line: Infinity}))
editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor())
}
if (tocExistsInEditor(editor)) {
updateExistingToc()
} else {
addTocAtCursorPosition()
}
}
export function tocExistsInEditor (editor) {
return tocRegex.test(editor.getValue())
}
/**
* Generates MD TOC based on MD document passed as string.
* @param markdownText MD document
* @returns generatedTOC String containing generated TOC
*/
export function generate (markdownText) {
const slugs = {}
const opts = {
uniqueSlugStartIndex: 1
}
const result = toc(markdownText, {
slugify: title => {
return uniqueSlug(slugify(title), slugs, opts)
},
linkify: false
})
const md = toc.bullets(result.json.map(linkify), {
highest: result.highest
})
return TOC_MARKER_START + EOL + EOL + md + EOL + EOL + TOC_MARKER_END
}
function wrapTocWithEol (toc, editor) {
const leftWrap = editor.getCursor().ch === 0 ? '' : EOL
const rightWrap = editor.getLine(editor.getCursor().line).length === editor.getCursor().ch ? '' : EOL
return leftWrap + toc + rightWrap
}
export default {
generate,
generateInEditor,
tocExistsInEditor
}

View File

@@ -1,332 +1,173 @@
import markdownit from 'markdown-it' import markdownit from 'markdown-it'
import sanitize from './markdown-it-sanitize-html'
import emoji from 'markdown-it-emoji' import emoji from 'markdown-it-emoji'
import math from '@rokt33r/markdown-it-math' import math from '@rokt33r/markdown-it-math'
import mdurl from 'mdurl'
import smartArrows from 'markdown-it-smartarrows'
import markdownItTocAndAnchor from '@hikerpig/markdown-it-toc-and-anchor'
import _ from 'lodash' import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import katex from 'katex'
import { lastFindInArray } from './utils'
function createGutter (str, firstLineNumber) { // FIXME We should not depend on global variable.
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1 const katex = window.katex
const lastLineNumber = (str.match(/\n/g) || []).length + firstLineNumber - 1 const config = ConfigManager.get()
function createGutter (str) {
const lc = (str.match(/\n/g) || []).length
const lines = [] const lines = []
for (let i = firstLineNumber; i <= lastLineNumber; i++) { for (let i = 1; i <= lc; i++) {
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>') lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
} }
return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>' return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
} }
class Markdown { var md = markdownit({
constructor (options = {}) { typographer: true,
const config = ConfigManager.get() linkify: true,
const defaultOptions = { html: true,
typographer: config.preview.smartQuotes, xhtmlOut: true,
linkify: true, breaks: true,
html: true, highlight: function (str, lang) {
xhtmlOut: true, if (lang === 'flowchart') {
breaks: config.preview.breaks, return `<pre class="flowchart">${str}</pre>`
sanitize: 'STRICT'
} }
if (lang === 'sequence') {
const updatedOptions = Object.assign(defaultOptions, options) return `<pre class="sequence">${str}</pre>`
this.md = markdownit(updatedOptions)
this.md.linkify.set({ fuzzyLink: false })
if (updatedOptions.sanitize !== 'NONE') {
const allowedTags = ['iframe', 'input', 'b',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'br', 'b', 'i', 'strong', 'em', 'a', 'pre', 'code', 'img', 'tt',
'div', 'ins', 'del', 'sup', 'sub', 'p', 'ol', 'ul', 'table', 'thead', 'tbody', 'tfoot', 'blockquote',
'dl', 'dt', 'dd', 'kbd', 'q', 'samp', 'var', 'hr', 'ruby', 'rt', 'rp', 'li', 'tr', 'td', 'th', 's', 'strike', 'summary', 'details'
]
const allowedAttributes = [
'abbr', 'accept', 'accept-charset',
'accesskey', 'action', 'align', 'alt', 'axis',
'border', 'cellpadding', 'cellspacing', 'char',
'charoff', 'charset', 'checked',
'clear', 'cols', 'colspan', 'color',
'compact', 'coords', 'datetime', 'dir',
'disabled', 'enctype', 'for', 'frame',
'headers', 'height', 'hreflang',
'hspace', 'ismap', 'label', 'lang',
'maxlength', 'media', 'method',
'multiple', 'name', 'nohref', 'noshade',
'nowrap', 'open', 'prompt', 'readonly', 'rel', 'rev',
'rows', 'rowspan', 'rules', 'scope',
'selected', 'shape', 'size', 'span',
'start', 'summary', 'tabindex', 'target',
'title', 'type', 'usemap', 'valign', 'value',
'vspace', 'width', 'itemprop'
]
if (updatedOptions.sanitize === 'ALLOW_STYLES') {
allowedTags.push('style')
allowedAttributes.push('style')
}
// Sanitize use rinput before other plugins
this.md.use(sanitize, {
allowedTags,
allowedAttributes: {
'*': allowedAttributes,
'a': ['href'],
'div': ['itemscope', 'itemtype'],
'blockquote': ['cite'],
'del': ['cite'],
'ins': ['cite'],
'q': ['cite'],
'img': ['src', 'width', 'height'],
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
'input': ['type', 'id', 'checked']
},
allowedIframeHostnames: ['www.youtube.com'],
selfClosing: [ 'img', 'br', 'hr', 'input' ],
allowedSchemes: [ 'http', 'https', 'ftp', 'mailto' ],
allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ],
allowProtocolRelative: true
})
} }
return '<pre class="code">' +
this.md.use(emoji, { createGutter(str) +
shortcuts: {} '<code class="' + lang + '">' +
}) str +
this.md.use(math, { '</code></pre>'
inlineOpen: config.preview.latexInlineOpen, }
inlineClose: config.preview.latexInlineClose, })
blockOpen: config.preview.latexBlockOpen, md.use(emoji, {
blockClose: config.preview.latexBlockClose, shortcuts: {}
inlineRenderer: function (str) { })
let output = '' md.use(math, {
try { inlineOpen: config.preview.latexInlineOpen,
output = katex.renderToString(str.trim()) inlineClose: config.preview.latexInlineClose,
} catch (err) { blockOpen: config.preview.latexBlockOpen,
output = `<span class="katex-error">${err.message}</span>` blockClose: config.preview.latexBlockClose,
} inlineRenderer: function (str) {
return output let output = ''
}, try {
blockRenderer: function (str) { output = katex.renderToString(str.trim())
let output = '' } catch (err) {
try { output = `<span class="katex-error">${err.message}</span>`
output = katex.renderToString(str.trim(), { displayMode: true })
} catch (err) {
output = `<div class="katex-error">${err.message}</div>`
}
return output
}
})
this.md.use(require('markdown-it-imsize'))
this.md.use(require('markdown-it-footnote'))
this.md.use(require('markdown-it-multimd-table'))
this.md.use(require('@enyaxu/markdown-it-anchor'), {
slugify: require('./slugify')
})
this.md.use(require('markdown-it-kbd'))
this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error']})
this.md.use(require('markdown-it-abbr'))
this.md.use(require('markdown-it-sub'))
this.md.use(require('markdown-it-sup'))
this.md.use(markdownItTocAndAnchor, {
toc: true,
tocPattern: /\[TOC\]/i,
anchorLink: false,
appendIdToHeading: false
})
this.md.use(require('./markdown-it-deflist'))
this.md.use(require('./markdown-it-frontmatter'))
this.md.use(require('./markdown-it-fence'), {
chart: token => {
if (token.parameters.hasOwnProperty('yaml')) {
token.parameters.format = 'yaml'
}
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="chart" data-height="${token.parameters.height}" data-format="${token.parameters.format || 'json'}">${token.content}</div>
</pre>`
},
flowchart: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="flowchart" data-height="${token.parameters.height}">${token.content}</div>
</pre>`
},
gallery: token => {
const content = token.content.split('\n').slice(0, -1).map(line => {
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
if (match) {
return mdurl.encode(match[1])
} else {
return mdurl.encode(line)
}
}).join('\n')
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="gallery" data-autoplay="${token.parameters.autoplay}" data-height="${token.parameters.height}">${content}</div>
</pre>`
},
mermaid: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="mermaid" data-height="${token.parameters.height}">${token.content}</div>
</pre>`
},
sequence: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="sequence" data-height="${token.parameters.height}">${token.content}</div>
</pre>`
}
}, token => {
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
${createGutter(token.content, token.firstLineNumber)}
<code class="${token.langType}">${token.content}</code>
</pre>`
})
const deflate = require('markdown-it-plantuml/lib/deflate')
const plantuml = require('markdown-it-plantuml')
const plantUmlStripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
const plantUmlServerAddress = plantUmlStripTrailingSlash(config.preview.plantUMLServerAddress)
const parsePlantUml = function (umlCode, openMarker, closeMarker, type) {
const s = unescape(encodeURIComponent(umlCode))
const zippedCode = deflate.encode64(
deflate.zip_deflate(`${openMarker}\n${s}\n${closeMarker}`, 9)
)
return `${plantUmlServerAddress}/${type}/${zippedCode}`
} }
return output
this.md.use(plantuml, { },
generateSource: (umlCode) => parsePlantUml(umlCode, '@startuml', '@enduml', 'svg') blockRenderer: function (str) {
}) let output = ''
try {
// Ditaa support. PlantUML server doesn't support Ditaa in SVG, so we set the format as PNG at the moment. output = katex.renderToString(str.trim(), { displayMode: true })
this.md.use(plantuml, { } catch (err) {
openMarker: '@startditaa', output = `<div class="katex-error">${err.message}</div>`
closeMarker: '@endditaa',
generateSource: (umlCode) => parsePlantUml(umlCode, '@startditaa', '@endditaa', 'png')
})
// Mindmap support
this.md.use(plantuml, {
openMarker: '@startmindmap',
closeMarker: '@endmindmap',
generateSource: (umlCode) => parsePlantUml(umlCode, '@startmindmap', '@endmindmap', 'svg')
})
// WBS support
this.md.use(plantuml, {
openMarker: '@startwbs',
closeMarker: '@endwbs',
generateSource: (umlCode) => parsePlantUml(umlCode, '@startwbs', '@endwbs', 'svg')
})
// Gantt support
this.md.use(plantuml, {
openMarker: '@startgantt',
closeMarker: '@endgantt',
generateSource: (umlCode) => parsePlantUml(umlCode, '@startgantt', '@endgantt', 'svg')
})
// Override task item
this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
let content, terminate, i, l, token
let nextLine = startLine + 1
const terminatorRules = state.md.block.ruler.getRules('paragraph')
const endLine = state.lineMax
// jump line-by-line until empty one or EOF
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
// this would be a code block normally, but after paragraph
// it's considered a lazy continuation regardless of what's there
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
// quirk for blockquotes, this line should already be checked by that rule
if (state.sCount[nextLine] < 0) { continue }
// Some tags can terminate paragraph without empty line.
terminate = false
for (i = 0, l = terminatorRules.length; i < l; i++) {
if (terminatorRules[i](state, nextLine, endLine, true)) {
terminate = true
break
}
}
if (terminate) { break }
}
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
state.line = nextLine
token = state.push('paragraph_open', 'p', 1)
token.map = [startLine, state.line]
if (state.parentType === 'list') {
const match = content.match(/^\[( |x)\] ?(.+)/i)
if (match) {
const liToken = lastFindInArray(state.tokens, token => token.type === 'list_item_open')
if (liToken) {
if (!liToken.attrs) {
liToken.attrs = []
}
if (config.preview.lineThroughCheckbox) {
liToken.attrs.push(['class', `taskListItem${match[1] !== ' ' ? ' checked' : ''}`])
} else {
liToken.attrs.push(['class', 'taskListItem'])
}
}
content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
}
}
token = state.push('inline', '', 0)
token.content = content
token.map = [startLine, state.line]
token.children = []
token = state.push('paragraph_close', 'p', -1)
return true
})
if (config.preview.smartArrows) {
this.md.use(smartArrows)
} }
return output
}
})
md.use(require('markdown-it-imsize'))
md.use(require('markdown-it-footnote'))
md.use(require('markdown-it-multimd-table'))
md.use(require('markdown-it-named-headers'), {
slugify: (header) => {
return encodeURI(header.trim()
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
.replace(/\s+/g, '-'))
.replace(/\-+$/, '')
}
})
md.use(require('markdown-it-kbd'))
// Add line number attribute for scrolling const deflate = require('markdown-it-plantuml/lib/deflate')
const originalRender = this.md.renderer.render md.use(require('markdown-it-plantuml'), '', {
this.md.renderer.render = (tokens, options, env) => { generateSource: function (umlCode) {
tokens.forEach((token) => { const s = unescape(encodeURIComponent(umlCode))
switch (token.type) { const zippedCode = deflate.encode64(
case 'blockquote_open': deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
case 'dd_open': )
case 'dt_open': return `http://www.plantuml.com/plantuml/svg/${zippedCode}`
case 'heading_open': }
case 'list_item_open': })
case 'paragraph_open':
case 'table_open': // Override task item
if (token.map) { md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
token.attrPush(['data-line', token.map[0]]) let content, terminate, i, l, token
} let nextLine = startLine + 1
} const terminatorRules = state.md.block.ruler.getRules('paragraph')
}) const endLine = state.lineMax
const result = originalRender.call(this.md.renderer, tokens, options, env)
return result // jump line-by-line until empty one or EOF
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
// this would be a code block normally, but after paragraph
// it's considered a lazy continuation regardless of what's there
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
// quirk for blockquotes, this line should already be checked by that rule
if (state.sCount[nextLine] < 0) { continue }
// Some tags can terminate paragraph without empty line.
terminate = false
for (i = 0, l = terminatorRules.length; i < l; i++) {
if (terminatorRules[i](state, nextLine, endLine, true)) {
terminate = true
break
}
} }
// FIXME We should not depend on global variable. if (terminate) { break }
window.md = this.md
} }
render (content) { content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
if (!_.isString(content)) content = ''
return this.md.render(content) state.line = nextLine
token = state.push('paragraph_open', 'p', 1)
token.map = [startLine, state.line]
if (state.parentType === 'list') {
const match = content.match(/^\[( |x)\] ?(.+)/i)
if (match) {
content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
}
} }
token = state.push('inline', '', 0)
token.content = content
token.map = [startLine, state.line]
token.children = []
token = state.push('paragraph_close', 'p', -1)
return true
})
// Add line number attribute for scrolling
const originalRender = md.renderer.render
md.renderer.render = function render (tokens, options, env) {
tokens.forEach((token) => {
switch (token.type) {
case 'heading_open':
case 'paragraph_open':
case 'blockquote_open':
case 'table_open':
token.attrPush(['data-line', token.map[0]])
}
})
const result = originalRender.call(md.renderer, tokens, options, env)
return result
}
// FIXME We should not depend on global variable.
window.md = md
function normalizeLinkText (linkText) {
return md.normalizeLinkText(linkText)
} }
export default Markdown const markdown = {
render: function markdown (content) {
if (!_.isString(content)) content = ''
const renderedContent = md.render(content)
return renderedContent
},
normalizeLinkText
}
export default markdown

View File

@@ -22,7 +22,7 @@ export function strip (input) {
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1') .replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
.replace(/>/g, '') .replace(/>/g, '')
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '') .replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
.replace(/^#{1,6}\s*/gm, '') .replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1')
.replace(/(`{3,})(.*?)\1/gm, '$2') .replace(/(`{3,})(.*?)\1/gm, '$2')
.replace(/^-{3,}\s*$/g, '') .replace(/^-{3,}\s*$/g, '')
.replace(/`(.+?)`/g, '$1') .replace(/`(.+?)`/g, '$1')

View File

@@ -1,81 +0,0 @@
import dataApi from 'browser/main/lib/dataApi'
import ee from 'browser/main/lib/eventEmitter'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import queryString from 'query-string'
import { push } from 'connected-react-router'
export function createMarkdownNote (storage, folder, dispatch, location, params, config) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
let tags = []
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
tags = params.tagname.split(' ')
}
return dataApi
.createNote(storage, {
type: 'MARKDOWN_NOTE',
folder: folder,
title: '',
tags,
content: '',
linesHighlighted: []
})
.then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
dispatch(push({
pathname: location.pathname,
search: queryString.stringify({ key: noteHash })
}))
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
})
}
export function createSnippetNote (storage, folder, dispatch, location, params, config) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
let tags = []
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
tags = params.tagname.split(' ')
}
const defaultLanguage = config.editor.snippetDefaultLanguage === 'Auto Detect' ? null : config.editor.snippetDefaultLanguage
return dataApi
.createNote(storage, {
type: 'SNIPPET_NOTE',
folder: folder,
title: '',
tags,
description: '',
snippets: [
{
name: '',
mode: defaultLanguage,
content: '',
linesHighlighted: []
}
]
})
.then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
dispatch(push({
pathname: location.pathname,
search: queryString.stringify({ key: noteHash })
}))
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
})
}

View File

@@ -1,9 +0,0 @@
import consts from 'browser/lib/consts'
import isString from 'lodash/isString'
export default function normalizeEditorFontFamily (fontFamily) {
const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
return isString(fontFamily) && fontFamily.length > 0
? [fontFamily].concat(defaultEditorFontFamily).join(', ')
: defaultEditorFontFamily.join(', ')
}

View File

@@ -4,30 +4,39 @@ export default function searchFromNotes (notes, search) {
if (search.trim().length === 0) return [] if (search.trim().length === 0) return []
const searchBlocks = search.split(' ').filter(block => { return block !== '' }) const searchBlocks = search.split(' ').filter(block => { return block !== '' })
let foundNotes = notes let foundNotes = findByWord(notes, searchBlocks[0])
searchBlocks.forEach((block) => { searchBlocks.forEach((block) => {
foundNotes = findByWordOrTag(foundNotes, block) foundNotes = findByWord(foundNotes, block)
if (block.match(/^#.+/)) {
foundNotes = foundNotes.concat(findByTag(notes, block))
}
}) })
return foundNotes return foundNotes
} }
function findByWordOrTag (notes, block) { function findByTag (notes, block) {
let tag = block const tag = block.match(/#(.+)/)[1]
if (tag.match(/^#.+/)) { const regExp = new RegExp(_.escapeRegExp(tag), 'i')
tag = tag.match(/#(.+)/)[1]
}
const tagRegExp = new RegExp(_.escapeRegExp(tag), 'i')
const wordRegExp = new RegExp(_.escapeRegExp(block), 'i')
return notes.filter((note) => { return notes.filter((note) => {
if (_.isArray(note.tags) && note.tags.some((_tag) => _tag.match(tagRegExp))) { if (!_.isArray(note.tags)) return false
return note.tags.some((_tag) => {
return _tag.match(regExp)
})
})
}
function findByWord (notes, block) {
const regExp = new RegExp(_.escapeRegExp(block), 'i')
return notes.filter((note) => {
if (_.isArray(note.tags) && note.tags.some((_tag) => {
return _tag.match(regExp)
})) {
return true return true
} }
if (note.type === 'SNIPPET_NOTE') { if (note.type === 'SNIPPET_NOTE') {
return note.description.match(wordRegExp) || note.snippets.some((snippet) => { return note.description.match(regExp)
return snippet.name.match(wordRegExp) || snippet.content.match(wordRegExp)
})
} else if (note.type === 'MARKDOWN_NOTE') { } else if (note.type === 'MARKDOWN_NOTE') {
return note.content.match(wordRegExp) return note.content.match(regExp)
} }
return false return false
}) })

View File

@@ -1,11 +0,0 @@
module.exports = function slugify (title) {
const slug = encodeURI(
title.trim()
.replace(/^\s+/, '')
.replace(/\s+$/, '')
.replace(/\s+/g, '-')
.replace(/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`]/g, '')
)
return slug
}

View File

@@ -1,232 +0,0 @@
import styles from '../components/CodeEditor.styl'
import i18n from 'browser/lib/i18n'
const Typo = require('typo-js')
const _ = require('lodash')
const CSS_ERROR_CLASS = 'codeEditor-typo'
const SPELLCHECK_DISABLED = 'NONE'
const DICTIONARY_PATH = '../dictionaries'
const MILLISECONDS_TILL_LIVECHECK = 500
let dictionary = null
let self
function getAvailableDictionaries () {
return [
{label: i18n.__('Spellcheck disabled'), value: SPELLCHECK_DISABLED},
{label: i18n.__('English'), value: 'en_GB'},
{label: i18n.__('German'), value: 'de_DE'},
{label: i18n.__('French'), value: 'fr_FR'}
]
}
/**
* Only to be used in the tests :)
*/
function setDictionaryForTestsOnly (newDictionary) {
dictionary = newDictionary
}
/**
* @description Initializes the spellcheck. It removes all existing marks of the current editor.
* If a language was given (i.e. lang !== this.SPELLCHECK_DISABLED) it will load the stated dictionary and use it to check the whole document.
* @param {Codemirror} editor CodeMirror-Editor
* @param {String} lang on of the values from getAvailableDictionaries()-Method
*/
function setLanguage (editor, lang) {
self = this
dictionary = null
if (editor == null) {
return
}
const existingMarks = editor.getAllMarks() || []
for (const mark of existingMarks) {
mark.clear()
}
if (lang !== SPELLCHECK_DISABLED) {
dictionary = new Typo(lang, false, false, {
dictionaryPath: DICTIONARY_PATH,
asyncLoad: true,
loadedCallback: () =>
checkWholeDocument(editor)
})
}
}
/**
* Checks the whole content of the editor for typos
* @param {Codemirror} editor CodeMirror-Editor
*/
function checkWholeDocument (editor) {
const lastLine = editor.lineCount() - 1
const textOfLastLine = editor.getLine(lastLine) || ''
const lastChar = textOfLastLine.length
const from = {line: 0, ch: 0}
const to = {line: lastLine, ch: lastChar}
checkMultiLineRange(editor, from, to)
}
/**
* Checks the given range for typos
* @param {Codemirror} editor CodeMirror-Editor
* @param {line, ch} from starting position of the spellcheck
* @param {line, ch} to end position of the spellcheck
*/
function checkMultiLineRange (editor, from, to) {
function sortRange (pos1, pos2) {
if (pos1.line > pos2.line || (pos1.line === pos2.line && pos1.ch > pos2.ch)) {
return {from: pos2, to: pos1}
}
return {from: pos1, to: pos2}
}
const {from: smallerPos, to: higherPos} = sortRange(from, to)
for (let l = smallerPos.line; l <= higherPos.line; l++) {
const line = editor.getLine(l) || ''
let w = 0
if (l === smallerPos.line) {
w = smallerPos.ch
}
let wEnd = line.length
if (l === higherPos.line) {
wEnd = higherPos.ch
}
while (w <= wEnd) {
const wordRange = editor.findWordAt({line: l, ch: w})
self.checkWord(editor, wordRange)
w += (wordRange.head.ch - wordRange.anchor.ch) + 1
}
}
}
/**
* @description Checks whether a certain range of characters in the editor (i.e. a word) contains a typo.
* If so the ranged will be marked with the class CSS_ERROR_CLASS.
* Note: Due to performance considerations, only words with more then 3 signs are checked.
* @param {Codemirror} editor CodeMirror-Editor
* @param wordRange Object specifying the range that should be checked.
* Having the following structure: <code>{anchor: {line: integer, ch: integer}, head: {line: integer, ch: integer}}</code>
*/
function checkWord (editor, wordRange) {
const word = editor.getRange(wordRange.anchor, wordRange.head)
if (word == null || word.length <= 3) {
return
}
if (!dictionary.check(word)) {
editor.markText(wordRange.anchor, wordRange.head, {className: styles[CSS_ERROR_CLASS]})
}
}
/**
* Checks the changes recently made (aka live check)
* @param {Codemirror} editor CodeMirror-Editor
* @param fromChangeObject codeMirror changeObject describing the start of the editing
* @param toChangeObject codeMirror changeObject describing the end of the editing
*/
function checkChangeRange (editor, fromChangeObject, toChangeObject) {
/**
* Calculate the smallest respectively largest position as a start, resp. end, position and return it
* @param start CodeMirror change object
* @param end CodeMirror change object
* @returns {{start: {line: *, ch: *}, end: {line: *, ch: *}}}
*/
function getStartAndEnd (start, end) {
const possiblePositions = [start.from, start.to, end.from, end.to]
let smallest = start.from
let biggest = end.to
for (const currentPos of possiblePositions) {
if (currentPos.line < smallest.line || (currentPos.line === smallest.line && currentPos.ch < smallest.ch)) {
smallest = currentPos
}
if (currentPos.line > biggest.line || (currentPos.line === biggest.line && currentPos.ch > biggest.ch)) {
biggest = currentPos
}
}
return {start: smallest, end: biggest}
}
if (dictionary === null || editor == null) { return }
try {
const {start, end} = getStartAndEnd(fromChangeObject, toChangeObject)
// Expand the range to include words after/before whitespaces
start.ch = Math.max(start.ch - 1, 0)
end.ch = end.ch + 1
// clean existing marks
const existingMarks = editor.findMarks(start, end) || []
for (const mark of existingMarks) {
mark.clear()
}
self.checkMultiLineRange(editor, start, end)
} catch (e) {
console.info('Error during the spell check. It might be due to problems figuring out the range of the new text..', e)
}
}
function saveLiveSpellCheckFrom (changeObject) {
liveSpellCheckFrom = changeObject
}
let liveSpellCheckFrom
const debouncedSpellCheckLeading = _.debounce(saveLiveSpellCheckFrom, MILLISECONDS_TILL_LIVECHECK, {
'leading': true,
'trailing': false
})
const debouncedSpellCheck = _.debounce(checkChangeRange, MILLISECONDS_TILL_LIVECHECK, {
'leading': false,
'trailing': true
})
/**
* Handles a keystroke. Buffers the input and performs a live spell check after a certain time. Uses _debounce from lodash to buffer the input
* @param {Codemirror} editor CodeMirror-Editor
* @param changeObject codeMirror changeObject
*/
function handleChange (editor, changeObject) {
if (dictionary === null) {
return
}
debouncedSpellCheckLeading(changeObject)
debouncedSpellCheck(editor, liveSpellCheckFrom, changeObject)
}
/**
* Returns an array of spelling suggestions for the given (wrong written) word.
* Returns an empty array if the dictionary is null (=> spellcheck is disabled) or the given word was null
* @param word word to be checked
* @returns {String[]} Array of suggestions
*/
function getSpellingSuggestion (word) {
if (dictionary == null || word == null) {
return []
}
return dictionary.suggest(word)
}
/**
* Returns the name of the CSS class used for errors
*/
function getCSSClassName () {
return styles[CSS_ERROR_CLASS]
}
module.exports = {
DICTIONARY_PATH,
CSS_ERROR_CLASS,
SPELLCHECK_DISABLED,
getAvailableDictionaries,
setLanguage,
checkChangeRange,
handleChange,
getSpellingSuggestion,
checkWord,
checkMultiLineRange,
checkWholeDocument,
setDictionaryForTestsOnly,
getCSSClassName
}

View File

@@ -1,9 +0,0 @@
const TurndownService = require('turndown')
const { gfm } = require('turndown-plugin-gfm')
export const createTurndownService = function () {
const turndown = new TurndownService()
turndown.use(gfm)
turndown.remove('script')
return turndown
}

View File

@@ -1,159 +0,0 @@
export function lastFindInArray (array, callback) {
for (let i = array.length - 1; i >= 0; --i) {
if (callback(array[i], i, array)) {
return array[i]
}
}
}
export function escapeHtmlCharacters (
html,
opt = { detectCodeBlock: false, skipSingleQuote: false }
) {
const matchHtmlRegExp = /["'&<>]/g
const matchCodeBlockRegExp = /```/g
const escapes = ['&quot;', '&amp;', '&#39;', '&lt;', '&gt;']
let match = null
const replaceAt = (str, index, replace) =>
str.substr(0, index) +
replace +
str.substr(index + replace.length - (replace.length - 1))
while ((match = matchHtmlRegExp.exec(html)) !== null) {
const current = { char: match[0], index: match.index }
const codeBlockIndexs = []
let openCodeBlock = null
// if the detectCodeBlock option is activated then this function should skip
// characters that needed to be escape but located in code block
if (opt.detectCodeBlock) {
// The first type of code block is lines that start with 4 spaces
// Here we check for the \n character located before the character that
// needed to be escape. It means we check for the begining of the line that
// contain that character, then we check if there are 4 spaces next to the
// \n character (the line start with 4 spaces)
let previousLineEnd = current.index - 1
while (html[previousLineEnd] !== '\n' && previousLineEnd !== -1) {
previousLineEnd--
}
// 4 spaces means this character is in a code block
if (
html[previousLineEnd + 1] === ' ' &&
html[previousLineEnd + 2] === ' ' &&
html[previousLineEnd + 3] === ' ' &&
html[previousLineEnd + 4] === ' '
) {
// skip the current character
continue
}
// The second type of code block is lines that wrapped in ```
// We will get the position of each ```
// then push it into an array
// then the array returned will be like this:
// [startCodeblock, endCodeBlock, startCodeBlock, endCodeBlock]
while ((openCodeBlock = matchCodeBlockRegExp.exec(html)) !== null) {
codeBlockIndexs.push(openCodeBlock.index)
}
let shouldSkipChar = false
// we loop through the array of positions
// we skip 2 element as the i index position is the position of ``` that
// open the codeblock and the i + 1 is the position of the ``` that close
// the code block
for (let i = 0; i < codeBlockIndexs.length; i += 2) {
// the i index position is the position of the ``` that open code block
// so we have to + 2 as that position is the position of the first ` in the ````
// but we need to make sure that the position current character is larger
// that the last ` in the ``` that open the code block so we have to take
// the position of the first ` and + 2
// the i + 1 index position is the closing ``` so the char must less than it
if (
current.index > codeBlockIndexs[i] + 2 &&
current.index < codeBlockIndexs[i + 1]
) {
// skip it
shouldSkipChar = true
break
}
}
if (shouldSkipChar) {
// skip the current character
continue
}
}
// otherwise, escape it !!!
if (current.char === '&') {
// when escaping character & we have to be becareful as the & could be a part
// of an escaped character like &quot; will be came &amp;quot;
let nextStr = ''
let nextIndex = current.index
let escapedStr = false
// maximum length of an escaped string is 5. For example ('&quot;')
// we take the next 5 character of the next string if it is one of the string:
// ['&quot;', '&amp;', '&#39;', '&lt;', '&gt;'] then we will not escape the & character
// as it is a part of the escaped string and should not be escaped
while (nextStr.length <= 5) {
nextStr += html[nextIndex]
nextIndex++
if (escapes.indexOf(nextStr) !== -1) {
escapedStr = true
break
}
}
if (!escapedStr) {
// this & char is not a part of an escaped string
html = replaceAt(html, current.index, '&amp;')
}
} else if (current.char === '"') {
html = replaceAt(html, current.index, '&quot;')
} else if (current.char === "'" && !opt.skipSingleQuote) {
html = replaceAt(html, current.index, '&#39;')
} else if (current.char === '<') {
html = replaceAt(html, current.index, '&lt;')
} else if (current.char === '>') {
html = replaceAt(html, current.index, '&gt;')
}
}
return 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 function isMarkdownTitleURL (str) {
return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
}
export function humanFileSize (bytes) {
const threshold = 1000
if (Math.abs(bytes) < threshold) {
return bytes + ' B'
}
var units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
var u = -1
do {
bytes /= threshold
++u
} while (Math.abs(bytes) >= threshold && u < units.length - 1)
return bytes.toFixed(1) + ' ' + units[u]
}
export default {
lastFindInArray,
escapeHtmlCharacters,
isObjectEqual,
isMarkdownTitleURL,
humanFileSize
}

View File

@@ -23,24 +23,10 @@ body[data-theme="dark"]
border-left 1px solid $ui-dark-borderColor border-left 1px solid $ui-dark-borderColor
.empty-message .empty-message
color $ui-dark-inactive-text-color color $ui-dark-inactive-text-color
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.root .root
background-color $ui-solarized-dark-noteDetail-backgroundColor background-color $ui-solarized-dark-noteDetail-backgroundColor
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
body[data-theme="dracula"]
.root
background-color $ui-dracula-noteDetail-backgroundColor
border-left 1px solid $ui-dracula-borderColor
.empty-message
color $ui-dracula-text-color

View File

@@ -3,7 +3,6 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './FolderSelect.styl' import styles from './FolderSelect.styl'
import _ from 'lodash' import _ from 'lodash'
import i18n from 'browser/lib/i18n'
class FolderSelect extends React.Component { class FolderSelect extends React.Component {
constructor (props) { constructor (props) {
@@ -250,7 +249,7 @@ class FolderSelect extends React.Component {
<input styleName='search-input' <input styleName='search-input'
ref='search' ref='search'
value={this.state.search} value={this.state.search}
placeholder={i18n.__('Folder...')} placeholder='Folder...'
onChange={(e) => this.handleSearchInputChange(e)} onChange={(e) => this.handleSearchInputChange(e)}
onBlur={(e) => this.handleSearchInputBlur(e)} onBlur={(e) => this.handleSearchInputBlur(e)}
onKeyDown={(e) => this.handleSearchInputKeyDown(e)} onKeyDown={(e) => this.handleSearchInputKeyDown(e)}

View File

@@ -36,7 +36,7 @@
height 34px height 34px
width 20px width 20px
line-height 34px line-height 34px
.search-input .search-input
vertical-align middle vertical-align middle
position relative position relative
@@ -71,7 +71,7 @@
overflow ellipsis overflow ellipsis
cursor pointer cursor pointer
&:hover &:hover
background-color $ui-button--hover-backgroundColor background-color $ui-button--hover-backgroundColor
.search-optionList-item--active .search-optionList-item--active
@extend .search-optionList-item @extend .search-optionList-item
@@ -133,55 +133,3 @@ 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
body[data-theme="dracula"]
.root
color $ui-dracula-text-color
&:hover
color #f8f8f2
background-color $ui-dark-button--hover-backgroundColor
border-color $ui-dracula-borderColor
.search-optionList
color #f8f8f2
border-color $ui-dracula-borderColor
background-color $ui-dracula-button-backgroundColor
.search-optionList-item
&:hover
background-color lighten($ui-dracula-button--hover-backgroundColor, 15%)
.search-optionList-item--active
background-color $ui-dracula-button--active-backgroundColor
color $ui-dracula-button--active-color
&:hover
background-color $ui-dark-button--hover-backgroundColor
color $ui-dracula-button--active-color
.search-optionList-item-name-surfix
color $ui-dracula-inactive-text-color

View File

@@ -1,69 +0,0 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './FromUrlButton.styl'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
class FromUrlButton extends React.Component {
constructor (props) {
super(props)
this.state = {
isActive: false
}
}
handleMouseDown (e) {
this.setState({
isActive: true
})
}
handleMouseUp (e) {
this.setState({
isActive: false
})
}
handleMouseLeave (e) {
this.setState({
isActive: false
})
}
render () {
const { className } = this.props
return (
<button className={_.isString(className)
? 'FromUrlButton ' + className
: 'FromUrlButton'
}
styleName={this.state.isActive || this.props.isActive
? 'root--active'
: 'root'
}
onMouseDown={(e) => this.handleMouseDown(e)}
onMouseUp={(e) => this.handleMouseUp(e)}
onMouseLeave={(e) => this.handleMouseLeave(e)}
onClick={this.props.onClick}>
<img styleName='icon'
src={this.state.isActive || this.props.isActive
? '../resources/icon/icon-external.svg'
: '../resources/icon/icon-external.svg'
}
/>
<span styleName='tooltip'>{i18n.__('Convert URL to Markdown')}</span>
</button>
)
}
}
FromUrlButton.propTypes = {
isActive: PropTypes.bool,
onClick: PropTypes.func,
className: PropTypes.string
}
export default CSSModules(FromUrlButton, styles)

View File

@@ -1,41 +0,0 @@
.root
top 45px
topBarButtonRight()
&:hover
transition 0.2s
color alpha($ui-favorite-star-button-color, 0.6)
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 50px
right 125px
width 90px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.root--active
@extend .root
transition 0.15s
color $ui-favorite-star-button-color
&:hover
transition 0.2s
color alpha($ui-favorite-star-button-color, 0.6)
.icon
transition transform 0.15s
height 13px
body[data-theme="dark"]
.root
topBarButtonDark()
&:hover
transition 0.2s
color alpha($ui-favorite-star-button-color, 0.6)

View File

@@ -2,20 +2,15 @@ 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 styles from './FullscreenButton.styl' import styles from './FullscreenButton.styl'
import i18n from 'browser/lib/i18n'
const OSX = global.process.platform === 'darwin'
const FullscreenButton = ({ const FullscreenButton = ({
onClick onClick
}) => { }) => (
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B' <button styleName='control-fullScreenButton' title='Fullscreen' onMouseDown={(e) => onClick(e)}>
return ( <img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}> <span styleName='tooltip'>Fullscreen</span>
<img src='../resources/icon/icon-full.svg' /> </button>
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span> )
</button>
)
}
FullscreenButton.propTypes = { FullscreenButton.propTypes = {
onClick: PropTypes.func.isRequired onClick: PropTypes.func.isRequired

View File

@@ -17,10 +17,6 @@
opacity 0 opacity 0
transition 0.1s transition 0.1s
.tooltip:lang(ja)
@extend .tooltip
right 35px
body[data-theme="dark"] body[data-theme="dark"]
.control-fullScreenButton .control-fullScreenButton
topBarButtonDark() topBarButtonDark()

View File

@@ -2,7 +2,6 @@ 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 styles from './InfoButton.styl' import styles from './InfoButton.styl'
import i18n from 'browser/lib/i18n'
const InfoButton = ({ const InfoButton = ({
onClick onClick
@@ -11,7 +10,7 @@ const InfoButton = ({
onClick={(e) => onClick(e)} onClick={(e) => onClick(e)}
> >
<img className='infoButton' src='../resources/icon/icon-info.svg' /> <img className='infoButton' src='../resources/icon/icon-info.svg' />
<span styleName='tooltip'>{i18n.__('Info')}</span> <span styleName='tooltip'>Info</span>
</button> </button>
) )

View File

@@ -3,7 +3,6 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoPanel.styl' import styles from './InfoPanel.styl'
import copy from 'copy-to-clipboard' import copy from 'copy-to-clipboard'
import i18n from 'browser/lib/i18n'
class InfoPanel extends React.Component { class InfoPanel extends React.Component {
copyNoteLink () { copyNoteLink () {
@@ -14,13 +13,13 @@ class InfoPanel extends React.Component {
render () { render () {
const { const {
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf, wordCount, letterCount, type, print storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, wordCount, letterCount, type, print
} = this.props } = this.props
return ( return (
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}> <div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
<div> <div>
<p styleName='modification-date'>{updatedAt}</p> <p styleName='modification-date'>{updatedAt}</p>
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p> <p styleName='modification-date-desc'>MODIFICATION DATE</p>
</div> </div>
<hr /> <hr />
@@ -30,11 +29,11 @@ class InfoPanel extends React.Component {
: <div styleName='count-wrap'> : <div styleName='count-wrap'>
<div styleName='count-number'> <div styleName='count-number'>
<p styleName='infoPanel-defaul-count'>{wordCount}</p> <p styleName='infoPanel-defaul-count'>{wordCount}</p>
<p styleName='infoPanel-sub-count'>{i18n.__('Words')}</p> <p styleName='infoPanel-sub-count'>Words</p>
</div> </div>
<div styleName='count-number'> <div styleName='count-number'>
<p styleName='infoPanel-defaul-count'>{letterCount}</p> <p styleName='infoPanel-defaul-count'>{letterCount}</p>
<p styleName='infoPanel-sub-count'>{i18n.__('Letters')}</p> <p styleName='infoPanel-sub-count'>Letters</p>
</div> </div>
</div> </div>
} }
@@ -46,53 +45,48 @@ class InfoPanel extends React.Component {
<div> <div>
<p styleName='infoPanel-default'>{storageName}</p> <p styleName='infoPanel-default'>{storageName}</p>
<p styleName='infoPanel-sub'>{i18n.__('STORAGE')}</p> <p styleName='infoPanel-sub'>STORAGE</p>
</div> </div>
<div> <div>
<p styleName='infoPanel-default'>{folderName}</p> <p styleName='infoPanel-default'>{folderName}</p>
<p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p> <p styleName='infoPanel-sub'>FOLDER</p>
</div> </div>
<div> <div>
<p styleName='infoPanel-default'>{createdAt}</p> <p styleName='infoPanel-default'>{createdAt}</p>
<p styleName='infoPanel-sub'>{i18n.__('CREATION DATE')}</p> <p styleName='infoPanel-sub'>CREATION DATE</p>
</div> </div>
<div> <div>
<input styleName='infoPanel-noteLink' ref='noteLink' defaultValue={noteLink} onClick={(e) => { e.target.select() }} /> <input styleName='infoPanel-noteLink' ref='noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'> <button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
<i className='fa fa-clipboard' /> <i className='fa fa-clipboard' />
</button> </button>
<p styleName='infoPanel-sub'>{i18n.__('NOTE LINK')}</p> <p styleName='infoPanel-sub'>NOTE LINK</p>
</div> </div>
<hr /> <hr />
<div id='export-wrap'> <div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}> <button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
<i className='fa fa-file-code-o' /> <i className='fa fa-file-code-o' />
<p>{i18n.__('.md')}</p> <p>.md</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e, 'export-txt')}> <button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
<i className='fa fa-file-text-o' /> <i className='fa fa-file-text-o' />
<p>{i18n.__('.txt')}</p> <p>.txt</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}> <button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
<i className='fa fa-html5' /> <i className='fa fa-html5' />
<p>{i18n.__('.html')}</p> <p>.html</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}> <button styleName='export--enable' onClick={(e) => print(e)}>
<i className='fa fa-file-pdf-o' />
<p>{i18n.__('.pdf')}</p>
</button>
<button styleName='export--enable' onClick={(e) => print(e, 'print')}>
<i className='fa fa-print' /> <i className='fa fa-print' />
<p>{i18n.__('Print')}</p> <p>Print</p>
</button> </button>
</div> </div>
</div> </div>
@@ -109,7 +103,6 @@ InfoPanel.propTypes = {
exportAsMd: PropTypes.func.isRequired, exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired, exportAsTxt: PropTypes.func.isRequired,
exportAsHtml: PropTypes.func.isRequired, exportAsHtml: PropTypes.func.isRequired,
exportAsPdf: PropTypes.func.isRequired,
wordCount: PropTypes.number, wordCount: PropTypes.number,
letterCount: PropTypes.number, letterCount: PropTypes.number,
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,

View File

@@ -11,11 +11,10 @@
.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
// width 300px width 300px
overflow auto overflow auto
background-color $ui-noteList-backgroundColor background-color $ui-noteList-backgroundColor
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1) box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
@@ -33,7 +32,6 @@
.control-infoButton-panel-trash .control-infoButton-panel-trash
z-index 200 z-index 200
margin-top 0px margin-top 0px
top 50px
right 0px right 0px
position absolute position absolute
padding 20px 25px 0 25px padding 20px 25px 0 25px
@@ -217,83 +215,3 @@ 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
body[data-theme="dracula"]
.control-infoButton-panel
background-color $ui-dracula-noteList-backgroundColor
.control-infoButton-panel-trash
background-color $ui-dracula-noteList-backgroundColor
.modification-date
color $ui-dracula-text-color
.modification-date-desc
color $ui-inactive-text-color
.infoPanel-defaul-count
color $ui-dracula-text-color
.infoPanel-sub-count
color $ui-inactive-text-color
.infoPanel-default
color $ui-dracula-text-color
.infoPanel-sub
color $ui-inactive-text-color
.infoPanel-noteLink
background-color alpha($ui-dracula-borderColor, 20%)
color $ui-dracula-text-color
[id=export-wrap]
button
color $ui-dark-inactive-text-color
&:hover
background-color alpha($ui-dracula-borderColor, 20%)
color $ui-dracula-text-color
p
color $ui-dark-inactive-text-color
&:hover
color $ui-dracula-text-color

View File

@@ -2,51 +2,50 @@ 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 styles from './InfoPanel.styl' import styles from './InfoPanel.styl'
import i18n from 'browser/lib/i18n'
const InfoPanelTrashed = ({ const InfoPanelTrashed = ({
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml
}) => ( }) => (
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}> <div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
<div> <div>
<p styleName='modification-date'>{updatedAt}</p> <p styleName='modification-date'>{updatedAt}</p>
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p> <p styleName='modification-date-desc'>MODIFICATION DATE</p>
</div> </div>
<hr /> <hr />
<div> <div>
<p styleName='infoPanel-default'>{storageName}</p> <p styleName='infoPanel-default'>{storageName}</p>
<p styleName='infoPanel-sub'>{i18n.__('STORAGE')}</p> <p styleName='infoPanel-sub'>STORAGE</p>
</div> </div>
<div> <div>
<p styleName='infoPanel-default'><text styleName='infoPanel-trash'>Trash</text>{folderName}</p> <p styleName='infoPanel-default'><text styleName='infoPanel-trash'>Trash</text>{folderName}</p>
<p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p> <p styleName='infoPanel-sub'>FOLDER</p>
</div> </div>
<div> <div>
<p styleName='infoPanel-default'>{createdAt}</p> <p styleName='infoPanel-default'>{createdAt}</p>
<p styleName='infoPanel-sub'>{i18n.__('CREATION DATE')}</p> <p styleName='infoPanel-sub'>CREATION DATE</p>
</div> </div>
<div id='export-wrap'> <div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}> <button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
<i className='fa fa-file-code-o' /> <i className='fa fa-file-code-o' />
<p>.md</p> <p>.md</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e, 'export-txt')}> <button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
<i className='fa fa-file-text-o' /> <i className='fa fa-file-text-o' />
<p>.txt</p> <p>.txt</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}> <button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
<i className='fa fa-html5' /> <i className='fa fa-html5' />
<p>.html</p> <p>.html</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}> <button styleName='export--unable'>
<i className='fa fa-file-pdf-o' /> <i className='fa fa-file-pdf-o' />
<p>.pdf</p> <p>.pdf</p>
</button> </button>
@@ -61,8 +60,7 @@ InfoPanelTrashed.propTypes = {
createdAt: PropTypes.string.isRequired, createdAt: PropTypes.string.isRequired,
exportAsMd: PropTypes.func.isRequired, exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired, exportAsTxt: PropTypes.func.isRequired,
exportAsHtml: PropTypes.func.isRequired, exportAsHtml: PropTypes.func.isRequired
exportAsPdf: PropTypes.func.isRequired
} }
export default CSSModules(InfoPanelTrashed, styles) export default CSSModules(InfoPanelTrashed, styles)

183
browser/main/Detail/MarkdownNoteDetail.js Executable file → Normal file
View File

@@ -9,6 +9,7 @@ import StarButton from './StarButton'
import TagSelect from './TagSelect' import TagSelect from './TagSelect'
import FolderSelect from './FolderSelect' import FolderSelect from './FolderSelect'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import markdown from 'browser/lib/markdownTextHelper' import markdown from 'browser/lib/markdownTextHelper'
import StatusBar from '../StatusBar' import StatusBar from '../StatusBar'
@@ -18,7 +19,6 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import TrashButton from './TrashButton' import TrashButton from './TrashButton'
import FullscreenButton from './FullscreenButton' import FullscreenButton from './FullscreenButton'
import RestoreButton from './RestoreButton'
import PermanentDeleteButton from './PermanentDeleteButton' import PermanentDeleteButton from './PermanentDeleteButton'
import InfoButton from './InfoButton' import InfoButton from './InfoButton'
import ToggleModeButton from './ToggleModeButton' import ToggleModeButton from './ToggleModeButton'
@@ -27,10 +27,6 @@ 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'
import markdownToc from 'browser/lib/markdown-toc-generator'
import queryString from 'query-string'
import { replace } from 'connected-react-router'
class MarkdownNoteDetail extends React.Component { class MarkdownNoteDetail extends React.Component {
constructor (props) { constructor (props) {
@@ -40,19 +36,15 @@ class MarkdownNoteDetail extends React.Component {
isMovingNote: false, isMovingNote: false,
note: Object.assign({ note: Object.assign({
title: '', title: '',
content: '', content: ''
linesHighlighted: []
}, props.note), }, props.note),
isLockButtonShown: props.config.editor.type !== 'SPLIT', isLockButtonShown: false,
isLocked: false, isLocked: false,
editorType: props.config.editor.type, editorType: props.config.editor.type
switchPreview: props.config.editor.switchPreview
} }
this.dispatchTimer = null this.dispatchTimer = null
this.toggleLockButton = this.handleToggleLockButton.bind(this) this.toggleLockButton = this.handleToggleLockButton.bind(this)
this.generateToc = () => this.handleGenerateToc()
} }
focus () { focus () {
@@ -61,48 +53,28 @@ 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)
})
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
ee.on('code:generate-toc', this.generateToc)
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
const isNewNote = nextProps.note.key !== this.props.note.key if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
const hasDeletedTags = nextProps.note.tags.length < this.props.note.tags.length
if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) {
if (this.saveQueue != null) this.saveNow() if (this.saveQueue != null) this.saveNow()
this.setState({ this.setState({
note: Object.assign({linesHighlighted: []}, nextProps.note) note: Object.assign({}, nextProps.note)
}, () => { }, () => {
this.refs.content.reload() this.refs.content.reload()
if (this.refs.tags) this.refs.tags.reset() if (this.refs.tags) this.refs.tags.reset()
}) })
} }
// Focus content if using blur or double click
// --> Moved here from componentDidMount so a re-render during search won't set focus to the editor
const {switchPreview} = nextProps.config.editor
if (this.state.switchPreview !== switchPreview) {
this.setState({
switchPreview
})
if (switchPreview === 'BLUR' || switchPreview === 'DBL_CLICK') {
console.log('setting focus', switchPreview)
this.focus()
}
}
} }
componentWillUnmount () { componentWillUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
ee.off('code:generate-toc', this.generateToc)
if (this.saveQueue != null) this.saveNow() if (this.saveQueue != null) this.saveNow()
} }
componentDidUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
}
handleUpdateTag () { handleUpdateTag () {
const { note } = this.state const { note } = this.state
if (this.refs.tags) note.tags = this.refs.tags.value if (this.refs.tags) note.tags = this.refs.tags.value
@@ -112,12 +84,7 @@ class MarkdownNoteDetail extends React.Component {
handleUpdateContent () { handleUpdateContent () {
const { note } = this.state const { note } = this.state
note.content = this.refs.content.value note.content = this.refs.content.value
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
let title = findNoteTitle(note.content, this.props.config.editor.enableFrontMatterTitle, this.props.config.editor.frontMatterTitleField)
title = striptags(title)
title = markdown.strip(title)
note.title = title
this.updateNote(note) this.updateNote(note)
} }
@@ -171,12 +138,12 @@ class MarkdownNoteDetail extends React.Component {
originNote: note, originNote: note,
note: newNote note: newNote
}) })
dispatch(replace({ hashHistory.replace({
pathname: location.pathname, pathname: location.pathname,
search: queryString.stringify({ query: {
key: newNote.key key: newNote.storage + '-' + newNote.key
}) }
})) })
this.setState({ this.setState({
isMovingNote: false isMovingNote: false
}) })
@@ -213,47 +180,13 @@ class MarkdownNoteDetail extends React.Component {
ee.emit('export:save-html') ee.emit('export:save-html')
} }
exportAsPdf () {
ee.emit('export:save-pdf')
}
handleKeyDown (e) {
switch (e.keyCode) {
// tab key
case 9:
if (e.ctrlKey && !e.shiftKey) {
e.preventDefault()
this.jumpNextTab()
} else if (e.ctrlKey && e.shiftKey) {
e.preventDefault()
this.jumpPrevTab()
} else if (!e.ctrlKey && !e.shiftKey && e.target === this.refs.description) {
e.preventDefault()
this.focusEditor()
}
break
// I key
case 73:
{
const isSuper = global.process.platform === 'darwin'
? e.metaKey
: e.ctrlKey
if (isSuper) {
e.preventDefault()
this.handleInfoButtonClick(e)
}
}
break
}
}
handleTrashButtonClick (e) { handleTrashButtonClick (e) {
const { note } = this.state const { note } = this.state
const { isTrashed } = note const { isTrashed } = note
const { confirmDeletion } = this.props.config.ui const { confirmDeletion } = this.props
if (isTrashed) { if (isTrashed) {
if (confirmDeleteNote(confirmDeletion, true)) { if (confirmDeletion(true)) {
const {note, dispatch} = this.props const {note, dispatch} = this.props
dataApi dataApi
.deleteNote(note.storage, note.key) .deleteNote(note.storage, note.key)
@@ -265,12 +198,11 @@ class MarkdownNoteDetail extends React.Component {
noteKey: data.noteKey noteKey: data.noteKey
}) })
} }
ee.once('list:next', dispatchHandler) ee.once('list:moved', dispatchHandler)
}) })
.then(() => ee.emit('list:next'))
} }
} else { } else {
if (confirmDeleteNote(confirmDeletion, false)) { if (confirmDeletion()) {
note.isTrashed = true note.isTrashed = true
this.setState({ this.setState({
@@ -310,7 +242,7 @@ class MarkdownNoteDetail extends React.Component {
} }
getToggleLockButton () { getToggleLockButton () {
return this.state.isLocked ? '../resources/icon/icon-lock.svg' : '../resources/icon/icon-unlock.svg' return this.state.isLocked ? '../resources/icon/icon-previewoff-on.svg' : '../resources/icon/icon-previewoff-off.svg'
} }
handleDeleteKeyDown (e) { handleDeleteKeyDown (e) {
@@ -319,18 +251,13 @@ class MarkdownNoteDetail extends React.Component {
handleToggleLockButton (event, noteStatus) { handleToggleLockButton (event, noteStatus) {
// first argument event is not used // first argument event is not used
if (noteStatus === 'CODE') { if (this.props.config.editor.switchPreview === 'BLUR' && noteStatus === 'CODE') {
this.setState({isLockButtonShown: true}) this.setState({isLockButtonShown: true})
} else { } else {
this.setState({isLockButtonShown: false}) this.setState({isLockButtonShown: false})
} }
} }
handleGenerateToc () {
const editor = this.refs.content.refs.code.editor
markdownToc.generateInEditor(editor)
}
handleFocus (e) { handleFocus (e) {
this.focus() this.focus()
} }
@@ -345,42 +272,16 @@ class MarkdownNoteDetail extends React.Component {
} }
handleSwitchMode (type) { handleSwitchMode (type) {
// If in split mode, hide the lock button this.setState({ editorType: type }, () => {
this.setState({ editorType: type, isLockButtonShown: !(type === 'SPLIT') }, () => {
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)
}) })
} }
handleDeleteNote () {
this.handleTrashButtonClick()
}
handleClearTodo () {
const { note } = this.state
const splitted = note.content.split('\n')
const clearTodoContent = splitted.map((line) => {
const trimmedLine = line.trim()
if (trimmedLine.match(/\[x\]/i)) {
return line.replace(/\[x\]/i, '[ ]')
} else {
return line
}
}).join('\n')
note.content = clearTodoContent
this.refs.content.setValue(note.content)
this.updateNote(note)
}
renderEditor () { renderEditor () {
const { config, ignorePreviewPointerEvents } = this.props const { config, ignorePreviewPointerEvents } = this.props
const { note } = this.state const { note } = this.state
if (this.state.editorType === 'EDITOR_PREVIEW') { if (this.state.editorType === 'EDITOR_PREVIEW') {
return <MarkdownEditor return <MarkdownEditor
ref='content' ref='content'
@@ -388,10 +289,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}
linesHighlighted={note.linesHighlighted}
onChange={this.handleUpdateContent.bind(this)} onChange={this.handleUpdateContent.bind(this)}
isLocked={this.state.isLocked}
ignorePreviewPointerEvents={ignorePreviewPointerEvents} ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/> />
} else { } else {
@@ -400,8 +298,6 @@ class MarkdownNoteDetail extends React.Component {
config={config} config={config}
value={note.content} value={note.content}
storageKey={note.storage} storageKey={note.storage}
noteKey={note.key}
linesHighlighted={note.linesHighlighted}
onChange={this.handleUpdateContent.bind(this)} onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents} ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/> />
@@ -409,7 +305,7 @@ class MarkdownNoteDetail extends React.Component {
} }
render () { render () {
const { data, dispatch, location, config } = this.props const { data, location } = this.props
const { note, editorType } = this.state const { note, editorType } = this.state
const storageKey = note.storage const storageKey = note.storage
const folderKey = note.folder const folderKey = note.folder
@@ -427,7 +323,10 @@ class MarkdownNoteDetail extends React.Component {
const trashTopBar = <div styleName='info'> const trashTopBar = <div styleName='info'>
<div styleName='info-left'> <div styleName='info-left'>
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} /> <i styleName='undo-button'
className='fa fa-undo fa-fw'
onClick={(e) => this.handleUndoButtonClick(e)}
/>
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} /> <PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -442,14 +341,13 @@ class MarkdownNoteDetail extends React.Component {
exportAsHtml={this.exportAsHtml} exportAsHtml={this.exportAsHtml}
exportAsMd={this.exportAsMd} exportAsMd={this.exportAsMd}
exportAsTxt={this.exportAsTxt} exportAsTxt={this.exportAsTxt}
exportAsPdf={this.exportAsPdf}
/> />
</div> </div>
</div> </div>
const detailTopBar = <div styleName='info'> const detailTopBar = <div styleName='info'>
<div styleName='info-left'> <div styleName='info-left'>
<div> <div styleName='info-left-top'>
<FolderSelect styleName='info-left-top-folderSelect' <FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder} value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder' ref='folder'
@@ -461,18 +359,14 @@ class MarkdownNoteDetail extends React.Component {
<TagSelect <TagSelect
ref='tags' ref='tags'
value={this.state.note.tags} value={this.state.note.tags}
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
showTagsAlphabetically={config.ui.showTagsAlphabetically}
data={data}
dispatch={dispatch}
onChange={this.handleUpdateTag.bind(this)} onChange={this.handleUpdateTag.bind(this)}
coloredTags={config.coloredTags}
/> />
<TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
</div>
<div styleName='info-right'>
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} /> <ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
</div>
<div styleName='info-right'>
<StarButton <StarButton
onClick={(e) => this.handleStarButtonClick(e)} onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred} isActive={note.isStarred}
@@ -485,7 +379,7 @@ class MarkdownNoteDetail extends React.Component {
onFocus={(e) => this.handleFocus(e)} onFocus={(e) => this.handleFocus(e)}
onMouseDown={(e) => this.handleLockButtonMouseDown(e)} onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
> >
<img src={imgSrc} /> <img styleName='iconInfo' src={imgSrc} />
{this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>} {this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
</button> </button>
@@ -505,14 +399,13 @@ class MarkdownNoteDetail extends React.Component {
<InfoPanel <InfoPanel
storageName={currentOption.storage.name} storageName={currentOption.storage.name}
folderName={currentOption.folder.name} folderName={currentOption.folder.name}
noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`} noteLink={`[${note.title}](${location.query.key})`}
updatedAt={formatDate(note.updatedAt)} updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)} createdAt={formatDate(note.createdAt)}
exportAsMd={this.exportAsMd} exportAsMd={this.exportAsMd}
exportAsTxt={this.exportAsTxt} exportAsTxt={this.exportAsTxt}
exportAsHtml={this.exportAsHtml} exportAsHtml={this.exportAsHtml}
exportAsPdf={this.exportAsPdf} wordCount={note.content.split(' ').length}
wordCount={note.content.trim().split(/\s+/g).length}
letterCount={note.content.replace(/\r?\n/g, '').length} letterCount={note.content.replace(/\r?\n/g, '').length}
type={note.type} type={note.type}
print={this.print} print={this.print}
@@ -524,7 +417,6 @@ class MarkdownNoteDetail extends React.Component {
<div className='NoteDetail' <div className='NoteDetail'
style={this.props.style} style={this.props.style}
styleName='root' styleName='root'
onKeyDown={(e) => this.handleKeyDown(e)}
> >
{location.pathname === '/trashed' ? trashTopBar : detailTopBar} {location.pathname === '/trashed' ? trashTopBar : detailTopBar}
@@ -551,7 +443,8 @@ 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)

View File

@@ -7,7 +7,6 @@
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
box-shadow none box-shadow none
padding 20px 40px padding 20px 40px
overflow hidden
.lock-button .lock-button
padding-bottom 3px padding-bottom 3px
@@ -45,7 +44,7 @@
margin 0 30px margin 0 30px
.body-noteEditor .body-noteEditor
absolute top bottom left right absolute top bottom left right
body[data-theme="white"] body[data-theme="white"]
.root .root
box-shadow $note-detail-box-shadow box-shadow $note-detail-box-shadow
@@ -71,14 +70,3 @@ 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
body[data-theme="dracula"]
.root
border-left 1px solid $ui-dracula-borderColor
background-color $ui-dracula-noteDetail-backgroundColor

View File

@@ -13,7 +13,6 @@ $info-margin-under-border = 30px
display flex display flex
align-items center align-items center
padding 0 20px padding 0 20px
z-index 99
.info-left .info-left
padding 0 10px padding 0 10px
@@ -98,21 +97,4 @@ body[data-theme="solarized-dark"]
.info .info
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
body[data-theme="dracula"]
.info
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteDetail-backgroundColor
.info > div
> button
-webkit-user-drag none
user-select none
> img, span
-webkit-user-drag none
user-select none

View File

@@ -2,7 +2,6 @@ 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 styles from './TrashButton.styl' import styles from './TrashButton.styl'
import i18n from 'browser/lib/i18n'
const PermanentDeleteButton = ({ const PermanentDeleteButton = ({
onClick onClick
@@ -10,8 +9,8 @@ const PermanentDeleteButton = ({
<button styleName='control-trashButton--in-trash' <button styleName='control-trashButton--in-trash'
onClick={(e) => onClick(e)} onClick={(e) => onClick(e)}
> >
<img src='../resources/icon/icon-trash.svg' /> <img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>{i18n.__('Permanent Delete')}</span> <span styleName='tooltip'>Permanent Delete</span>
</button> </button>
) )

View File

@@ -1,22 +0,0 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './RestoreButton.styl'
import i18n from 'browser/lib/i18n'
const RestoreButton = ({
onClick
}) => (
<button styleName='control-restoreButton'
onClick={onClick}
>
<i className='fa fa-undo fa-fw' styleName='iconRestore' />
<span styleName='tooltip'>{i18n.__('Restore')}</span>
</button>
)
RestoreButton.propTypes = {
onClick: PropTypes.func.isRequired
}
export default CSSModules(RestoreButton, styles)

View File

@@ -1,22 +0,0 @@
.control-restoreButton
top 115px
topBarButtonRight()
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 50px
left 25px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
body[data-theme="dark"]
.control-restoreButton
topBarButtonDark()

View File

@@ -8,6 +8,7 @@ import StarButton from './StarButton'
import TagSelect from './TagSelect' import TagSelect from './TagSelect'
import FolderSelect from './FolderSelect' import FolderSelect from './FolderSelect'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir' import 'codemirror-mode-elixir'
@@ -16,25 +17,33 @@ import StatusBar from '../StatusBar'
import context from 'browser/lib/context' 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 AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import FullscreenButton from './FullscreenButton'
import TrashButton from './TrashButton' import TrashButton from './TrashButton'
import RestoreButton from './RestoreButton'
import PermanentDeleteButton from './PermanentDeleteButton' import PermanentDeleteButton from './PermanentDeleteButton'
import InfoButton from './InfoButton' import InfoButton from './InfoButton'
import InfoPanel from './InfoPanel' 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 { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' function pass (name) {
import markdownToc from 'browser/lib/markdown-toc-generator' switch (name) {
import queryString from 'query-string' case 'ejs':
import { replace } from 'connected-react-router' 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
const { dialog } = remote const { Menu, MenuItem, dialog } = remote
class SnippetNoteDetail extends React.Component { class SnippetNoteDetail extends React.Component {
constructor (props) { constructor (props) {
@@ -43,43 +52,22 @@ class SnippetNoteDetail extends React.Component {
this.state = { this.state = {
isMovingNote: false, isMovingNote: false,
snippetIndex: 0, snippetIndex: 0,
showArrows: false,
enableLeftArrow: false,
enableRightArrow: false,
note: Object.assign({ note: Object.assign({
description: '' description: ''
}, props.note, { }, props.note, {
snippets: props.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet)) snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet))
}) })
} }
this.scrollToNextTabThreshold = 0.7
this.generateToc = () => this.handleGenerateToc()
}
componentDidMount () {
const visibleTabs = this.visibleTabs
const allTabs = this.allTabs
if (visibleTabs.offsetWidth < allTabs.scrollWidth) {
this.setState({
showArrows: visibleTabs.offsetWidth < allTabs.scrollWidth,
enableRightArrow: allTabs.offsetLeft !== visibleTabs.offsetWidth - allTabs.scrollWidth,
enableLeftArrow: allTabs.offsetLeft !== 0
})
}
ee.on('code:generate-toc', this.generateToc)
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) { if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
if (this.saveQueue != null) this.saveNow() if (this.saveQueue != null) this.saveNow()
const nextNote = Object.assign({ const nextNote = Object.assign({
description: '' description: ''
}, nextProps.note, { }, nextProps.note, {
snippets: nextProps.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet)) snippets: nextProps.note.snippets.map((snippet) => Object.assign({}, snippet))
}) })
this.setState({ this.setState({
snippetIndex: 0, snippetIndex: 0,
note: nextNote note: nextNote
@@ -89,23 +77,12 @@ class SnippetNoteDetail extends React.Component {
this.refs['code-' + index].reload() this.refs['code-' + index].reload()
}) })
if (this.refs.tags) this.refs.tags.reset() if (this.refs.tags) this.refs.tags.reset()
this.setState(this.getArrowsState())
}) })
} }
} }
componentWillUnmount () { componentWillUnmount () {
if (this.saveQueue != null) this.saveNow() if (this.saveQueue != null) this.saveNow()
ee.off('code:generate-toc', this.generateToc)
}
handleGenerateToc () {
const { note, snippetIndex } = this.state
const currentMode = note.snippets[snippetIndex].mode
if (currentMode.includes('Markdown')) {
const currentEditor = this.refs[`code-${snippetIndex}`].refs.code.editor
markdownToc.generateInEditor(currentEditor)
}
} }
handleChange (e) { handleChange (e) {
@@ -114,7 +91,7 @@ class SnippetNoteDetail extends React.Component {
if (this.refs.tags) note.tags = this.refs.tags.value if (this.refs.tags) note.tags = this.refs.tags.value
note.description = this.refs.description.value note.description = this.refs.description.value
note.updatedAt = new Date() note.updatedAt = new Date()
note.title = findNoteTitle(note.description, false) note.title = findNoteTitle(note.description)
this.setState({ this.setState({
note note
@@ -166,12 +143,12 @@ class SnippetNoteDetail extends React.Component {
originNote: note, originNote: note,
note: newNote note: newNote
}) })
dispatch(replace({ hashHistory.replace({
pathname: location.pathname, pathname: location.pathname,
search: queryString.stringify({ query: {
key: newNote.key key: newNote.storage + '-' + newNote.key
}) }
})) })
this.setState({ this.setState({
isMovingNote: false isMovingNote: false
}) })
@@ -199,10 +176,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.config.ui const { confirmDeletion } = this.props
if (isTrashed) { if (isTrashed) {
if (confirmDeleteNote(confirmDeletion, true)) { if (confirmDeletion(true)) {
const {note, dispatch} = this.props const {note, dispatch} = this.props
dataApi dataApi
.deleteNote(note.storage, note.key) .deleteNote(note.storage, note.key)
@@ -214,12 +191,11 @@ class SnippetNoteDetail extends React.Component {
noteKey: data.noteKey noteKey: data.noteKey
}) })
} }
ee.once('list:next', dispatchHandler) ee.once('list:moved', dispatchHandler)
}) })
.then(() => ee.emit('list:next'))
} }
} else { } else {
if (confirmDeleteNote(confirmDeletion, false)) { if (confirmDeletion()) {
note.isTrashed = true note.isTrashed = true
this.setState({ this.setState({
@@ -250,51 +226,6 @@ class SnippetNoteDetail extends React.Component {
ee.emit('editor:fullscreen') ee.emit('editor:fullscreen')
} }
handleTabMoveLeftButtonClick (e) {
{
const left = this.visibleTabs.scrollLeft
const tabs = this.allTabs.querySelectorAll('div')
const lastVisibleTab = Array.from(tabs).find((tab) => {
return tab.offsetLeft + tab.offsetWidth >= left
})
if (lastVisibleTab) {
const visiblePart = lastVisibleTab.offsetWidth + lastVisibleTab.offsetLeft - left
const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
const scrollToTab = (isFullyVisible && lastVisibleTab.previousSibling)
? lastVisibleTab.previousSibling
: lastVisibleTab
// FIXME use `scrollIntoView()` instead of custom method after update to Electron2.0 (with Chrome 61 its possible animate the scroll)
this.moveToTab(scrollToTab)
// scrollToTab.scrollIntoView({behavior: 'smooth', inline: 'start', block: 'start'})
}
}
}
handleTabMoveRightButtonClick (e) {
const left = this.visibleTabs.scrollLeft
const width = this.visibleTabs.offsetWidth
const tabs = this.allTabs.querySelectorAll('div')
const lastVisibleTab = Array.from(tabs).find((tab) => {
return tab.offsetLeft + tab.offsetWidth >= width + left
})
if (lastVisibleTab) {
const visiblePart = width + left - lastVisibleTab.offsetLeft
const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
const scrollToTab = (isFullyVisible && lastVisibleTab.nextSibling)
? lastVisibleTab.nextSibling
: lastVisibleTab
// FIXME use `scrollIntoView()` instead of custom method after update to Electron2.0 (with Chrome 61 its possible animate the scroll)
this.moveToTab(scrollToTab)
// scrollToTab.scrollIntoView({behavior: 'smooth', inline: 'end', block: 'end'})
}
}
handleTabPlusButtonClick (e) { handleTabPlusButtonClick (e) {
this.addSnippet() this.addSnippet()
} }
@@ -331,9 +262,9 @@ class SnippetNoteDetail extends React.Component {
if (this.state.note.snippets[index].content.trim().length > 0) { if (this.state.note.snippets[index].content.trim().length > 0) {
const dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), { const dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: i18n.__('Delete a snippet'), message: 'Delete a snippet',
detail: i18n.__('This work cannot be undone.'), detail: 'This work cannot be undone.',
buttons: [i18n.__('Confirm'), i18n.__('Cancel')] buttons: ['Confirm', 'Cancel']
}) })
if (dialogIndex === 0) { if (dialogIndex === 0) {
this.deleteSnippetByIndex(index) this.deleteSnippetByIndex(index)
@@ -354,19 +285,6 @@ class SnippetNoteDetail extends React.Component {
this.setState({ note, snippetIndex }, () => { this.setState({ note, snippetIndex }, () => {
this.save() this.save()
this.refs['code-' + this.state.snippetIndex].reload() this.refs['code-' + this.state.snippetIndex].reload()
if (this.visibleTabs.offsetWidth > this.allTabs.scrollWidth) {
this.moveTabBarBy(0)
} else {
const lastTab = this.allTabs.lastChild
if (lastTab.offsetLeft + lastTab.offsetWidth < this.visibleTabs.offsetWidth) {
const width = this.visibleTabs.offsetWidth
const newLeft = lastTab.offsetLeft + lastTab.offsetWidth - width
this.moveTabBarBy(newLeft > 0 ? -newLeft : 0)
} else {
this.setState(this.getArrowsState())
}
}
}) })
} }
@@ -381,11 +299,11 @@ class SnippetNoteDetail extends React.Component {
name: mode name: mode
}) })
} }
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})})) this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState(state => ({ this.setState({
note: state.note note: this.state.note
}), () => { }, () => {
this.save() this.save()
}) })
} }
@@ -394,11 +312,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(state => ({note: Object.assign(state.note, {snippets: snippets})})) this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState(state => ({ this.setState({
note: state.note note: this.state.note
}), () => { }, () => {
this.save() this.save()
}) })
@@ -412,12 +330,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
snippets[index].linesHighlighted = e.options.linesHighlighted this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState({
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})})) note: this.state.note
this.setState(state => ({ }, () => {
note: state.note
}), () => {
this.save() this.save()
}) })
} }
@@ -425,7 +341,6 @@ class SnippetNoteDetail extends React.Component {
handleKeyDown (e) { handleKeyDown (e) {
switch (e.keyCode) { switch (e.keyCode) {
// tab key
case 9: case 9:
if (e.ctrlKey && !e.shiftKey) { if (e.ctrlKey && !e.shiftKey) {
e.preventDefault() e.preventDefault()
@@ -438,19 +353,6 @@ class SnippetNoteDetail extends React.Component {
this.focusEditor() this.focusEditor()
} }
break break
// I key
case 73:
{
const isSuper = global.process.platform === 'darwin'
? e.metaKey
: e.ctrlKey
if (isSuper) {
e.preventDefault()
this.handleInfoButtonClick(e)
}
}
break
// L key
case 76: case 76:
{ {
const isSuper = global.process.platform === 'darwin' const isSuper = global.process.platform === 'darwin'
@@ -462,13 +364,12 @@ class SnippetNoteDetail extends React.Component {
} }
} }
break break
// T key
case 84: case 84:
{ {
const isSuper = global.process.platform === 'darwin' const isSuper = global.process.platform === 'darwin'
? e.metaKey ? e.metaKey
: e.ctrlKey : e.ctrlKey
if (isSuper && !e.shiftKey && !e.altKey) { if (isSuper) {
e.preventDefault() e.preventDefault()
this.addSnippet() this.addSnippet()
} }
@@ -478,14 +379,14 @@ class SnippetNoteDetail extends React.Component {
} }
handleModeButtonClick (e, index) { handleModeButtonClick (e, index) {
const templetes = [] const menu = new Menu()
CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => { CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => {
templetes.push({ menu.append(new MenuItem({
label: mode.name, label: mode.name,
click: (e) => this.handleModeOptionClick(index, mode.name)(e) click: (e) => this.handleModeOptionClick(index, mode.name)(e)
}) }))
}) })
context.popup(templetes) menu.popup(remote.getCurrentWindow())
} }
handleIndentTypeButtonClick (e) { handleIndentTypeButtonClick (e) {
@@ -518,19 +419,6 @@ class SnippetNoteDetail extends React.Component {
]) ])
} }
handleWrapLineButtonClick (e) {
context.popup([
{
label: 'on',
click: (e) => this.handleWrapLineItemClick(e, true)
},
{
label: 'off',
click: (e) => this.handleWrapLineItemClick(e, false)
}
])
}
handleIndentSizeItemClick (e, indentSize) { handleIndentSizeItemClick (e, indentSize) {
const { config, dispatch } = this.props const { config, dispatch } = this.props
const editor = Object.assign({}, config.editor, { const editor = Object.assign({}, config.editor, {
@@ -563,116 +451,46 @@ class SnippetNoteDetail extends React.Component {
}) })
} }
handleWrapLineItemClick (e, lineWrapping) {
const { config, dispatch } = this.props
const editor = Object.assign({}, config.editor, {
lineWrapping
})
ConfigManager.set({
editor
})
dispatch({
type: 'SET_CONFIG',
config: {
editor
}
})
}
focus () { focus () {
this.refs.description.focus() this.refs.description.focus()
} }
moveToTab (tab) {
const easeOutCubic = t => (--t) * t * t + 1
const startScrollPosition = this.visibleTabs.scrollLeft
const animationTiming = 300
const scrollMoreCoeff = 1.4 // introduce coefficient, because we want to scroll a bit further to see next tab
let scrollBy = (tab.offsetLeft - startScrollPosition)
if (tab.offsetLeft > startScrollPosition) {
// if tab is on the right side and we want to show the whole tab in visible area,
// we need to include width of the tab and visible area in the formula
// ___________________________________________
// |____|_______|________|________|_show_this_|
// ↑_____________________↑
// visible area
scrollBy += (tab.offsetWidth - this.visibleTabs.offsetWidth)
}
let startTime = null
const scrollAnimation = time => {
startTime = startTime || time
const elapsed = (time - startTime) / animationTiming
this.visibleTabs.scrollLeft = startScrollPosition + easeOutCubic(elapsed) * scrollBy * scrollMoreCoeff
if (elapsed < 1) {
window.requestAnimationFrame(scrollAnimation)
} else {
this.setState(this.getArrowsState())
}
}
window.requestAnimationFrame(scrollAnimation)
}
getArrowsState () {
const allTabs = this.allTabs
const visibleTabs = this.visibleTabs
const showArrows = visibleTabs.offsetWidth < allTabs.scrollWidth
const enableRightArrow = visibleTabs.scrollLeft !== allTabs.scrollWidth - visibleTabs.offsetWidth
const enableLeftArrow = visibleTabs.scrollLeft !== 0
return {showArrows, enableRightArrow, enableLeftArrow}
}
addSnippet () { addSnippet () {
const { config: { editor: { snippetDefaultLanguage } } } = this.props
const { note } = this.state const { note } = this.state
const defaultLanguage = snippetDefaultLanguage === 'Auto Detect' ? null : snippetDefaultLanguage
note.snippets = note.snippets.concat([{ note.snippets = note.snippets.concat([{
name: '', name: '',
mode: defaultLanguage, mode: 'Plain Text',
content: '', content: ''
linesHighlighted: []
}]) }])
const snippetIndex = note.snippets.length - 1 const snippetIndex = note.snippets.length - 1
this.setState(Object.assign({ this.setState({
note, note,
snippetIndex snippetIndex
}, this.getArrowsState()), () => { }, () => {
if (this.state.showArrows) {
const tabs = this.allTabs.querySelectorAll('div')
if (tabs) {
this.moveToTab(tabs[snippetIndex])
}
}
this.refs['tab-' + snippetIndex].startRenaming() this.refs['tab-' + snippetIndex].startRenaming()
}) })
} }
jumpNextTab () { jumpNextTab () {
this.setState(state => ({ this.setState({
snippetIndex: (state.snippetIndex + 1) % state.note.snippets.length snippetIndex: (this.state.snippetIndex + 1) % this.state.note.snippets.length
}), () => { }, () => {
this.focusEditor() this.focusEditor()
}) })
} }
jumpPrevTab () { jumpPrevTab () {
this.setState(state => ({ this.setState({
snippetIndex: (state.snippetIndex - 1 + state.note.snippets.length) % state.note.snippets.length snippetIndex: (this.state.snippetIndex - 1 + this.state.note.snippets.length) % this.state.note.snippets.length
}), () => { }, () => {
this.focusEditor() this.focusEditor()
}) })
} }
focusEditor () { focusEditor () {
console.log('code-' + this.state.snippetIndex)
this.refs['code-' + this.state.snippetIndex].focus() this.refs['code-' + this.state.snippetIndex].focus()
} }
@@ -681,32 +499,22 @@ class SnippetNoteDetail extends React.Component {
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none' if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
} }
showWarning (e, msg) { showWarning () {
const warningMessage = (msg) => ({
'export-txt': 'Text export',
'export-md': 'Markdown export',
'export-html': 'HTML export',
'export-pdf': 'PDF export',
'print': 'Print'
})[msg]
dialog.showMessageBox(remote.getCurrentWindow(), { dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: i18n.__('Sorry!'), message: 'Sorry!',
detail: i18n.__(warningMessage(msg) + ' is available only in markdown notes.'), detail: 'md/text import is available only a markdown note.',
buttons: [i18n.__('OK')] buttons: ['OK']
}) })
} }
render () { render () {
const { data, dispatch, config, location } = this.props const { data, config, location } = this.props
const { note } = this.state const { note } = this.state
const storageKey = note.storage const storageKey = note.storage
const folderKey = note.folder const folderKey = note.folder
const autoDetect = config.editor.snippetDefaultLanguage === 'Auto Detect'
let editorFontSize = parseInt(config.editor.fontSize, 10) let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
let editorIndentSize = parseInt(config.editor.indentSize, 10) let editorIndentSize = parseInt(config.editor.indentSize, 10)
@@ -731,6 +539,10 @@ 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))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
return <div styleName='tabView' return <div styleName='tabView'
key={index} key={index}
style={{zIndex: isActive ? 5 : 4}} style={{zIndex: isActive ? 5 : 4}}
@@ -739,35 +551,24 @@ class SnippetNoteDetail extends React.Component {
? <MarkdownEditor styleName='tabView-content' ? <MarkdownEditor styleName='tabView-content'
value={snippet.content} value={snippet.content}
config={config} config={config}
linesHighlighted={snippet.linesHighlighted}
onChange={(e) => this.handleCodeChange(index)(e)} onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index} ref={'code-' + index}
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents} ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
storageKey={storageKey} storageKey={storageKey}
/> />
: <CodeEditor styleName='tabView-content' : <CodeEditor styleName='tabView-content'
mode={snippet.mode || (autoDetect ? null : config.editor.snippetDefaultLanguage)} mode={snippet.mode}
value={snippet.content} value={snippet.content}
linesHighlighted={snippet.linesHighlighted}
lineWrapping={config.editor.lineWrapping}
theme={config.editor.theme} theme={config.editor.theme}
fontFamily={config.editor.fontFamily} fontFamily={config.editor.fontFamily}
fontSize={editorFontSize} fontSize={editorFontSize}
indentType={config.editor.indentType} indentType={config.editor.indentType}
indentSize={editorIndentSize} indentSize={editorIndentSize}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
onChange={(e) => this.handleCodeChange(index)(e)} onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index} ref={'code-' + index}
enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey}
autoDetect={autoDetect}
/> />
} }
</div> </div>
@@ -786,7 +587,10 @@ class SnippetNoteDetail extends React.Component {
const trashTopBar = <div styleName='info'> const trashTopBar = <div styleName='info'>
<div styleName='info-left'> <div styleName='info-left'>
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} /> <i styleName='undo-button'
className='fa fa-undo fa-fw'
onClick={(e) => this.handleUndoButtonClick(e)}
/>
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} /> <PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -801,14 +605,13 @@ class SnippetNoteDetail extends React.Component {
exportAsMd={this.showWarning} exportAsMd={this.showWarning}
exportAsTxt={this.showWarning} exportAsTxt={this.showWarning}
exportAsHtml={this.showWarning} exportAsHtml={this.showWarning}
exportAsPdf={this.showWarning}
/> />
</div> </div>
</div> </div>
const detailTopBar = <div styleName='info'> const detailTopBar = <div styleName='info'>
<div styleName='info-left'> <div styleName='info-left'>
<div> <div styleName='info-left-top'>
<FolderSelect styleName='info-left-top-folderSelect' <FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder} value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder' ref='folder'
@@ -820,12 +623,7 @@ class SnippetNoteDetail extends React.Component {
<TagSelect <TagSelect
ref='tags' ref='tags'
value={this.state.note.tags} value={this.state.note.tags}
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
showTagsAlphabetically={config.ui.showTagsAlphabetically}
data={data}
dispatch={dispatch}
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
coloredTags={config.coloredTags}
/> />
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
@@ -834,7 +632,11 @@ class SnippetNoteDetail extends React.Component {
isActive={note.isStarred} isActive={note.isStarred}
/> />
<FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} /> <button styleName='control-fullScreenButton' title='Fullscreen'
onMouseDown={(e) => this.handleFullScreenButton(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>Fullscreen</span>
</button>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} /> <TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -845,15 +647,12 @@ class SnippetNoteDetail extends React.Component {
<InfoPanel <InfoPanel
storageName={currentOption.storage.name} storageName={currentOption.storage.name}
folderName={currentOption.folder.name} folderName={currentOption.folder.name}
noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`} noteLink={`[${note.title}](${location.query.key})`}
updatedAt={formatDate(note.updatedAt)} updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)} createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning} exportAsMd={this.showWarning}
exportAsTxt={this.showWarning} exportAsTxt={this.showWarning}
exportAsHtml={this.showWarning}
exportAsPdf={this.showWarning}
type={note.type} type={note.type}
print={this.showWarning}
/> />
</div> </div>
</div> </div>
@@ -874,32 +673,16 @@ class SnippetNoteDetail extends React.Component {
fontSize: parseInt(config.preview.fontSize, 10) fontSize: parseInt(config.preview.fontSize, 10)
}} }}
ref='description' ref='description'
placeholder={i18n.__('Description...')} placeholder='Description...'
value={this.state.note.description} value={this.state.note.description}
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
/> />
</div> </div>
<div styleName='tabList'> <div styleName='tabList'>
<button styleName='tabButton' <div styleName='list'>
hidden={!this.state.showArrows} {tabList}
disabled={!this.state.enableLeftArrow}
onClick={(e) => this.handleTabMoveLeftButtonClick(e)}
>
<i className='fa fa-chevron-left' />
</button>
<div styleName='list' onScroll={(e) => { this.setState(this.getArrowsState()) }} ref={(tabs) => { this.visibleTabs = tabs }}>
<div styleName='allTabs' ref={(tabs) => { this.allTabs = tabs }}>
{tabList}
</div>
</div> </div>
<button styleName='tabButton' <button styleName='plusButton'
hidden={!this.state.showArrows}
disabled={!this.state.enableRightArrow}
onClick={(e) => this.handleTabMoveRightButtonClick(e)}
>
<i className='fa fa-chevron-right' />
</button>
<button styleName='tabButton'
onClick={(e) => this.handleTabPlusButtonClick(e)} onClick={(e) => this.handleTabPlusButtonClick(e)}
> >
<i className='fa fa-plus' /> <i className='fa fa-plus' />
@@ -913,7 +696,7 @@ class SnippetNoteDetail extends React.Component {
onClick={(e) => this.handleModeButtonClick(e, this.state.snippetIndex)} onClick={(e) => this.handleModeButtonClick(e, this.state.snippetIndex)}
> >
{this.state.note.snippets[this.state.snippetIndex].mode == null {this.state.note.snippets[this.state.snippetIndex].mode == null
? i18n.__('Select Syntax...') ? 'Select Syntax...'
: this.state.note.snippets[this.state.snippetIndex].mode : this.state.note.snippets[this.state.snippetIndex].mode
}&nbsp; }&nbsp;
<i className='fa fa-caret-down' /> <i className='fa fa-caret-down' />
@@ -930,12 +713,6 @@ class SnippetNoteDetail extends React.Component {
size: {config.editor.indentSize}&nbsp; size: {config.editor.indentSize}&nbsp;
<i className='fa fa-caret-down' /> <i className='fa fa-caret-down' />
</button> </button>
<button
onClick={(e) => this.handleWrapLineButtonClick(e)}
>
Wrap Line: {config.editor.lineWrapping ? 'on' : 'off'}&nbsp;
<i className='fa fa-caret-down' />
</button>
</div> </div>
<StatusBar <StatusBar
@@ -956,7 +733,8 @@ 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)

View File

@@ -31,35 +31,19 @@
.tabList .tabList
absolute left right absolute left right
top 70px top 55px
height 30px height 30px
display flex display flex
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
overflow hidden
.tabList .list .tabList .list
flex 1 flex 1
display flex
overflow hidden overflow hidden
overflow-x scroll
position relative
&::-webkit-scrollbar { .tabList .plusButton
display: none;
}
.allTabs
display flex
position relative
overflow visible
left 0
transition left 0.1s
.tabList .tabButton
navWhiteButtonColor() navWhiteButtonColor()
width 30px width 30px
border-left 1px solid $ui-borderColor
border-top 1px solid $ui-borderColor
border-right 1px solid $ui-borderColor
.tabView .tabView
absolute left right bottom absolute left right bottom
@@ -101,34 +85,17 @@
opacity 0 opacity 0
transition 0.1s transition 0.1s
body[data-theme="white"], body[data-theme="default"] body[data-theme="white"]
.root .root
box-shadow $note-detail-box-shadow box-shadow $note-detail-box-shadow
border none border none
.tabButton
&:hover
background-color alpha($ui-button--active-backgroundColor, 20%)
color $ui-text-color
transition 0.15s
body[data-theme="dark"] body[data-theme="dark"]
.root .root
border-left 1px solid $ui-dark-borderColor border-left 1px solid $ui-dark-borderColor
background-color $ui-dark-noteDetail-backgroundColor background-color $ui-dark-noteDetail-backgroundColor
box-shadow none box-shadow none
.tabList .tabButton
border-color $ui-dark-borderColor
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
.tabButton
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
color $ui-dark-text-color
transition 0.15s
.body .body
background-color $ui-dark-noteDetail-backgroundColor background-color $ui-dark-noteDetail-backgroundColor
@@ -138,6 +105,7 @@ body[data-theme="dark"]
border 1px solid $ui-dark-borderColor border 1px solid $ui-dark-borderColor
.tabList .tabList
background-color $ui-button--active-backgroundColor
background-color $ui-dark-noteDetail-backgroundColor background-color $ui-dark-noteDetail-backgroundColor
.tabList .list .tabList .list
@@ -169,65 +137,6 @@ body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
border 1px solid $ui-solarized-dark-borderColor border 1px solid $ui-solarized-dark-borderColor
.tabList .tabButton
border-color $ui-solarized-dark-borderColor
.tabButton
&:hover
color $ui-solarized-dark-button--active-color
background-color $ui-solarized-dark-noteDetail-backgroundColor
transition 0.15s
.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 .tabButton
border-color $ui-monokai-borderColor
.tabButton
&:hover
color $ui-monokai-text-color
background-color $ui-monokai-noteDetail-backgroundColor
.tabList
background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color
body[data-theme="dracula"]
.root
border-left 1px solid $ui-dracula-borderColor
background-color $ui-dracula-noteDetail-backgroundColor
.body
background-color $ui-dracula-noteDetail-backgroundColor
.body .description textarea
background-color $ui-dracula-noteDetail-backgroundColor
color $ui-dracula-text-color
border 1px solid $ui-dracula-borderColor
.tabList .tabButton
border-color $ui-dracula-borderColor
.tabButton
&:hover
color $ui-dracula-text-color
background-color $ui-dracula-noteDetail-backgroundColor
.tabList
background-color $ui-dracula-noteDetail-backgroundColor
color $ui-dracula-text-color

View File

@@ -3,7 +3,6 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './StarButton.styl' import styles from './StarButton.styl'
import _ from 'lodash' import _ from 'lodash'
import i18n from 'browser/lib/i18n'
class StarButton extends React.Component { class StarButton extends React.Component {
constructor (props) { constructor (props) {
@@ -54,7 +53,7 @@ class StarButton extends React.Component {
: '../resources/icon/icon-star.svg' : '../resources/icon/icon-star.svg'
} }
/> />
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Star')}</span> <span styleName='tooltip'>Star</span>
</button> </button>
) )
} }

View File

@@ -21,11 +21,6 @@
opacity 0 opacity 0
transition 0.1s transition 0.1s
.tooltip:lang(ja)
@extend .tooltip
right 103px
width 70px
.root--active .root--active
@extend .root @extend .root
transition 0.15s transition 0.15s
@@ -42,4 +37,4 @@ body[data-theme="dark"]
topBarButtonDark() topBarButtonDark()
&:hover &:hover
transition 0.2s transition 0.2s
color alpha($ui-favorite-star-button-color, 0.6) color alpha($ui-favorite-star-button-color, 0.6)

View File

@@ -1,40 +1,71 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import invertColor from 'invert-color'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './TagSelect.styl' import styles from './TagSelect.styl'
import _ from 'lodash' import _ from 'lodash'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n'
import ee from 'browser/main/lib/eventEmitter'
import Autosuggest from 'react-autosuggest'
import { push } from 'connected-react-router'
class TagSelect extends React.Component { class TagSelect extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
this.state = { this.state = {
newTag: '', newTag: ''
suggestions: []
} }
this.handleAddTag = this.handleAddTag.bind(this)
this.onInputBlur = this.onInputBlur.bind(this)
this.onInputChange = this.onInputChange.bind(this)
this.onInputKeyDown = this.onInputKeyDown.bind(this)
this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this)
this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this)
this.onSuggestionSelected = this.onSuggestionSelected.bind(this)
} }
addNewTag (newTag) { componentDidMount () {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG') this.value = this.props.value
}
newTag = newTag.trim().replace(/ +/g, '_') componentDidUpdate () {
if (newTag.charAt(0) === '#') { this.value = this.props.value
newTag.substring(1) }
handleNewTagInputKeyDown (e) {
switch (e.keyCode) {
case 9:
e.preventDefault()
this.submitTag()
break
case 13:
this.submitTag()
break
case 8:
if (this.refs.newTag.value.length === 0) {
this.removeLastTag()
}
} }
}
handleNewTagBlur (e) {
this.submitTag()
}
removeLastTag () {
let { value } = this.props
value = _.isArray(value)
? value.slice()
: []
value.pop()
value = _.uniq(value)
this.value = value
this.props.onChange()
}
reset () {
this.setState({
newTag: ''
})
}
submitTag () {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
let { value } = this.props
let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
newTag = newTag.charAt(0) === '#' ? newTag.substring(1) : newTag
if (newTag.length <= 0) { if (newTag.length <= 0) {
this.setState({ this.setState({
@@ -43,18 +74,11 @@ class TagSelect extends React.Component {
return return
} }
let { value } = this.props
value = _.isArray(value) value = _.isArray(value)
? value.slice() ? value.slice()
: [] : []
value.push(newTag)
if (!_.includes(value, newTag)) { value = _.uniq(value)
value.push(newTag)
}
if (this.props.saveTagsAlphabetically) {
value = _.sortBy(value)
}
this.setState({ this.setState({
newTag: '' newTag: ''
@@ -64,168 +88,44 @@ class TagSelect extends React.Component {
}) })
} }
buildSuggestions () { handleNewTagInputChange (e) {
this.suggestions = _.sortBy(this.props.data.tagNoteMap.map( this.setState({
(tag, name) => ({ newTag: this.refs.newTag.value
name, })
nameLC: name.toLowerCase(),
size: tag.size
})
).filter(
tag => tag.size > 0
), ['name'])
}
componentDidMount () {
this.value = this.props.value
this.buildSuggestions()
ee.on('editor:add-tag', this.handleAddTag)
}
componentDidUpdate () {
this.value = this.props.value
}
componentWillUnmount () {
ee.off('editor:add-tag', this.handleAddTag)
}
handleAddTag () {
this.refs.newTag.input.focus()
}
handleTagLabelClick (tag) {
const { dispatch } = this.props
// Note: `tag` requires encoding later.
// E.g. % in tag is a problem (see issue #3170) - encodeURIComponent(tag) is not working.
dispatch(push(`/tags/${tag}`))
} }
handleTagRemoveButtonClick (tag) { handleTagRemoveButtonClick (tag) {
this.removeTagByCallback((value, tag) => { return (e) => {
let { value } = this.props
value.splice(value.indexOf(tag), 1) value.splice(value.indexOf(tag), 1)
}, tag) value = _.uniq(value)
}
onInputBlur (e) { this.value = value
this.submitNewTag() this.props.onChange()
}
onInputChange (e, { newValue, method }) {
this.setState({
newTag: newValue
})
}
onInputKeyDown (e) {
switch (e.keyCode) {
case 9:
e.preventDefault()
this.submitNewTag()
break
case 13:
this.submitNewTag()
break
case 8:
if (this.state.newTag.length === 0) {
this.removeLastTag()
}
} }
} }
onSuggestionsClearRequested () {
this.setState({
suggestions: []
})
}
onSuggestionsFetchRequested ({ value }) {
const valueLC = value.toLowerCase()
const suggestions = _.filter(
this.suggestions,
tag => !_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1
)
this.setState({
suggestions
})
}
onSuggestionSelected (event, { suggestion, suggestionValue }) {
this.addNewTag(suggestionValue)
}
removeLastTag () {
this.removeTagByCallback((value) => {
value.pop()
})
}
removeTagByCallback (callback, tag = null) {
let { value } = this.props
value = _.isArray(value)
? value.slice()
: []
callback(value, tag)
value = _.uniq(value)
this.value = value
this.props.onChange()
}
reset () {
this.buildSuggestions()
this.setState({
newTag: ''
})
}
submitNewTag () {
this.addNewTag(this.refs.newTag.input.value)
}
render () { render () {
const { value, className, showTagsAlphabetically, coloredTags } = this.props const { value, className } = this.props
const tagList = _.isArray(value) const tagList = _.isArray(value)
? (showTagsAlphabetically ? _.sortBy(value) : value).map((tag) => { ? value.map((tag) => {
const wrapperStyle = {}
const textStyle = {}
const BLACK = '#333333'
const WHITE = '#f1f1f1'
const color = coloredTags[tag]
const invertedColor = color && invertColor(color, { black: BLACK, white: WHITE })
let iconRemove = '../resources/icon/icon-x.svg'
if (color) {
wrapperStyle.backgroundColor = color
textStyle.color = invertedColor
}
if (invertedColor === WHITE) {
iconRemove = '../resources/icon/icon-x-light.svg'
}
return ( return (
<span styleName='tag' <span styleName='tag'
key={tag} key={tag}
style={wrapperStyle}
> >
<span styleName='tag-label' style={textStyle} onClick={(e) => this.handleTagLabelClick(tag)}>#{tag}</span> <span styleName='tag-label'>#{tag}</span>
<button styleName='tag-removeButton' <button styleName='tag-removeButton'
onClick={(e) => this.handleTagRemoveButtonClick(tag)} onClick={(e) => this.handleTagRemoveButtonClick(tag)(e)}
> >
<img className='tag-removeButton-icon' src={iconRemove} width='8px' /> <img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
</button> </button>
</span> </span>
) )
}) })
: [] : []
const { newTag, suggestions } = this.state
return ( return (
<div className={_.isString(className) <div className={_.isString(className)
? 'TagSelect ' + className ? 'TagSelect ' + className
@@ -234,25 +134,13 @@ class TagSelect extends React.Component {
styleName='root' styleName='root'
> >
{tagList} {tagList}
<Autosuggest <input styleName='newTag'
ref='newTag' ref='newTag'
suggestions={suggestions} value={this.state.newTag}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} placeholder='Add tag...'
onSuggestionsClearRequested={this.onSuggestionsClearRequested} onChange={(e) => this.handleNewTagInputChange(e)}
onSuggestionSelected={this.onSuggestionSelected} onKeyDown={(e) => this.handleNewTagInputKeyDown(e)}
getSuggestionValue={suggestion => suggestion.name} onBlur={(e) => this.handleNewTagBlur(e)}
renderSuggestion={suggestion => (
<div>
{suggestion.name}
</div>
)}
inputProps={{
placeholder: i18n.__('Add tag...'),
value: newTag,
onChange: this.onInputChange,
onKeyDown: this.onInputKeyDown,
onBlur: this.onInputBlur
}}
/> />
</div> </div>
) )
@@ -260,11 +148,10 @@ class TagSelect extends React.Component {
} }
TagSelect.propTypes = { TagSelect.propTypes = {
dispatch: PropTypes.func,
className: PropTypes.string, className: PropTypes.string,
value: PropTypes.arrayOf(PropTypes.string), value: PropTypes.arrayOf(PropTypes.string),
onChange: PropTypes.func, onChange: PropTypes.func
coloredTags: PropTypes.object
} }
export default CSSModules(TagSelect, styles) export default CSSModules(TagSelect, styles)

Some files were not shown because too many files have changed in this diff Show More