mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-15 02:36:36 +00:00
Merge branch 'master' into fix-scroll
This commit is contained in:
41
.vscode/launch.json
vendored
Normal file
41
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
// 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_modeules/.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
Normal file
27
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,15 +1,25 @@
|
|||||||
# Current behavior
|
# Current behavior
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Please paste some **screenshots** with the **developer tool** open (console tab) when you report a bug.
|
Let us know what is currently happening.
|
||||||
|
|
||||||
If your issue is regarding boostnote mobile, move to https://github.com/BoostIO/boostnote-mobile.
|
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
|
# Expected behavior
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Let us know what you think should happen!
|
||||||
|
-->
|
||||||
|
|
||||||
# Steps to reproduce
|
# Steps to reproduce
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please be thorough, issues we can reproduce are easier to fix!
|
||||||
|
-->
|
||||||
|
|
||||||
1.
|
1.
|
||||||
2.
|
2.
|
||||||
3.
|
3.
|
||||||
|
|||||||
36
PULL_REQUEST_TEMPLATE.md
Normal file
36
PULL_REQUEST_TEMPLATE.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!--
|
||||||
|
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
|
||||||
@@ -11,9 +11,14 @@ import eventEmitter from 'browser/main/lib/eventEmitter'
|
|||||||
import iconv from 'iconv-lite'
|
import iconv from 'iconv-lite'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import consts from 'browser/lib/consts'
|
import consts from 'browser/lib/consts'
|
||||||
|
import styles from '../components/CodeEditor.styl'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
const { ipcRenderer } = require('electron')
|
const { ipcRenderer, remote } = require('electron')
|
||||||
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
|
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
|
||||||
|
const spellcheck = require('browser/lib/spellcheck')
|
||||||
|
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
|
||||||
|
import TurndownService from 'turndown'
|
||||||
|
import { gfm } from 'turndown-plugin-gfm'
|
||||||
|
|
||||||
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||||
|
|
||||||
@@ -28,7 +33,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
leading: false,
|
leading: false,
|
||||||
trailing: true
|
trailing: true
|
||||||
})
|
})
|
||||||
this.changeHandler = e => this.handleChange(e)
|
this.changeHandler = (editor, changeObject) => this.handleChange(editor, changeObject)
|
||||||
this.focusHandler = () => {
|
this.focusHandler = () => {
|
||||||
ipcRenderer.send('editor:focused', true)
|
ipcRenderer.send('editor:focused', true)
|
||||||
}
|
}
|
||||||
@@ -57,9 +62,18 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
this.searchHandler = (e, msg) => this.handleSearch(msg)
|
this.searchHandler = (e, msg) => this.handleSearch(msg)
|
||||||
this.searchState = null
|
this.searchState = null
|
||||||
|
this.scrollToLineHandeler = this.scrollToLine.bind(this)
|
||||||
|
|
||||||
this.formatTable = () => this.handleFormatTable()
|
this.formatTable = () => this.handleFormatTable()
|
||||||
|
this.contextMenuHandler = function (editor, event) {
|
||||||
|
const menu = buildEditorContextMenu(editor, event)
|
||||||
|
if (menu != null) {
|
||||||
|
setTimeout(() => menu.popup(remote.getCurrentWindow()), 30)
|
||||||
|
}
|
||||||
|
}
|
||||||
this.editorActivityHandler = () => this.handleEditorActivity()
|
this.editorActivityHandler = () => this.handleEditorActivity()
|
||||||
|
|
||||||
|
this.turndownService = new TurndownService()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearch (msg) {
|
handleSearch (msg) {
|
||||||
@@ -129,6 +143,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { rulers, enableRulers } = this.props
|
const { rulers, enableRulers } = this.props
|
||||||
const expandSnippet = this.expandSnippet.bind(this)
|
const expandSnippet = this.expandSnippet.bind(this)
|
||||||
|
eventEmitter.on('line:jump', this.scrollToLineHandeler)
|
||||||
|
|
||||||
const defaultSnippet = [
|
const defaultSnippet = [
|
||||||
{
|
{
|
||||||
@@ -230,6 +245,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.on('blur', this.blurHandler)
|
this.editor.on('blur', this.blurHandler)
|
||||||
this.editor.on('change', this.changeHandler)
|
this.editor.on('change', this.changeHandler)
|
||||||
this.editor.on('paste', this.pasteHandler)
|
this.editor.on('paste', this.pasteHandler)
|
||||||
|
this.editor.on('contextmenu', this.contextMenuHandler)
|
||||||
eventEmitter.on('top:search', this.searchHandler)
|
eventEmitter.on('top:search', this.searchHandler)
|
||||||
|
|
||||||
eventEmitter.emit('code:init')
|
eventEmitter.emit('code:init')
|
||||||
@@ -247,6 +263,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
this.textEditorInterface = new TextEditorInterface(this.editor)
|
this.textEditorInterface = new TextEditorInterface(this.editor)
|
||||||
this.tableEditor = new TableEditor(this.textEditorInterface)
|
this.tableEditor = new TableEditor(this.textEditorInterface)
|
||||||
|
if (this.props.spellCheck) {
|
||||||
|
this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'})
|
||||||
|
}
|
||||||
|
|
||||||
eventEmitter.on('code:format-table', this.formatTable)
|
eventEmitter.on('code:format-table', this.formatTable)
|
||||||
|
|
||||||
this.tableEditorOptions = options({
|
this.tableEditorOptions = options({
|
||||||
@@ -296,6 +316,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
if (this.props.enableTableEditor) {
|
if (this.props.enableTableEditor) {
|
||||||
this.editor.on('changes', this.editorActivityHandler)
|
this.editor.on('changes', this.editorActivityHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
clientWidth: this.refs.root.clientWidth
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
expandSnippet (line, cursor, cm, snippets) {
|
expandSnippet (line, cursor, cm, snippets) {
|
||||||
@@ -311,22 +335,28 @@ export default class CodeEditor extends React.Component {
|
|||||||
const snippetLines = snippets[i].content.split('\n')
|
const snippetLines = snippets[i].content.split('\n')
|
||||||
let cursorLineNumber = 0
|
let cursorLineNumber = 0
|
||||||
let cursorLinePosition = 0
|
let cursorLinePosition = 0
|
||||||
|
|
||||||
|
let cursorIndex
|
||||||
for (let j = 0; j < snippetLines.length; j++) {
|
for (let j = 0; j < snippetLines.length; j++) {
|
||||||
const cursorIndex = snippetLines[j].indexOf(templateCursorString)
|
cursorIndex = snippetLines[j].indexOf(templateCursorString)
|
||||||
|
|
||||||
if (cursorIndex !== -1) {
|
if (cursorIndex !== -1) {
|
||||||
cursorLineNumber = j
|
cursorLineNumber = j
|
||||||
cursorLinePosition = cursorIndex
|
cursorLinePosition = cursorIndex
|
||||||
cm.replaceRange(
|
|
||||||
snippets[i].content.replace(templateCursorString, ''),
|
break
|
||||||
wordBeforeCursor.range.from,
|
|
||||||
wordBeforeCursor.range.to
|
|
||||||
)
|
|
||||||
cm.setCursor({
|
|
||||||
line: cursor.line + cursorLineNumber,
|
|
||||||
ch: cursorLinePosition
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cm.replaceRange(
|
||||||
|
snippets[i].content.replace(templateCursorString, ''),
|
||||||
|
wordBeforeCursor.range.from,
|
||||||
|
wordBeforeCursor.range.to
|
||||||
|
)
|
||||||
|
cm.setCursor({
|
||||||
|
line: cursor.line + cursorLineNumber,
|
||||||
|
ch: cursorLinePosition + cursor.ch - wordBeforeCursor.text.length
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
cm.replaceRange(
|
cm.replaceRange(
|
||||||
snippets[i].content,
|
snippets[i].content,
|
||||||
@@ -384,10 +414,12 @@ export default class CodeEditor extends React.Component {
|
|||||||
eventEmitter.off('top:search', this.searchHandler)
|
eventEmitter.off('top:search', this.searchHandler)
|
||||||
this.editor.off('scroll', this.scrollHandler)
|
this.editor.off('scroll', this.scrollHandler)
|
||||||
this.editor.off('cursorActivity', this.editorActivityHandler)
|
this.editor.off('cursorActivity', this.editorActivityHandler)
|
||||||
|
this.editor.off('contextmenu', this.contextMenuHandler)
|
||||||
|
|
||||||
const editorTheme = document.getElementById('editorTheme')
|
const editorTheme = document.getElementById('editorTheme')
|
||||||
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
||||||
|
|
||||||
|
spellcheck.setLanguage(null, spellcheck.SPELLCHECK_DISABLED)
|
||||||
eventEmitter.off('code:format-table', this.formatTable)
|
eventEmitter.off('code:format-table', this.formatTable)
|
||||||
|
|
||||||
if (this.props.enableTableEditor) {
|
if (this.props.enableTableEditor) {
|
||||||
@@ -451,6 +483,24 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.setOption('extraKeys', this.defaultKeyMap)
|
this.editor.setOption('extraKeys', this.defaultKeyMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.clientWidth !== this.refs.root.clientWidth) {
|
||||||
|
this.setState({
|
||||||
|
clientWidth: this.refs.root.clientWidth
|
||||||
|
})
|
||||||
|
|
||||||
|
needRefresh = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevProps.spellCheck !== this.props.spellCheck) {
|
||||||
|
if (this.props.spellCheck === false) {
|
||||||
|
spellcheck.setLanguage(this.editor, spellcheck.SPELLCHECK_DISABLED)
|
||||||
|
let elem = document.getElementById('editor-bottom-panel')
|
||||||
|
elem.parentNode.removeChild(elem)
|
||||||
|
} else {
|
||||||
|
this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (needRefresh) {
|
if (needRefresh) {
|
||||||
this.editor.refresh()
|
this.editor.refresh()
|
||||||
}
|
}
|
||||||
@@ -464,16 +514,23 @@ export default class CodeEditor extends React.Component {
|
|||||||
CodeMirror.autoLoadMode(this.editor, syntax.mode)
|
CodeMirror.autoLoadMode(this.editor, syntax.mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange (e) {
|
handleChange (editor, changeObject) {
|
||||||
this.value = this.editor.getValue()
|
spellcheck.handleChange(editor, changeObject)
|
||||||
|
this.value = editor.getValue()
|
||||||
if (this.props.onChange) {
|
if (this.props.onChange) {
|
||||||
this.props.onChange(e)
|
this.props.onChange(editor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveCursorTo (row, col) {}
|
moveCursorTo (row, col) {}
|
||||||
|
|
||||||
scrollToLine (num) {}
|
scrollToLine (event, num) {
|
||||||
|
const cursor = {
|
||||||
|
line: num,
|
||||||
|
ch: 1
|
||||||
|
}
|
||||||
|
this.editor.setCursor(cursor)
|
||||||
|
}
|
||||||
|
|
||||||
focus () {
|
focus () {
|
||||||
this.editor.focus()
|
this.editor.focus()
|
||||||
@@ -536,7 +593,11 @@ export default class CodeEditor extends React.Component {
|
|||||||
)
|
)
|
||||||
return prevChar === '](' && nextChar === ')'
|
return prevChar === '](' && nextChar === ')'
|
||||||
}
|
}
|
||||||
if (dataTransferItem.type.match('image')) {
|
|
||||||
|
const pastedHtml = clipboardData.getData('text/html')
|
||||||
|
if (pastedHtml !== '') {
|
||||||
|
this.handlePasteHtml(e, editor, pastedHtml)
|
||||||
|
} else if (dataTransferItem.type.match('image')) {
|
||||||
attachmentManagement.handlePastImageEvent(
|
attachmentManagement.handlePastImageEvent(
|
||||||
this,
|
this,
|
||||||
storageKey,
|
storageKey,
|
||||||
@@ -606,6 +667,12 @@ export default class CodeEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePasteHtml (e, editor, pastedHtml) {
|
||||||
|
e.preventDefault()
|
||||||
|
const markdown = this.turndownService.turndown(pastedHtml)
|
||||||
|
editor.replaceSelection(markdown)
|
||||||
|
}
|
||||||
|
|
||||||
mapNormalResponse (response, pastedTxt) {
|
mapNormalResponse (response, pastedTxt) {
|
||||||
return this.decodeResponse(response).then(body => {
|
return this.decodeResponse(response).then(body => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -688,6 +755,25 @@ export default class CodeEditor extends React.Component {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createSpellCheckPanel () {
|
||||||
|
const panel = document.createElement('div')
|
||||||
|
panel.className = 'panel bottom'
|
||||||
|
panel.id = 'editor-bottom-panel'
|
||||||
|
const dropdown = document.createElement('select')
|
||||||
|
dropdown.title = 'Spellcheck'
|
||||||
|
dropdown.className = styles['spellcheck-select']
|
||||||
|
dropdown.addEventListener('change', (e) => spellcheck.setLanguage(this.editor, dropdown.value))
|
||||||
|
const options = spellcheck.getAvailableDictionaries()
|
||||||
|
for (const op of options) {
|
||||||
|
const option = document.createElement('option')
|
||||||
|
option.value = op.value
|
||||||
|
option.innerHTML = op.label
|
||||||
|
dropdown.appendChild(option)
|
||||||
|
}
|
||||||
|
panel.appendChild(dropdown)
|
||||||
|
return panel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeEditor.propTypes = {
|
CodeEditor.propTypes = {
|
||||||
@@ -698,7 +784,8 @@ CodeEditor.propTypes = {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
onBlur: PropTypes.func,
|
onBlur: PropTypes.func,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
readOnly: PropTypes.bool
|
readOnly: PropTypes.bool,
|
||||||
|
spellCheck: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeEditor.defaultProps = {
|
CodeEditor.defaultProps = {
|
||||||
@@ -708,5 +795,6 @@ CodeEditor.defaultProps = {
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontFamily: 'Monaco, Consolas',
|
fontFamily: 'Monaco, Consolas',
|
||||||
indentSize: 4,
|
indentSize: 4,
|
||||||
indentType: 'space'
|
indentType: 'space',
|
||||||
|
spellCheck: false
|
||||||
}
|
}
|
||||||
|
|||||||
6
browser/components/CodeEditor.styl
Normal file
6
browser/components/CodeEditor.styl
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.codeEditor-typo
|
||||||
|
text-decoration underline wavy red
|
||||||
|
|
||||||
|
.spellcheck-select
|
||||||
|
border: none
|
||||||
|
text-decoration underline wavy red
|
||||||
@@ -6,6 +6,7 @@ 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'
|
||||||
|
|
||||||
class MarkdownEditor extends React.Component {
|
class MarkdownEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -18,7 +19,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
this.supportMdSelectionBold = [16, 17, 186]
|
this.supportMdSelectionBold = [16, 17, 186]
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
status: 'PREVIEW',
|
status: props.config.editor.switchPreview === 'RIGHTCLICK' ? props.config.editor.delfaultStatus : 'PREVIEW',
|
||||||
renderValue: props.value,
|
renderValue: props.value,
|
||||||
keyPressed: new Set(),
|
keyPressed: new Set(),
|
||||||
isLocked: false
|
isLocked: false
|
||||||
@@ -64,6 +65,10 @@ 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)
|
||||||
@@ -72,9 +77,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
handleContextMenu (e) {
|
handleContextMenu (e) {
|
||||||
const { config } = this.props
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'RIGHTCLICK') {
|
if (config.editor.switchPreview === 'RIGHTCLICK') {
|
||||||
const newStatus = this.state.status === 'PREVIEW'
|
const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW'
|
||||||
? 'CODE'
|
|
||||||
: 'PREVIEW'
|
|
||||||
this.setState({
|
this.setState({
|
||||||
status: newStatus
|
status: newStatus
|
||||||
}, () => {
|
}, () => {
|
||||||
@@ -84,6 +87,10 @@ 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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,8 +147,10 @@ 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 = /\[x\]/i
|
const checkedMatch = /^\s*[\+\-\*] \[x\]/i
|
||||||
const uncheckedMatch = /\[ \]/
|
const uncheckedMatch = /^\s*[\+\-\*] \[ \]/
|
||||||
|
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
|
||||||
@@ -150,10 +159,10 @@ class MarkdownEditor extends React.Component {
|
|||||||
const targetLine = lines[lineIndex]
|
const targetLine = lines[lineIndex]
|
||||||
|
|
||||||
if (targetLine.match(checkedMatch)) {
|
if (targetLine.match(checkedMatch)) {
|
||||||
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
|
lines[lineIndex] = targetLine.replace(checkReplace, '[ ]')
|
||||||
}
|
}
|
||||||
if (targetLine.match(uncheckedMatch)) {
|
if (targetLine.match(uncheckedMatch)) {
|
||||||
lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]')
|
lines[lineIndex] = targetLine.replace(uncheckReplace, '[x]')
|
||||||
}
|
}
|
||||||
this.refs.code.setValue(lines.join('\n'))
|
this.refs.code.setValue(lines.join('\n'))
|
||||||
}
|
}
|
||||||
@@ -250,7 +259,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
: 'codeEditor--hide'
|
: 'codeEditor--hide'
|
||||||
}
|
}
|
||||||
ref='code'
|
ref='code'
|
||||||
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}
|
||||||
@@ -268,6 +277,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
enableTableEditor={config.editor.enableTableEditor}
|
enableTableEditor={config.editor.enableTableEditor}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={(e) => this.handleChange(e)}
|
||||||
onBlur={(e) => this.handleBlur(e)}
|
onBlur={(e) => this.handleBlur(e)}
|
||||||
|
spellCheck={config.editor.spellcheck}
|
||||||
/>
|
/>
|
||||||
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
|
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
|
||||||
? 'preview'
|
? 'preview'
|
||||||
@@ -300,6 +310,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
customCSS={config.preview.customCSS}
|
customCSS={config.preview.customCSS}
|
||||||
allowCustomCSS={config.preview.allowCustomCSS}
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
|
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
.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%
|
||||||
|
|||||||
@@ -17,8 +17,13 @@ import copy from 'copy-to-clipboard'
|
|||||||
import mdurl from 'mdurl'
|
import mdurl from 'mdurl'
|
||||||
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
||||||
import { escapeHtmlCharacters } from 'browser/lib/utils'
|
import { escapeHtmlCharacters } from 'browser/lib/utils'
|
||||||
|
import yaml from 'js-yaml'
|
||||||
|
import context from 'browser/lib/context'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import fs from 'fs'
|
||||||
|
import ConfigManager from '../main/lib/ConfigManager'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote, shell } = require('electron')
|
||||||
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
|
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
|
||||||
|
|
||||||
const { app } = remote
|
const { app } = remote
|
||||||
@@ -27,6 +32,8 @@ const fileUrl = require('file-url')
|
|||||||
|
|
||||||
const dialog = remote.dialog
|
const dialog = remote.dialog
|
||||||
|
|
||||||
|
const uri2path = require('file-uri-to-path')
|
||||||
|
|
||||||
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
||||||
const appPath = fileUrl(
|
const appPath = fileUrl(
|
||||||
process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve()
|
process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve()
|
||||||
@@ -75,7 +82,6 @@ function buildStyle (
|
|||||||
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
|
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
|
||||||
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
|
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
|
||||||
}
|
}
|
||||||
${allowCustomCSS ? customCSS : ''}
|
|
||||||
${markdownStyle}
|
${markdownStyle}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -83,6 +89,11 @@ body {
|
|||||||
font-size: ${fontSize}px;
|
font-size: ${fontSize}px;
|
||||||
${scrollPastEnd && 'padding-bottom: 90vh;'}
|
${scrollPastEnd && 'padding-bottom: 90vh;'}
|
||||||
}
|
}
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
padding-bottom: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
code {
|
code {
|
||||||
font-family: '${codeBlockFontFamily.join("','")}';
|
font-family: '${codeBlockFontFamily.join("','")}';
|
||||||
background-color: rgba(0,0,0,0.04);
|
background-color: rgba(0,0,0,0.04);
|
||||||
@@ -139,6 +150,8 @@ body p {
|
|||||||
display: none
|
display: none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${allowCustomCSS ? customCSS : ''}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +174,6 @@ const scrollBarDarkStyle = `
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const { shell } = require('electron')
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
|
||||||
const defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
|
const defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
|
||||||
@@ -219,8 +231,32 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContextMenu (e) {
|
handleContextMenu (event) {
|
||||||
this.props.onContextMenu(e)
|
// If a contextMenu handler was passed to us, use it instead of the self-defined one -> return
|
||||||
|
if (_.isFunction(this.props.onContextMenu)) {
|
||||||
|
this.props.onContextMenu(event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// No contextMenu was passed to us -> execute our own link-opener
|
||||||
|
if (event.target.tagName.toLowerCase() === 'a') {
|
||||||
|
const href = event.target.href
|
||||||
|
const isLocalFile = href.startsWith('file:')
|
||||||
|
if (isLocalFile) {
|
||||||
|
const absPath = uri2path(href)
|
||||||
|
try {
|
||||||
|
if (fs.lstatSync(absPath).isFile()) {
|
||||||
|
context.popup([
|
||||||
|
{
|
||||||
|
label: i18n.__('Show in explorer'),
|
||||||
|
click: (e) => shell.showItemInFolder(absPath)
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Error while evaluating if the file is locally available', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDoubleClick (e) {
|
handleDoubleClick (e) {
|
||||||
@@ -228,6 +264,10 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleMouseDown (e) {
|
handleMouseDown (e) {
|
||||||
|
const config = ConfigManager.get()
|
||||||
|
if (config.editor.switchPreview === 'RIGHTCLICK' && e.buttons === 2 && config.editor.type === 'SPLIT') {
|
||||||
|
eventEmitter.emit('topbar:togglemodebutton', 'CODE')
|
||||||
|
}
|
||||||
if (e.target != null) {
|
if (e.target != null) {
|
||||||
switch (e.target.tagName) {
|
switch (e.target.tagName) {
|
||||||
case 'A':
|
case 'A':
|
||||||
@@ -297,9 +337,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
allowCustomCSS,
|
allowCustomCSS,
|
||||||
customCSS
|
customCSS
|
||||||
)
|
)
|
||||||
let body = this.markdown.render(
|
let body = this.markdown.render(noteContent)
|
||||||
escapeHtmlCharacters(noteContent, { detectCodeBlock: true })
|
|
||||||
)
|
|
||||||
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||||
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
|
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
|
||||||
noteContent,
|
noteContent,
|
||||||
@@ -397,6 +435,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
case 'dark':
|
case 'dark':
|
||||||
case 'solarized-dark':
|
case 'solarized-dark':
|
||||||
case 'monokai':
|
case 'monokai':
|
||||||
|
case 'dracula':
|
||||||
return scrollBarDarkStyle
|
return scrollBarDarkStyle
|
||||||
default:
|
default:
|
||||||
return scrollBarStyle
|
return scrollBarStyle
|
||||||
@@ -498,7 +537,8 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
prevProps.smartQuotes !== this.props.smartQuotes ||
|
prevProps.smartQuotes !== this.props.smartQuotes ||
|
||||||
prevProps.sanitize !== this.props.sanitize ||
|
prevProps.sanitize !== this.props.sanitize ||
|
||||||
prevProps.smartArrows !== this.props.smartArrows ||
|
prevProps.smartArrows !== this.props.smartArrows ||
|
||||||
prevProps.breaks !== this.props.breaks
|
prevProps.breaks !== this.props.breaks ||
|
||||||
|
prevProps.lineThroughCheckbox !== this.props.lineThroughCheckbox
|
||||||
) {
|
) {
|
||||||
this.initMarkdown()
|
this.initMarkdown()
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
@@ -704,7 +744,6 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
el.addEventListener('click', this.linkClickHandler)
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
|
||||||
el.className = 'flowchart-error'
|
el.className = 'flowchart-error'
|
||||||
el.innerHTML = 'Flowchart parse error: ' + e.message
|
el.innerHTML = 'Flowchart parse error: ' + e.message
|
||||||
}
|
}
|
||||||
@@ -725,7 +764,6 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
el.addEventListener('click', this.linkClickHandler)
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
|
||||||
el.className = 'sequence-error'
|
el.className = 'sequence-error'
|
||||||
el.innerHTML = 'Sequence diagram parse error: ' + e.message
|
el.innerHTML = 'Sequence diagram parse error: ' + e.message
|
||||||
}
|
}
|
||||||
@@ -736,14 +774,21 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.refs.root.contentWindow.document.querySelectorAll('.chart'),
|
this.refs.root.contentWindow.document.querySelectorAll('.chart'),
|
||||||
el => {
|
el => {
|
||||||
try {
|
try {
|
||||||
const chartConfig = JSON.parse(el.innerHTML)
|
const format = el.attributes.getNamedItem('data-format').value
|
||||||
|
const chartConfig = format === 'yaml' ? yaml.load(el.innerHTML) : JSON.parse(el.innerHTML)
|
||||||
el.innerHTML = ''
|
el.innerHTML = ''
|
||||||
var canvas = document.createElement('canvas')
|
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
el.appendChild(canvas)
|
el.appendChild(canvas)
|
||||||
/* eslint-disable no-new */
|
|
||||||
new Chart(canvas, chartConfig)
|
const height = el.attributes.getNamedItem('data-height')
|
||||||
|
if (height && height.value !== 'undefined') {
|
||||||
|
el.style.height = height.value + 'vh'
|
||||||
|
canvas.height = height.value + 'vh'
|
||||||
|
}
|
||||||
|
|
||||||
|
const chart = new Chart(canvas, chartConfig)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
|
||||||
el.className = 'chart-error'
|
el.className = 'chart-error'
|
||||||
el.innerHTML = 'chartjs diagram parse error: ' + e.message
|
el.innerHTML = 'chartjs diagram parse error: ' + e.message
|
||||||
}
|
}
|
||||||
@@ -827,6 +872,15 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const regexIsLine = /^:line:[0-9]/
|
||||||
|
if (regexIsLine.test(linkHash)) {
|
||||||
|
const numberPattern = /\d+/g
|
||||||
|
|
||||||
|
const lineNumber = parseInt(linkHash.match(numberPattern)[0])
|
||||||
|
eventEmitter.emit('line:jump', lineNumber)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// this will match the old link format storage.key-note.key
|
// this will match the old link format storage.key-note.key
|
||||||
// e.g.
|
// e.g.
|
||||||
// 877f99c3268608328037-1c211eb7dcb463de6490
|
// 877f99c3268608328037-1c211eb7dcb463de6490
|
||||||
|
|||||||
@@ -165,8 +165,10 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
const idMatch = /checkbox-([0-9]+)/
|
const idMatch = /checkbox-([0-9]+)/
|
||||||
const checkedMatch = /\[x\]/i
|
const checkedMatch = /^\s*[\+\-\*] \[x\]/i
|
||||||
const uncheckedMatch = /\[ \]/
|
const uncheckedMatch = /^\s*[\+\-\*] \[ \]/
|
||||||
|
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
|
||||||
@@ -175,10 +177,10 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
const targetLine = lines[lineIndex]
|
const targetLine = lines[lineIndex]
|
||||||
|
|
||||||
if (targetLine.match(checkedMatch)) {
|
if (targetLine.match(checkedMatch)) {
|
||||||
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
|
lines[lineIndex] = targetLine.replace(checkReplace, '[ ]')
|
||||||
}
|
}
|
||||||
if (targetLine.match(uncheckedMatch)) {
|
if (targetLine.match(uncheckedMatch)) {
|
||||||
lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]')
|
lines[lineIndex] = targetLine.replace(uncheckReplace, '[x]')
|
||||||
}
|
}
|
||||||
this.refs.code.setValue(lines.join('\n'))
|
this.refs.code.setValue(lines.join('\n'))
|
||||||
}
|
}
|
||||||
@@ -264,7 +266,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
styleName='codeEditor'
|
styleName='codeEditor'
|
||||||
ref='code'
|
ref='code'
|
||||||
width={this.state.codeEditorWidthInPercent + '%'}
|
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}
|
||||||
@@ -283,6 +285,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
onChange={this.handleOnChange.bind(this)}
|
onChange={this.handleOnChange.bind(this)}
|
||||||
onScroll={this.handleEditorScroll.bind(this)}
|
onScroll={this.handleEditorScroll.bind(this)}
|
||||||
onCursorActivity={this.handleCursorActivity.bind(this)}
|
onCursorActivity={this.handleCursorActivity.bind(this)}
|
||||||
|
spellCheck={config.editor.spellcheck}
|
||||||
/>
|
/>
|
||||||
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
||||||
<div styleName='slider-hitbox' />
|
<div styleName='slider-hitbox' />
|
||||||
@@ -312,6 +315,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
customCSS={config.preview.customCSS}
|
customCSS={config.preview.customCSS}
|
||||||
allowCustomCSS={config.preview.allowCustomCSS}
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
|
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,16 +24,19 @@ const TagElement = ({ tagName }) => (
|
|||||||
/**
|
/**
|
||||||
* @description Tag element list component.
|
* @description Tag element list component.
|
||||||
* @param {Array|null} tags
|
* @param {Array|null} tags
|
||||||
|
* @param {boolean} showTagsAlphabetically
|
||||||
* @return {React.Component}
|
* @return {React.Component}
|
||||||
*/
|
*/
|
||||||
const TagElementList = tags => {
|
const TagElementList = (tags, showTagsAlphabetically) => {
|
||||||
if (!isArray(tags)) {
|
if (!isArray(tags)) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagElements = tags.map(tag => TagElement({ tagName: tag }))
|
if (showTagsAlphabetically) {
|
||||||
|
return _.sortBy(tags).map(tag => TagElement({ tagName: tag }))
|
||||||
return tagElements
|
} else {
|
||||||
|
return tags.map(tag => TagElement({ tagName: tag }))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,7 +58,8 @@ const NoteItem = ({
|
|||||||
pathname,
|
pathname,
|
||||||
storageName,
|
storageName,
|
||||||
folderName,
|
folderName,
|
||||||
viewType
|
viewType,
|
||||||
|
showTagsAlphabetically
|
||||||
}) => (
|
}) => (
|
||||||
<div
|
<div
|
||||||
styleName={isActive ? 'item--active' : 'item'}
|
styleName={isActive ? 'item--active' : 'item'}
|
||||||
@@ -74,28 +78,26 @@ const NoteItem = ({
|
|||||||
? note.title
|
? note.title
|
||||||
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>}
|
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>}
|
||||||
</div>
|
</div>
|
||||||
{['ALL', 'STORAGE'].includes(viewType) &&
|
<div styleName='item-middle'>
|
||||||
<div styleName='item-middle'>
|
<div styleName='item-middle-time'>{dateDisplay}</div>
|
||||||
<div styleName='item-middle-time'>{dateDisplay}</div>
|
<div styleName='item-middle-app-meta'>
|
||||||
<div styleName='item-middle-app-meta'>
|
<div
|
||||||
<div
|
title={
|
||||||
title={
|
viewType === 'ALL'
|
||||||
viewType === 'ALL'
|
? storageName
|
||||||
? storageName
|
: viewType === 'STORAGE' ? folderName : null
|
||||||
: viewType === 'STORAGE' ? folderName : null
|
}
|
||||||
}
|
styleName='item-middle-app-meta-label'
|
||||||
styleName='item-middle-app-meta-label'
|
>
|
||||||
>
|
{viewType === 'ALL' && storageName}
|
||||||
{viewType === 'ALL' && storageName}
|
{viewType === 'STORAGE' && folderName}
|
||||||
{viewType === 'STORAGE' && folderName}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>
|
||||||
|
</div>
|
||||||
<div styleName='item-bottom'>
|
<div styleName='item-bottom'>
|
||||||
<div styleName='item-bottom-tagList'>
|
<div styleName='item-bottom-tagList'>
|
||||||
{note.tags.length > 0
|
{note.tags.length > 0
|
||||||
? TagElementList(note.tags)
|
? TagElementList(note.tags, showTagsAlphabetically)
|
||||||
: <span
|
: <span
|
||||||
style={{ fontStyle: 'italic', opacity: 0.5 }}
|
style={{ fontStyle: 'italic', opacity: 0.5 }}
|
||||||
styleName='item-bottom-tagList-empty'
|
styleName='item-bottom-tagList-empty'
|
||||||
|
|||||||
@@ -368,13 +368,13 @@ body[data-theme="monokai"]
|
|||||||
.item-title
|
.item-title
|
||||||
.item-title-icon
|
.item-title-icon
|
||||||
.item-bottom-time
|
.item-bottom-time
|
||||||
color $ui-monokai-text-color
|
color $ui-monokai-active-color
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
background-color alpha(white, 10%)
|
background-color alpha(white, 10%)
|
||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
&:hover
|
&:hover
|
||||||
// background-color alpha($ui-monokai-button--active-backgroundColor, 60%)
|
// background-color alpha($ui-monokai-button--active-backgroundColor, 60%)
|
||||||
color #c0392b
|
color #f92672
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
background-color alpha(#fff, 20%)
|
background-color alpha(#fff, 20%)
|
||||||
|
|
||||||
@@ -394,3 +394,76 @@ body[data-theme="monokai"]
|
|||||||
.item-bottom-tagList-empty
|
.item-bottom-tagList-empty
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
|
|
||||||
|
body[data-theme="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
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
vertical-align middle
|
||||||
@@ -240,7 +240,7 @@ body[data-theme="monokai"]
|
|||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
.item-simple-bottom-time
|
.item-simple-bottom-time
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-monokai-text-color
|
||||||
.item-simple-bottom-tagList-item
|
.item-simple-bottom-tagList-item
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color alpha(#fff, 20%)
|
background-color alpha(#fff, 20%)
|
||||||
@@ -286,3 +286,67 @@ body[data-theme="monokai"]
|
|||||||
.item-simple-right-storageName
|
.item-simple-right-storageName
|
||||||
padding-left 4px
|
padding-left 4px
|
||||||
opacity 0.4
|
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
|
||||||
@@ -51,4 +51,15 @@ body[data-theme="monokai"]
|
|||||||
border none
|
border none
|
||||||
background-color $ui-monokai-button-backgroundColor
|
background-color $ui-monokai-button-backgroundColor
|
||||||
&:hover
|
&:hover
|
||||||
color #5CB85C
|
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
|
||||||
@@ -263,4 +263,46 @@ body[data-theme="monokai"]
|
|||||||
background-color $ui-monokai-button-backgroundColor
|
background-color $ui-monokai-button-backgroundColor
|
||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-monokai-text-color
|
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
|
||||||
@@ -180,4 +180,48 @@ body[data-theme="monokai"]
|
|||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
.deleteButton
|
.deleteButton
|
||||||
color alpha($ui-monokai-text-color, 30%)
|
color alpha($ui-monokai-text-color, 30%)
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.root
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
&:hover
|
||||||
|
background-color $ui-dracula-noteDetail-backgroundColor
|
||||||
|
.deleteButton
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
&:hover
|
||||||
|
background-color darken($ui-dracula-noteDetail-backgroundColor, 15%)
|
||||||
|
&:active
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.root--active
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
|
&:hover
|
||||||
|
background-color $ui-dracula-noteDetail-backgroundColor
|
||||||
|
.deleteButton
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
&:hover
|
||||||
|
background-color darken($ui-dracula-noteDetail-backgroundColor, 15%)
|
||||||
|
&:active
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.button
|
||||||
|
border none
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
background-color transparent
|
||||||
|
transition color background-color 0.15s
|
||||||
|
border-left 4px solid transparent
|
||||||
|
&:hover
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
background-color $ui-dracula-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.input
|
||||||
|
background-color $ui-dracula-noteDetail-backgroundColor
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
|
.deleteButton
|
||||||
|
color alpha($ui-dracula-text-color, 30%)
|
||||||
@@ -156,4 +156,23 @@ body[data-theme="monokai"]
|
|||||||
background-color $ui-monokai-button-backgroundColor
|
background-color $ui-monokai-button-backgroundColor
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
background-color $ui-monokai-button-backgroundColor
|
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
|
||||||
@@ -7,18 +7,18 @@ import styles from './StorageList.styl'
|
|||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Array} storgaeList
|
* @param {Array} storageList
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const StorageList = ({storageList, isFolded}) => (
|
const StorageList = ({storageList, isFolded}) => (
|
||||||
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
|
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
|
||||||
{storageList.length > 0 ? storageList : (
|
{storageList.length > 0 ? storageList : (
|
||||||
<div styleName='storgaeList-empty'>No storage mount.</div>
|
<div styleName='storageList-empty'>No storage mount.</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
StorageList.propTypes = {
|
StorageList.propTypes = {
|
||||||
storgaeList: PropTypes.arrayOf(PropTypes.element).isRequired
|
storageList: PropTypes.arrayOf(PropTypes.element).isRequired
|
||||||
}
|
}
|
||||||
export default CSSModules(StorageList, styles)
|
export default CSSModules(StorageList, styles)
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
* @param {bool} isRelated
|
* @param {bool} isRelated
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isActive, isRelated, count}) => (
|
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count}) => (
|
||||||
<div styleName='tagList-itemContainer'>
|
<div styleName='tagList-itemContainer' onContextMenu={e => handleContextMenu(e, name)}>
|
||||||
{isRelated
|
{isRelated
|
||||||
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
|
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
|
||||||
<i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
|
<i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
|
||||||
@@ -25,7 +25,7 @@ const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isAc
|
|||||||
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
|
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
|
||||||
<span styleName='tagList-item-name'>
|
<span styleName='tagList-item-name'>
|
||||||
{`# ${name}`}
|
{`# ${name}`}
|
||||||
<span styleName='tagList-item-count'>{count}</span>
|
<span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import styles from './TodoListPercentage.styl'
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const TodoListPercentage = ({
|
const TodoListPercentage = ({
|
||||||
percentageOfTodo
|
percentageOfTodo, onClearCheckboxClick
|
||||||
}) => (
|
}) => (
|
||||||
<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,11 +20,15 @@ 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)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
.percentageBar
|
.percentageBar
|
||||||
|
display: flex
|
||||||
position absolute
|
position absolute
|
||||||
top 72px
|
top 72px
|
||||||
right 0px
|
right 0px
|
||||||
@@ -30,6 +31,20 @@
|
|||||||
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
|
||||||
@@ -39,7 +54,10 @@ 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
|
||||||
@@ -50,12 +68,28 @@ body[data-theme="solarized-dark"]
|
|||||||
.percentageText
|
.percentageText
|
||||||
color #fdf6e3
|
color #fdf6e3
|
||||||
|
|
||||||
|
.todoClearText
|
||||||
|
color #fdf6e3
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
body[data-theme="monokai"]
|
||||||
.percentageBar
|
.percentageBar
|
||||||
background-color #f92672
|
background-color: $ui-monokai-borderColor
|
||||||
|
|
||||||
.progressBar
|
.progressBar
|
||||||
background-color: #373831
|
background-color $ui-monokai-active-color
|
||||||
|
|
||||||
.percentageText
|
.percentageText
|
||||||
color #fdf6e3
|
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
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ li
|
|||||||
&.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
|
||||||
@@ -206,41 +209,39 @@ code
|
|||||||
text-decoration none
|
text-decoration none
|
||||||
margin-right 2px
|
margin-right 2px
|
||||||
pre
|
pre
|
||||||
padding 0.5em !important
|
padding 0.5rem !important
|
||||||
border solid 1px #D1D1D1
|
border solid 1px #D1D1D1
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
overflow-x auto
|
overflow-x auto
|
||||||
margin 0 0 1em
|
margin 0 0 1rem
|
||||||
display flex
|
display flex
|
||||||
line-height 1.4em
|
line-height 1.4em
|
||||||
&.flowchart, &.sequence, &.chart
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
background-color white
|
|
||||||
&.CodeMirror
|
|
||||||
height initial
|
|
||||||
flex-wrap wrap
|
|
||||||
&>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
|
&>span.filename
|
||||||
width 100%
|
margin -0.5rem 100% 0.5rem -0.5rem
|
||||||
border-radius: 5px 0px 0px 0px
|
padding 0.125rem 0.375rem
|
||||||
margin -8px 100% 8px -8px
|
|
||||||
padding 0px 6px
|
|
||||||
background-color #777;
|
background-color #777;
|
||||||
color white
|
color white
|
||||||
|
&:empty
|
||||||
|
display none
|
||||||
&>span.lineNumber
|
&>span.lineNumber
|
||||||
display none
|
display none
|
||||||
font-size 1em
|
font-size 1em
|
||||||
padding 0.5em 0
|
padding 0.5rem 0
|
||||||
margin -0.5em 0.5em -0.5em -0.5em
|
margin -0.5rem 0.5rem -0.5rem -0.5rem
|
||||||
border-right 1px solid
|
border-right 1px solid
|
||||||
text-align right
|
text-align right
|
||||||
border-top-left-radius 4px
|
border-top-left-radius 4px
|
||||||
@@ -361,7 +362,7 @@ for name, val in admonition_types
|
|||||||
.admonition.{name}
|
.admonition.{name}
|
||||||
@extend $admonition
|
@extend $admonition
|
||||||
border-left-color: val[color]
|
border-left-color: val[color]
|
||||||
|
|
||||||
.admonition.{name}>.admonition-title
|
.admonition.{name}>.admonition-title
|
||||||
@extend $admonition-title
|
@extend $admonition-title
|
||||||
border-bottom-color: .1rem solid rgba(val[color], 0.2)
|
border-bottom-color: .1rem solid rgba(val[color], 0.2)
|
||||||
@@ -372,6 +373,49 @@ for name, val in admonition_types
|
|||||||
color: val[color]
|
color: val[color]
|
||||||
content: val[icon]
|
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
|
||||||
|
|
||||||
themeDarkBackground = darken(#21252B, 10%)
|
themeDarkBackground = darken(#21252B, 10%)
|
||||||
themeDarkText = #f9f9f9
|
themeDarkText = #f9f9f9
|
||||||
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
||||||
@@ -422,6 +466,14 @@ 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
|
||||||
|
|
||||||
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%)
|
||||||
@@ -449,6 +501,14 @@ 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
|
||||||
|
|
||||||
themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
|
themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
|
||||||
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
|
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
|
||||||
@@ -477,4 +537,49 @@ body[data-theme="monokai"]
|
|||||||
&:last-child
|
&:last-child
|
||||||
border-right solid 1px themeMonokaiTableBorder
|
border-right solid 1px themeMonokaiTableBorder
|
||||||
kbd
|
kbd
|
||||||
background-color themeDarkBackground
|
background-color themeDarkBackground
|
||||||
|
dl
|
||||||
|
border-color themeDarkBorder
|
||||||
|
background-color themeMonokaiTableHead
|
||||||
|
dt
|
||||||
|
border-color themeDarkBorder
|
||||||
|
dd
|
||||||
|
border-color themeDarkBorder
|
||||||
|
background-color $ui-monokai-noteDetail-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
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import mermaidAPI from 'mermaid'
|
|||||||
|
|
||||||
// fixes bad styling in the mermaid dark theme
|
// fixes bad styling in the mermaid dark theme
|
||||||
const darkThemeStyling = `
|
const darkThemeStyling = `
|
||||||
.loopText tspan {
|
.loopText tspan {
|
||||||
fill: white;
|
fill: white;
|
||||||
}`
|
}`
|
||||||
|
|
||||||
function getRandomInt (min, max) {
|
function getRandomInt (min, max) {
|
||||||
@@ -11,9 +11,9 @@ function getRandomInt (min, max) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getId () {
|
function getId () {
|
||||||
var pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
const pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||||
var id = 'm-'
|
let id = 'm-'
|
||||||
for (var i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
id += pool[getRandomInt(0, 16)]
|
id += pool[getRandomInt(0, 16)]
|
||||||
}
|
}
|
||||||
return id
|
return id
|
||||||
@@ -21,16 +21,20 @@ function getId () {
|
|||||||
|
|
||||||
function render (element, content, theme) {
|
function render (element, content, theme) {
|
||||||
try {
|
try {
|
||||||
let isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai'
|
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({
|
mermaidAPI.initialize({
|
||||||
theme: isDarkTheme ? 'dark' : 'default',
|
theme: isDarkTheme ? 'dark' : 'default',
|
||||||
themeCSS: isDarkTheme ? darkThemeStyling : ''
|
themeCSS: isDarkTheme ? darkThemeStyling : '',
|
||||||
|
useMaxWidth: false
|
||||||
})
|
})
|
||||||
mermaidAPI.render(getId(), content, (svgGraph) => {
|
mermaidAPI.render(getId(), content, (svgGraph) => {
|
||||||
element.innerHTML = svgGraph
|
element.innerHTML = svgGraph
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
|
||||||
element.className = 'mermaid-error'
|
element.className = 'mermaid-error'
|
||||||
element.innerHTML = 'mermaid diagram parse error: ' + e.message
|
element.innerHTML = 'mermaid diagram parse error: ' + e.message
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,8 +48,12 @@ const languages = [
|
|||||||
locale: 'pl'
|
locale: 'pl'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Portuguese',
|
name: 'Portuguese (PT-BR)',
|
||||||
locale: 'pt'
|
locale: 'pt-BR'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Portuguese (PT-PT)',
|
||||||
|
locale: 'pt-PT'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Russian',
|
name: 'Russian',
|
||||||
@@ -61,6 +65,9 @@ const languages = [
|
|||||||
}, {
|
}, {
|
||||||
name: 'Turkish',
|
name: 'Turkish',
|
||||||
locale: 'tr'
|
locale: 'tr'
|
||||||
|
}, {
|
||||||
|
name: 'Thai',
|
||||||
|
locale: 'th'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
65
browser/lib/contextMenuBuilder.js
Normal file
65
browser/lib/contextMenuBuilder.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
const {remote} = require('electron')
|
||||||
|
const {Menu} = remote.require('electron')
|
||||||
|
const spellcheck = require('./spellcheck')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = buildEditorContextMenu
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export function findNoteTitle (value) {
|
export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleField = 'title') {
|
||||||
const splitted = value.split('\n')
|
const splitted = value.split('\n')
|
||||||
let title = null
|
let title = null
|
||||||
let isInsideCodeBlock = false
|
let isInsideCodeBlock = false
|
||||||
@@ -6,6 +6,11 @@ export function findNoteTitle (value) {
|
|||||||
if (splitted[0] === '---') {
|
if (splitted[0] === '---') {
|
||||||
let line = 0
|
let line = 0
|
||||||
while (++line < splitted.length) {
|
while (++line < splitted.length) {
|
||||||
|
if (enableFrontMatterTitle && splitted[line].startsWith(frontMatterTitleField + ':')) {
|
||||||
|
title = splitted[line].substring(frontMatterTitleField.length + 1).trim()
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
if (splitted[line] === '---') {
|
if (splitted[line] === '---') {
|
||||||
splitted.splice(0, line + 1)
|
splitted.splice(0, line + 1)
|
||||||
|
|
||||||
@@ -14,17 +19,19 @@ export function findNoteTitle (value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
splitted.some((line, index) => {
|
if (title === null) {
|
||||||
const trimmedLine = line.trim()
|
splitted.some((line, index) => {
|
||||||
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
|
const trimmedLine = line.trim()
|
||||||
if (trimmedLine.match('```')) {
|
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
|
||||||
isInsideCodeBlock = !isInsideCodeBlock
|
if (trimmedLine.match('```')) {
|
||||||
}
|
isInsideCodeBlock = !isInsideCodeBlock
|
||||||
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
|
}
|
||||||
title = trimmedLine
|
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
|
||||||
return true
|
title = trimmedLine
|
||||||
}
|
return true
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (title === null) {
|
if (title === null) {
|
||||||
title = ''
|
title = ''
|
||||||
|
|||||||
232
browser/lib/markdown-it-deflist.js
Normal file
232
browser/lib/markdown-it-deflist.js
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
'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' ] })
|
||||||
|
}
|
||||||
136
browser/lib/markdown-it-fence.js
Normal file
136
browser/lib/markdown-it-fence.js
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
'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])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import sanitizeHtml from 'sanitize-html'
|
import sanitizeHtml from 'sanitize-html'
|
||||||
import { escapeHtmlCharacters } from './utils'
|
import { escapeHtmlCharacters } from './utils'
|
||||||
|
import url from 'url'
|
||||||
|
|
||||||
module.exports = function sanitizePlugin (md, options) {
|
module.exports = function sanitizePlugin (md, options) {
|
||||||
options = options || {}
|
options = options || {}
|
||||||
@@ -14,7 +15,7 @@ module.exports = function sanitizePlugin (md, options) {
|
|||||||
options
|
options
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (state.tokens[tokenIdx].type === 'fence') {
|
if (state.tokens[tokenIdx].type === '_fence') {
|
||||||
// escapeHtmlCharacters has better performance
|
// escapeHtmlCharacters has better performance
|
||||||
state.tokens[tokenIdx].content = escapeHtmlCharacters(
|
state.tokens[tokenIdx].content = escapeHtmlCharacters(
|
||||||
state.tokens[tokenIdx].content,
|
state.tokens[tokenIdx].content,
|
||||||
@@ -25,7 +26,7 @@ module.exports = function sanitizePlugin (md, options) {
|
|||||||
const inlineTokens = state.tokens[tokenIdx].children
|
const inlineTokens = state.tokens[tokenIdx].children
|
||||||
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
|
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
|
||||||
if (inlineTokens[childIdx].type === 'html_inline') {
|
if (inlineTokens[childIdx].type === 'html_inline') {
|
||||||
inlineTokens[childIdx].content = sanitizeHtml(
|
inlineTokens[childIdx].content = sanitizeInline(
|
||||||
inlineTokens[childIdx].content,
|
inlineTokens[childIdx].content,
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
@@ -35,3 +36,89 @@ module.exports = function sanitizePlugin (md, options) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:=("|')(?:[^\3]+?)\3)?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i
|
||||||
|
const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/ig
|
||||||
|
|
||||||
|
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, '')
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import toc from 'markdown-toc'
|
import toc from 'markdown-toc'
|
||||||
import diacritics from 'diacritics-map'
|
import diacritics from 'diacritics-map'
|
||||||
import stripColor from 'strip-color'
|
import stripColor from 'strip-color'
|
||||||
|
import mdlink from 'markdown-link'
|
||||||
|
|
||||||
const EOL = require('os').EOL
|
const EOL = require('os').EOL
|
||||||
|
|
||||||
@@ -42,6 +43,12 @@ function caseSensitiveSlugify (str) {
|
|||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function linkify (tok, text, slug, opts) {
|
||||||
|
var uniqeID = opts.num === 0 ? '' : '-' + opts.num
|
||||||
|
tok.content = mdlink(text, '#' + slug + uniqeID)
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
|
||||||
const TOC_MARKER_START = '<!-- toc -->'
|
const TOC_MARKER_START = '<!-- toc -->'
|
||||||
const TOC_MARKER_END = '<!-- tocstop -->'
|
const TOC_MARKER_END = '<!-- tocstop -->'
|
||||||
|
|
||||||
@@ -84,7 +91,7 @@ export function generateInEditor (editor) {
|
|||||||
* @returns generatedTOC String containing generated TOC
|
* @returns generatedTOC String containing generated TOC
|
||||||
*/
|
*/
|
||||||
export function generate (markdownText) {
|
export function generate (markdownText) {
|
||||||
const generatedToc = toc(markdownText, {slugify: caseSensitiveSlugify})
|
const generatedToc = toc(markdownText, {slugify: caseSensitiveSlugify, linkify: linkify})
|
||||||
return TOC_MARKER_START + EOL + EOL + generatedToc.content + EOL + EOL + TOC_MARKER_END
|
return TOC_MARKER_START + EOL + EOL + generatedToc.content + EOL + EOL + TOC_MARKER_END
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import _ from 'lodash'
|
|||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import katex from 'katex'
|
import katex from 'katex'
|
||||||
import { lastFindInArray } from './utils'
|
import { lastFindInArray } from './utils'
|
||||||
|
import anchor from '@enyaxu/markdown-it-anchor'
|
||||||
|
|
||||||
function createGutter (str, firstLineNumber) {
|
function createGutter (str, firstLineNumber) {
|
||||||
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
||||||
@@ -27,32 +28,6 @@ class Markdown {
|
|||||||
html: true,
|
html: true,
|
||||||
xhtmlOut: true,
|
xhtmlOut: true,
|
||||||
breaks: config.preview.breaks,
|
breaks: config.preview.breaks,
|
||||||
highlight: function (str, lang) {
|
|
||||||
const delimiter = ':'
|
|
||||||
const langInfo = lang.split(delimiter)
|
|
||||||
const langType = langInfo[0]
|
|
||||||
const fileName = langInfo[1] || ''
|
|
||||||
const firstLineNumber = parseInt(langInfo[2], 10)
|
|
||||||
|
|
||||||
if (langType === 'flowchart') {
|
|
||||||
return `<pre class="flowchart">${str}</pre>`
|
|
||||||
}
|
|
||||||
if (langType === 'sequence') {
|
|
||||||
return `<pre class="sequence">${str}</pre>`
|
|
||||||
}
|
|
||||||
if (langType === 'chart') {
|
|
||||||
return `<pre class="chart">${str}</pre>`
|
|
||||||
}
|
|
||||||
if (langType === 'mermaid') {
|
|
||||||
return `<pre class="mermaid">${str}</pre>`
|
|
||||||
}
|
|
||||||
return '<pre class="code CodeMirror">' +
|
|
||||||
'<span class="filename">' + fileName + '</span>' +
|
|
||||||
createGutter(str, firstLineNumber) +
|
|
||||||
'<code class="' + langType + '">' +
|
|
||||||
str +
|
|
||||||
'</code></pre>'
|
|
||||||
},
|
|
||||||
sanitize: 'STRICT'
|
sanitize: 'STRICT'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +80,11 @@ class Markdown {
|
|||||||
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
||||||
'input': ['type', 'id', 'checked']
|
'input': ['type', 'id', 'checked']
|
||||||
},
|
},
|
||||||
allowedIframeHostnames: ['www.youtube.com']
|
allowedIframeHostnames: ['www.youtube.com'],
|
||||||
|
selfClosing: [ 'img', 'br', 'hr', 'input' ],
|
||||||
|
allowedSchemes: [ 'http', 'https', 'ftp', 'mailto' ],
|
||||||
|
allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ],
|
||||||
|
allowProtocolRelative: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,18 +118,60 @@ class Markdown {
|
|||||||
this.md.use(require('markdown-it-imsize'))
|
this.md.use(require('markdown-it-imsize'))
|
||||||
this.md.use(require('markdown-it-footnote'))
|
this.md.use(require('markdown-it-footnote'))
|
||||||
this.md.use(require('markdown-it-multimd-table'))
|
this.md.use(require('markdown-it-multimd-table'))
|
||||||
this.md.use(require('markdown-it-named-headers'), {
|
this.md.use(anchor, {
|
||||||
slugify: (header) => {
|
slugify: (title) => {
|
||||||
return encodeURI(header.trim()
|
var slug = encodeURI(title.trim()
|
||||||
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
|
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
|
||||||
.replace(/\s+/g, '-'))
|
.replace(/\s+/g, '-'))
|
||||||
.replace(/\-+$/, '')
|
.replace(/\-+$/, '')
|
||||||
|
return slug
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.md.use(require('markdown-it-kbd'))
|
this.md.use(require('markdown-it-kbd'))
|
||||||
this.md.use(require('markdown-it-admonition'))
|
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(require('./markdown-it-deflist'))
|
||||||
this.md.use(require('./markdown-it-frontmatter'))
|
this.md.use(require('./markdown-it-frontmatter'))
|
||||||
|
|
||||||
|
this.md.use(require('./markdown-it-fence'), {
|
||||||
|
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>`
|
||||||
|
},
|
||||||
|
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 deflate = require('markdown-it-plantuml/lib/deflate')
|
||||||
this.md.use(require('markdown-it-plantuml'), '', {
|
this.md.use(require('markdown-it-plantuml'), '', {
|
||||||
generateSource: function (umlCode) {
|
generateSource: function (umlCode) {
|
||||||
@@ -222,7 +243,11 @@ class Markdown {
|
|||||||
if (!liToken.attrs) {
|
if (!liToken.attrs) {
|
||||||
liToken.attrs = []
|
liToken.attrs = []
|
||||||
}
|
}
|
||||||
liToken.attrs.push(['class', 'taskListItem'])
|
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>`
|
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>`
|
||||||
}
|
}
|
||||||
@@ -247,9 +272,12 @@ class Markdown {
|
|||||||
this.md.renderer.render = (tokens, options, env) => {
|
this.md.renderer.render = (tokens, options, env) => {
|
||||||
tokens.forEach((token) => {
|
tokens.forEach((token) => {
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case 'heading_open':
|
|
||||||
case 'paragraph_open':
|
|
||||||
case 'blockquote_open':
|
case 'blockquote_open':
|
||||||
|
case 'dd_open':
|
||||||
|
case 'dt_open':
|
||||||
|
case 'heading_open':
|
||||||
|
case 'list_item_open':
|
||||||
|
case 'paragraph_open':
|
||||||
case 'table_open':
|
case 'table_open':
|
||||||
token.attrPush(['data-line', token.map[0]])
|
token.attrPush(['data-line', token.map[0]])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,21 @@ import dataApi from 'browser/main/lib/dataApi'
|
|||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
|
|
||||||
export function createMarkdownNote (storage, folder, dispatch, location) {
|
export function createMarkdownNote (storage, folder, dispatch, location, params, config) {
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
||||||
|
|
||||||
|
let tags = []
|
||||||
|
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
|
||||||
|
tags = params.tagname.split(' ')
|
||||||
|
}
|
||||||
|
|
||||||
return dataApi
|
return dataApi
|
||||||
.createNote(storage, {
|
.createNote(storage, {
|
||||||
type: 'MARKDOWN_NOTE',
|
type: 'MARKDOWN_NOTE',
|
||||||
folder: folder,
|
folder: folder,
|
||||||
title: '',
|
title: '',
|
||||||
|
tags,
|
||||||
content: ''
|
content: ''
|
||||||
})
|
})
|
||||||
.then(note => {
|
.then(note => {
|
||||||
@@ -29,14 +36,21 @@ export function createMarkdownNote (storage, folder, dispatch, location) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSnippetNote (storage, folder, dispatch, location, config) {
|
export function createSnippetNote (storage, folder, dispatch, location, params, config) {
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
||||||
|
|
||||||
|
let tags = []
|
||||||
|
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
|
||||||
|
tags = params.tagname.split(' ')
|
||||||
|
}
|
||||||
|
|
||||||
return dataApi
|
return dataApi
|
||||||
.createNote(storage, {
|
.createNote(storage, {
|
||||||
type: 'SNIPPET_NOTE',
|
type: 'SNIPPET_NOTE',
|
||||||
folder: folder,
|
folder: folder,
|
||||||
title: '',
|
title: '',
|
||||||
|
tags,
|
||||||
description: '',
|
description: '',
|
||||||
snippets: [
|
snippets: [
|
||||||
{
|
{
|
||||||
|
|||||||
232
browser/lib/spellcheck.js
Normal file
232
browser/lib/spellcheck.js
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
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.__('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
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ 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
|
||||||
@@ -37,3 +37,10 @@ body[data-theme="monokai"]
|
|||||||
border-left 1px solid $ui-monokai-borderColor
|
border-left 1px solid $ui-monokai-borderColor
|
||||||
.empty-message
|
.empty-message
|
||||||
color $ui-monokai-text-color
|
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
|
||||||
@@ -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
|
||||||
@@ -159,3 +159,29 @@ body[data-theme="monokai"]
|
|||||||
color $ui-monokai-button--active-color
|
color $ui-monokai-button--active-color
|
||||||
.search-optionList-item-name-surfix
|
.search-optionList-item-name-surfix
|
||||||
color $ui-monokai-inactive-text-color
|
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
|
||||||
|
|||||||
@@ -257,3 +257,43 @@ body[data-theme="monokai"]
|
|||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-monokai-text-color
|
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
|
||||||
@@ -61,11 +61,14 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
||||||
this.handleSwitchMode(reversedType)
|
this.handleSwitchMode(reversedType)
|
||||||
})
|
})
|
||||||
|
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
|
||||||
ee.on('code:generate-toc', this.generateToc)
|
ee.on('code:generate-toc', this.generateToc)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
|
const isNewNote = nextProps.note.key !== this.props.note.key
|
||||||
|
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({}, nextProps.note)
|
note: Object.assign({}, nextProps.note)
|
||||||
@@ -91,7 +94,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)))
|
note.title = markdown.strip(striptags(findNoteTitle(note.content, this.props.config.editor.enableFrontMatterTitle, this.props.config.editor.frontMatterTitleField)))
|
||||||
this.updateNote(note)
|
this.updateNote(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +190,36 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
ee.emit('export:save-html')
|
ee.emit('export:save-html')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@@ -293,9 +326,33 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
@@ -321,7 +378,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { data, location } = this.props
|
const { data, location, config } = this.props
|
||||||
const { note, editorType } = this.state
|
const { note, editorType } = this.state
|
||||||
const storageKey = note.storage
|
const storageKey = note.storage
|
||||||
const folderKey = note.folder
|
const folderKey = note.folder
|
||||||
@@ -372,10 +429,12 @@ 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}
|
data={data}
|
||||||
onChange={this.handleUpdateTag.bind(this)}
|
onChange={this.handleUpdateTag.bind(this)}
|
||||||
/>
|
/>
|
||||||
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
<TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
||||||
</div>
|
</div>
|
||||||
<div styleName='info-right'>
|
<div styleName='info-right'>
|
||||||
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
||||||
@@ -429,6 +488,7 @@ 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}
|
||||||
|
|||||||
@@ -76,3 +76,8 @@ body[data-theme="monokai"]
|
|||||||
.root
|
.root
|
||||||
border-left 1px solid $ui-monokai-borderColor
|
border-left 1px solid $ui-monokai-borderColor
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.root
|
||||||
|
border-left 1px solid $ui-dracula-borderColor
|
||||||
|
background-color $ui-dracula-noteDetail-backgroundColor
|
||||||
@@ -98,8 +98,13 @@ 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"]
|
body[data-theme="monokai"]
|
||||||
.info
|
.info
|
||||||
border-color $ui-monokai-borderColor
|
border-color $ui-monokai-borderColor
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.info
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
|
background-color $ui-dracula-noteDetail-backgroundColor
|
||||||
@@ -112,7 +112,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)
|
note.title = findNoteTitle(note.description, false)
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
note
|
note
|
||||||
@@ -354,12 +354,10 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
this.refs['code-' + this.state.snippetIndex].reload()
|
this.refs['code-' + this.state.snippetIndex].reload()
|
||||||
|
|
||||||
if (this.visibleTabs.offsetWidth > this.allTabs.scrollWidth) {
|
if (this.visibleTabs.offsetWidth > this.allTabs.scrollWidth) {
|
||||||
console.log('no need for arrows')
|
|
||||||
this.moveTabBarBy(0)
|
this.moveTabBarBy(0)
|
||||||
} else {
|
} else {
|
||||||
const lastTab = this.allTabs.lastChild
|
const lastTab = this.allTabs.lastChild
|
||||||
if (lastTab.offsetLeft + lastTab.offsetWidth < this.visibleTabs.offsetWidth) {
|
if (lastTab.offsetLeft + lastTab.offsetWidth < this.visibleTabs.offsetWidth) {
|
||||||
console.log('need to scroll')
|
|
||||||
const width = this.visibleTabs.offsetWidth
|
const width = this.visibleTabs.offsetWidth
|
||||||
const newLeft = lastTab.offsetLeft + lastTab.offsetWidth - width
|
const newLeft = lastTab.offsetLeft + lastTab.offsetWidth - width
|
||||||
this.moveTabBarBy(newLeft > 0 ? -newLeft : 0)
|
this.moveTabBarBy(newLeft > 0 ? -newLeft : 0)
|
||||||
@@ -436,6 +434,18 @@ 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
|
// L key
|
||||||
case 76:
|
case 76:
|
||||||
{
|
{
|
||||||
@@ -627,7 +637,6 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
focusEditor () {
|
focusEditor () {
|
||||||
console.log('code-' + this.state.snippetIndex)
|
|
||||||
this.refs['code-' + this.state.snippetIndex].focus()
|
this.refs['code-' + this.state.snippetIndex].focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,6 +768,9 @@ 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}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={(e) => this.handleChange(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -169,4 +169,21 @@ body[data-theme="monokai"]
|
|||||||
|
|
||||||
.tabList
|
.tabList
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
color $ui-monokai-text-color
|
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
|
||||||
|
background-color $ui-dracula-noteDetail-backgroundColor
|
||||||
|
color $ui-dracula-text-color
|
||||||
@@ -88,6 +88,11 @@ class TagSelect extends React.Component {
|
|||||||
this.refs.newTag.input.focus()
|
this.refs.newTag.input.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleTagLabelClick (tag) {
|
||||||
|
const { router } = this.context
|
||||||
|
router.push(`/tags/${tag}`)
|
||||||
|
}
|
||||||
|
|
||||||
handleTagRemoveButtonClick (tag) {
|
handleTagRemoveButtonClick (tag) {
|
||||||
this.removeTagByCallback((value, tag) => {
|
this.removeTagByCallback((value, tag) => {
|
||||||
value.splice(value.indexOf(tag), 1)
|
value.splice(value.indexOf(tag), 1)
|
||||||
@@ -174,15 +179,15 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { value, className } = this.props
|
const { value, className, showTagsAlphabetically } = this.props
|
||||||
|
|
||||||
const tagList = _.isArray(value)
|
const tagList = _.isArray(value)
|
||||||
? value.map((tag) => {
|
? (showTagsAlphabetically ? _.sortBy(value) : value).map((tag) => {
|
||||||
return (
|
return (
|
||||||
<span styleName='tag'
|
<span styleName='tag'
|
||||||
key={tag}
|
key={tag}
|
||||||
>
|
>
|
||||||
<span styleName='tag-label'>#{tag}</span>
|
<span styleName='tag-label' onClick={(e) => this.handleTagLabelClick(tag)}>#{tag}</span>
|
||||||
<button styleName='tag-removeButton'
|
<button styleName='tag-removeButton'
|
||||||
onClick={(e) => this.handleTagRemoveButtonClick(tag)}
|
onClick={(e) => this.handleTagRemoveButtonClick(tag)}
|
||||||
>
|
>
|
||||||
@@ -228,6 +233,10 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TagSelect.contextTypes = {
|
||||||
|
router: PropTypes.shape({})
|
||||||
|
}
|
||||||
|
|
||||||
TagSelect.propTypes = {
|
TagSelect.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
value: PropTypes.arrayOf(PropTypes.string),
|
value: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
|||||||
@@ -39,8 +39,9 @@
|
|||||||
|
|
||||||
.tag-label
|
.tag-label
|
||||||
font-size 13px
|
font-size 13px
|
||||||
color: $ui-text-color
|
color $ui-text-color
|
||||||
padding 4px 16px 4px 8px
|
padding 4px 16px 4px 8px
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.tag
|
.tag
|
||||||
@@ -67,11 +68,22 @@ body[data-theme="solarized-dark"]
|
|||||||
|
|
||||||
body[data-theme="monokai"]
|
body[data-theme="monokai"]
|
||||||
.tag
|
.tag
|
||||||
background-color $ui-monokai-button-backgroundColor
|
background-color $ui-monokai-tag-backgroundColor
|
||||||
|
|
||||||
.tag-removeButton
|
.tag-removeButton
|
||||||
border-color $ui-button--focus-borderColor
|
border-color $ui-button--focus-borderColor
|
||||||
background-color transparent
|
background-color transparent
|
||||||
|
|
||||||
.tag-label
|
.tag-label
|
||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.tag
|
||||||
|
background-color $ui-dracula-tag-backgroundColor
|
||||||
|
|
||||||
|
.tag-removeButton
|
||||||
|
border-color $ui-dracula-button--focus-borderColor
|
||||||
|
background-color transparent
|
||||||
|
|
||||||
|
.tag-label
|
||||||
|
color $ui-dracula-borderColor
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './ToggleModeButton.styl'
|
import styles from './ToggleModeButton.styl'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const ToggleModeButton = ({
|
const ToggleModeButton = ({
|
||||||
onClick, editorType
|
onClick, editorType
|
||||||
}) => (
|
}) => (
|
||||||
<div styleName='control-toggleModeButton'>
|
<div styleName='control-toggleModeButton'>
|
||||||
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => onClick('SPLIT')}>
|
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => onClick('SPLIT')}>
|
||||||
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} />
|
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} />
|
||||||
</div>
|
</div>
|
||||||
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
|
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
|
||||||
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
|
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
|
||||||
</div>
|
</div>
|
||||||
<span styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
|
<span styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
ToggleModeButton.propTypes = {
|
ToggleModeButton.propTypes = {
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
editorType: PropTypes.string.Required
|
editorType: PropTypes.string.Required
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(ToggleModeButton, styles)
|
export default CSSModules(ToggleModeButton, styles)
|
||||||
|
|||||||
@@ -59,7 +59,14 @@ body[data-theme="solarized-dark"]
|
|||||||
|
|
||||||
body[data-theme="monokai"]
|
body[data-theme="monokai"]
|
||||||
.control-toggleModeButton
|
.control-toggleModeButton
|
||||||
background-color #272822
|
background-color #373831
|
||||||
.active
|
.active
|
||||||
background-color #1EC38B
|
background-color #f92672
|
||||||
|
box-shadow 2px 0px 7px #222222
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.control-toggleModeButton
|
||||||
|
background-color #44475a
|
||||||
|
.active
|
||||||
|
background-color #bd93f9
|
||||||
box-shadow 2px 0px 7px #222222
|
box-shadow 2px 0px 7px #222222
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class Main extends React.Component {
|
|||||||
init () {
|
init () {
|
||||||
dataApi
|
dataApi
|
||||||
.addStorage({
|
.addStorage({
|
||||||
name: 'My Storage',
|
name: 'My Storage Location',
|
||||||
path: path.join(remote.app.getPath('home'), 'Boostnote')
|
path: path.join(remote.app.getPath('home'), 'Boostnote')
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@@ -80,7 +80,6 @@ class Main extends React.Component {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
console.log(data)
|
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'ADD_STORAGE',
|
type: 'ADD_STORAGE',
|
||||||
storage: data.storage,
|
storage: data.storage,
|
||||||
@@ -141,7 +140,7 @@ class Main extends React.Component {
|
|||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { dispatch, config } = this.props
|
const { dispatch, config } = this.props
|
||||||
|
|
||||||
const supportedThemes = ['dark', 'white', 'solarized-dark', 'monokai']
|
const supportedThemes = ['dark', 'white', 'solarized-dark', 'monokai', 'dracula']
|
||||||
|
|
||||||
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
|
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
|
||||||
document.body.setAttribute('data-theme', config.ui.theme)
|
document.body.setAttribute('data-theme', config.ui.theme)
|
||||||
@@ -168,6 +167,8 @@ class Main extends React.Component {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
delete CodeMirror.keyMap.emacs['Ctrl-V']
|
||||||
|
|
||||||
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
|
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,7 +298,7 @@ class Main extends React.Component {
|
|||||||
onMouseUp={e => this.handleMouseUp(e)}
|
onMouseUp={e => this.handleMouseUp(e)}
|
||||||
>
|
>
|
||||||
<SideNav
|
<SideNav
|
||||||
{..._.pick(this.props, ['dispatch', 'data', 'config', 'location'])}
|
{..._.pick(this.props, ['dispatch', 'data', 'config', 'params', 'location'])}
|
||||||
width={this.state.navWidth}
|
width={this.state.navWidth}
|
||||||
/>
|
/>
|
||||||
{!config.isSideNavFolded &&
|
{!config.isSideNavFolded &&
|
||||||
|
|||||||
@@ -79,3 +79,7 @@ body[data-theme="solarized-dark"]
|
|||||||
body[data-theme="monokai"]
|
body[data-theme="monokai"]
|
||||||
.root, .root--expanded
|
.root, .root--expanded
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.root, .root--expanded
|
||||||
|
background-color $ui-dracula-noteList-backgroundColor
|
||||||
@@ -35,19 +35,20 @@ class NewNoteButton extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleNewNoteButtonClick (e) {
|
handleNewNoteButtonClick (e) {
|
||||||
const { location, dispatch, config } = this.props
|
const { location, params, dispatch, config } = this.props
|
||||||
const { storage, folder } = this.resolveTargetFolder()
|
const { storage, folder } = this.resolveTargetFolder()
|
||||||
|
|
||||||
if (config.ui.defaultNote === 'MARKDOWN_NOTE') {
|
if (config.ui.defaultNote === 'MARKDOWN_NOTE') {
|
||||||
createMarkdownNote(storage.key, folder.key, dispatch, location)
|
createMarkdownNote(storage.key, folder.key, dispatch, location, params, config)
|
||||||
} else if (config.ui.defaultNote === 'SNIPPET_NOTE') {
|
} else if (config.ui.defaultNote === 'SNIPPET_NOTE') {
|
||||||
createSnippetNote(storage.key, folder.key, dispatch, location, config)
|
createSnippetNote(storage.key, folder.key, dispatch, location, params, config)
|
||||||
} else {
|
} else {
|
||||||
modal.open(NewNoteModal, {
|
modal.open(NewNoteModal, {
|
||||||
storage: storage.key,
|
storage: storage.key,
|
||||||
folder: folder.key,
|
folder: folder.key,
|
||||||
dispatch,
|
dispatch,
|
||||||
location,
|
location,
|
||||||
|
params,
|
||||||
config
|
config
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
.control-button--active
|
.control-button--active
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
&:active
|
&:active
|
||||||
@@ -109,7 +109,7 @@ body[data-theme="solarized-dark"]
|
|||||||
color $ui-solarized-dark-inactive-text-color
|
color $ui-solarized-dark-inactive-text-color
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
.control-button--active
|
.control-button--active
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
&:active
|
&:active
|
||||||
@@ -138,3 +138,27 @@ body[data-theme="monokai"]
|
|||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
&:active
|
&:active
|
||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.root
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
|
background-color $ui-dracula-noteList-backgroundColor
|
||||||
|
|
||||||
|
.control
|
||||||
|
background-color $ui-dracula-noteList-backgroundColor
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
|
|
||||||
|
.control-sortBy-select
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
|
.control-button
|
||||||
|
color $ui-dracula-inactive-text-color
|
||||||
|
&:hover
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
|
.control-button--active
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
&:active
|
||||||
|
color $ui-dracula-text-color
|
||||||
@@ -56,7 +56,6 @@ class NoteList extends React.Component {
|
|||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.selectNextNoteHandler = () => {
|
this.selectNextNoteHandler = () => {
|
||||||
console.log('fired next')
|
|
||||||
this.selectNextNote()
|
this.selectNextNote()
|
||||||
}
|
}
|
||||||
this.selectPriorNoteHandler = () => {
|
this.selectPriorNoteHandler = () => {
|
||||||
@@ -84,7 +83,9 @@ class NoteList extends React.Component {
|
|||||||
|
|
||||||
// TODO: not Selected noteKeys but SelectedNote(for reusing)
|
// TODO: not Selected noteKeys but SelectedNote(for reusing)
|
||||||
this.state = {
|
this.state = {
|
||||||
|
ctrlKeyDown: false,
|
||||||
shiftKeyDown: false,
|
shiftKeyDown: false,
|
||||||
|
prevShiftNoteIndex: -1,
|
||||||
selectedNoteKeys: []
|
selectedNoteKeys: []
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +268,7 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleNoteListKeyDown (e) {
|
handleNoteListKeyDown (e) {
|
||||||
if (e.metaKey || e.ctrlKey) return true
|
if (e.metaKey) return true
|
||||||
|
|
||||||
// A key
|
// A key
|
||||||
if (e.keyCode === 65 && !e.shiftKey) {
|
if (e.keyCode === 65 && !e.shiftKey) {
|
||||||
@@ -307,6 +308,8 @@ class NoteList extends React.Component {
|
|||||||
|
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
this.setState({ shiftKeyDown: true })
|
this.setState({ shiftKeyDown: true })
|
||||||
|
} else if (e.ctrlKey) {
|
||||||
|
this.setState({ ctrlKeyDown: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,6 +317,10 @@ class NoteList extends React.Component {
|
|||||||
if (!e.shiftKey) {
|
if (!e.shiftKey) {
|
||||||
this.setState({ shiftKeyDown: false })
|
this.setState({ shiftKeyDown: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!e.ctrlKey) {
|
||||||
|
this.setState({ ctrlKeyDown: false })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getNotes () {
|
getNotes () {
|
||||||
@@ -390,25 +397,65 @@ class NoteList extends React.Component {
|
|||||||
return pinnedNotes.concat(unpinnedNotes)
|
return pinnedNotes.concat(unpinnedNotes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNoteIndexByKey (noteKey) {
|
||||||
|
return this.notes.findIndex((note) => {
|
||||||
|
if (!note) return -1
|
||||||
|
|
||||||
|
return note.key === noteKey
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
handleNoteClick (e, uniqueKey) {
|
handleNoteClick (e, uniqueKey) {
|
||||||
const { router } = this.context
|
const { router } = this.context
|
||||||
const { location } = this.props
|
const { location } = this.props
|
||||||
let { selectedNoteKeys } = this.state
|
let { selectedNoteKeys, prevShiftNoteIndex } = this.state
|
||||||
const { shiftKeyDown } = this.state
|
const { ctrlKeyDown, shiftKeyDown } = this.state
|
||||||
|
const hasSelectedNoteKey = selectedNoteKeys.length > 0
|
||||||
|
|
||||||
if (shiftKeyDown && selectedNoteKeys.includes(uniqueKey)) {
|
if (ctrlKeyDown && selectedNoteKeys.includes(uniqueKey)) {
|
||||||
const newSelectedNoteKeys = selectedNoteKeys.filter((noteKey) => noteKey !== uniqueKey)
|
const newSelectedNoteKeys = selectedNoteKeys.filter((noteKey) => noteKey !== uniqueKey)
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedNoteKeys: newSelectedNoteKeys
|
selectedNoteKeys: newSelectedNoteKeys
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!shiftKeyDown) {
|
if (!ctrlKeyDown && !shiftKeyDown) {
|
||||||
selectedNoteKeys = []
|
selectedNoteKeys = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!shiftKeyDown) {
|
||||||
|
prevShiftNoteIndex = -1
|
||||||
|
}
|
||||||
|
|
||||||
selectedNoteKeys.push(uniqueKey)
|
selectedNoteKeys.push(uniqueKey)
|
||||||
|
|
||||||
|
if (shiftKeyDown && hasSelectedNoteKey) {
|
||||||
|
let firstShiftNoteIndex = this.getNoteIndexByKey(selectedNoteKeys[0])
|
||||||
|
// Shift selection can either start from first note in the exisiting selectedNoteKeys
|
||||||
|
// or previous first shift note index
|
||||||
|
firstShiftNoteIndex = firstShiftNoteIndex > prevShiftNoteIndex
|
||||||
|
? firstShiftNoteIndex : prevShiftNoteIndex
|
||||||
|
|
||||||
|
const lastShiftNoteIndex = this.getNoteIndexByKey(uniqueKey)
|
||||||
|
|
||||||
|
const startIndex = firstShiftNoteIndex < lastShiftNoteIndex
|
||||||
|
? firstShiftNoteIndex : lastShiftNoteIndex
|
||||||
|
const endIndex = firstShiftNoteIndex > lastShiftNoteIndex
|
||||||
|
? firstShiftNoteIndex : lastShiftNoteIndex
|
||||||
|
|
||||||
|
selectedNoteKeys = []
|
||||||
|
for (let i = startIndex; i <= endIndex; i++) {
|
||||||
|
selectedNoteKeys.push(this.notes[i].key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevShiftNoteIndex < 0) {
|
||||||
|
prevShiftNoteIndex = firstShiftNoteIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedNoteKeys
|
selectedNoteKeys,
|
||||||
|
prevShiftNoteIndex
|
||||||
})
|
})
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
@@ -519,7 +566,7 @@ class NoteList extends React.Component {
|
|||||||
click: this.cloneNote.bind(this)
|
click: this.cloneNote.bind(this)
|
||||||
}, {
|
}, {
|
||||||
label: copyNoteLink,
|
label: copyNoteLink,
|
||||||
click: this.copyNoteLink(note)
|
click: this.copyNoteLink.bind(this, note)
|
||||||
})
|
})
|
||||||
if (note.type === 'MARKDOWN_NOTE') {
|
if (note.type === 'MARKDOWN_NOTE') {
|
||||||
if (note.blog && note.blog.blogLink && note.blog.blogId) {
|
if (note.blog && note.blog.blogLink && note.blog.blogId) {
|
||||||
@@ -616,7 +663,6 @@ class NoteList extends React.Component {
|
|||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error('Cannot Delete note: ' + err)
|
console.error('Cannot Delete note: ' + err)
|
||||||
})
|
})
|
||||||
console.log('Notes were all deleted')
|
|
||||||
} else {
|
} else {
|
||||||
if (!confirmDeleteNote(confirmDeletion, false)) return
|
if (!confirmDeleteNote(confirmDeletion, false)) return
|
||||||
|
|
||||||
@@ -636,7 +682,6 @@ class NoteList extends React.Component {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
||||||
console.log('Notes went to trash')
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error('Notes could not go to trash: ' + err)
|
console.error('Notes could not go to trash: ' + err)
|
||||||
@@ -996,6 +1041,7 @@ class NoteList extends React.Component {
|
|||||||
folderName={this.getNoteFolder(note).name}
|
folderName={this.getNoteFolder(note).name}
|
||||||
storageName={this.getNoteStorage(note).name}
|
storageName={this.getNoteStorage(note).name}
|
||||||
viewType={viewType}
|
viewType={viewType}
|
||||||
|
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
text-align center
|
text-align center
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.top-menu-label
|
.top-menu-label
|
||||||
margin-left 5px
|
margin-left 5px
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
@@ -122,3 +122,8 @@ body[data-theme="monokai"]
|
|||||||
.root, .root--folded
|
.root, .root--folded
|
||||||
background-color $ui-monokai-backgroundColor
|
background-color $ui-monokai-backgroundColor
|
||||||
border-right 1px solid $ui-monokai-borderColor
|
border-right 1px solid $ui-monokai-borderColor
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.root, .root--folded
|
||||||
|
background-color $ui-dracula-backgroundColor
|
||||||
|
border-right 1px solid $ui-dracula-borderColor
|
||||||
@@ -274,7 +274,7 @@ class StorageItem extends React.Component {
|
|||||||
const { folderNoteMap, trashedSet } = data
|
const { folderNoteMap, trashedSet } = data
|
||||||
const SortableStorageItemChild = SortableElement(StorageItemChild)
|
const SortableStorageItemChild = SortableElement(StorageItemChild)
|
||||||
const folderList = storage.folders.map((folder, index) => {
|
const folderList = storage.folders.map((folder, index) => {
|
||||||
let folderRegex = new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + escapeStringRegexp(path.sep) + 'folders' + escapeStringRegexp(path.sep) + folder.key)
|
const folderRegex = new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + escapeStringRegexp(path.sep) + 'folders' + escapeStringRegexp(path.sep) + folder.key)
|
||||||
const isActive = !!(location.pathname.match(folderRegex))
|
const isActive = !!(location.pathname.match(folderRegex))
|
||||||
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ import TagButton from './TagButton'
|
|||||||
import {SortableContainer} from 'react-sortable-hoc'
|
import {SortableContainer} from 'react-sortable-hoc'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import context from 'browser/lib/context'
|
import context from 'browser/lib/context'
|
||||||
|
import { remote } from 'electron'
|
||||||
|
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||||
|
|
||||||
|
function matchActiveTags (tags, activeTags) {
|
||||||
|
return _.every(activeTags, v => tags.indexOf(v) >= 0)
|
||||||
|
}
|
||||||
|
|
||||||
class SideNav extends React.Component {
|
class SideNav extends React.Component {
|
||||||
// TODO: should not use electron stuff v0.7
|
// TODO: should not use electron stuff v0.7
|
||||||
@@ -30,6 +36,52 @@ class SideNav extends React.Component {
|
|||||||
EventEmitter.off('side:preferences', this.handleMenuButtonClick)
|
EventEmitter.off('side:preferences', this.handleMenuButtonClick)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteTag (tag) {
|
||||||
|
const selectedButton = remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
ype: 'warning',
|
||||||
|
message: i18n.__('Confirm tag deletion'),
|
||||||
|
detail: i18n.__('This will permanently remove this tag.'),
|
||||||
|
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||||
|
})
|
||||||
|
|
||||||
|
if (selectedButton === 0) {
|
||||||
|
const { data, dispatch, location, params } = this.props
|
||||||
|
|
||||||
|
const notes = data.noteMap
|
||||||
|
.map(note => note)
|
||||||
|
.filter(note => note.tags.indexOf(tag) !== -1)
|
||||||
|
.map(note => {
|
||||||
|
note = Object.assign({}, note)
|
||||||
|
note.tags = note.tags.slice()
|
||||||
|
|
||||||
|
note.tags.splice(note.tags.indexOf(tag), 1)
|
||||||
|
|
||||||
|
return note
|
||||||
|
})
|
||||||
|
|
||||||
|
Promise
|
||||||
|
.all(notes.map(note => dataApi.updateNote(note.storage, note.key, note)))
|
||||||
|
.then(updatedNotes => {
|
||||||
|
updatedNotes.forEach(note => {
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_NOTE',
|
||||||
|
note
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (location.pathname.match('/tags')) {
|
||||||
|
const tags = params.tagname.split(' ')
|
||||||
|
const index = tags.indexOf(tag)
|
||||||
|
if (index !== -1) {
|
||||||
|
tags.splice(index, 1)
|
||||||
|
|
||||||
|
this.context.router.push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleMenuButtonClick (e) {
|
handleMenuButtonClick (e) {
|
||||||
openModal(PreferencesModal)
|
openModal(PreferencesModal)
|
||||||
}
|
}
|
||||||
@@ -44,6 +96,17 @@ class SideNav extends React.Component {
|
|||||||
router.push('/starred')
|
router.push('/starred')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleTagContextMenu (e, tag) {
|
||||||
|
const menu = []
|
||||||
|
|
||||||
|
menu.push({
|
||||||
|
label: i18n.__('Delete Tag'),
|
||||||
|
click: this.deleteTag.bind(this, tag)
|
||||||
|
})
|
||||||
|
|
||||||
|
context.popup(menu)
|
||||||
|
}
|
||||||
|
|
||||||
handleToggleButtonClick (e) {
|
handleToggleButtonClick (e) {
|
||||||
const { dispatch, config } = this.props
|
const { dispatch, config } = this.props
|
||||||
|
|
||||||
@@ -144,12 +207,20 @@ class SideNav extends React.Component {
|
|||||||
|
|
||||||
tagListComponent () {
|
tagListComponent () {
|
||||||
const { data, location, config } = this.props
|
const { data, location, config } = this.props
|
||||||
const relatedTags = this.getRelatedTags(this.getActiveTags(location.pathname), data.noteMap)
|
const activeTags = this.getActiveTags(location.pathname)
|
||||||
|
const relatedTags = this.getRelatedTags(activeTags, data.noteMap)
|
||||||
let tagList = _.sortBy(data.tagNoteMap.map(
|
let tagList = _.sortBy(data.tagNoteMap.map(
|
||||||
(tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
|
(tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
|
||||||
), ['name']).filter(
|
).filter(
|
||||||
tag => tag.size > 0
|
tag => tag.size > 0
|
||||||
)
|
), ['name'])
|
||||||
|
if (config.ui.enableLiveNoteCounts && activeTags.length !== 0) {
|
||||||
|
const notesTags = data.noteMap.map(note => note.tags)
|
||||||
|
tagList = tagList.map(tag => {
|
||||||
|
tag.size = notesTags.filter(tags => tags.includes(tag.name) && matchActiveTags(tags, activeTags)).length
|
||||||
|
return tag
|
||||||
|
})
|
||||||
|
}
|
||||||
if (config.sortTagsBy === 'COUNTER') {
|
if (config.sortTagsBy === 'COUNTER') {
|
||||||
tagList = _.sortBy(tagList, item => (0 - item.size))
|
tagList = _.sortBy(tagList, item => (0 - item.size))
|
||||||
}
|
}
|
||||||
@@ -165,6 +236,7 @@ class SideNav extends React.Component {
|
|||||||
name={tag.name}
|
name={tag.name}
|
||||||
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
|
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
|
||||||
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
|
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
|
||||||
|
handleContextMenu={this.handleTagContextMenu.bind(this)}
|
||||||
isActive={this.getTagActive(location.pathname, tag.name)}
|
isActive={this.getTagActive(location.pathname, tag.name)}
|
||||||
isRelated={tag.related}
|
isRelated={tag.related}
|
||||||
key={tag.name}
|
key={tag.name}
|
||||||
@@ -198,7 +270,7 @@ class SideNav extends React.Component {
|
|||||||
const tags = pathSegments[pathSegments.length - 1]
|
const tags = pathSegments[pathSegments.length - 1]
|
||||||
return (tags === 'alltags')
|
return (tags === 'alltags')
|
||||||
? []
|
? []
|
||||||
: tags.split(' ').map(tag => decodeURIComponent(tag))
|
: decodeURIComponent(tags).split(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClickTagListItem (name) {
|
handleClickTagListItem (name) {
|
||||||
@@ -230,7 +302,7 @@ class SideNav extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
listOfTags.push(tag)
|
listOfTags.push(tag)
|
||||||
}
|
}
|
||||||
router.push(`/tags/${listOfTags.map(tag => encodeURIComponent(tag)).join(' ')}`)
|
router.push(`/tags/${encodeURIComponent(listOfTags.join(' '))}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
emptyTrash (entries) {
|
emptyTrash (entries) {
|
||||||
@@ -238,6 +310,8 @@ class SideNav extends React.Component {
|
|||||||
const deletionPromises = entries.map((note) => {
|
const deletionPromises = entries.map((note) => {
|
||||||
return dataApi.deleteNote(note.storage, note.key)
|
return dataApi.deleteNote(note.storage, note.key)
|
||||||
})
|
})
|
||||||
|
const { confirmDeletion } = this.props.config.ui
|
||||||
|
if (!confirmDeleteNote(confirmDeletion, true)) return
|
||||||
Promise.all(deletionPromises)
|
Promise.all(deletionPromises)
|
||||||
.then((arrayOfStorageAndNoteKeys) => {
|
.then((arrayOfStorageAndNoteKeys) => {
|
||||||
arrayOfStorageAndNoteKeys.forEach(({ storageKey, noteKey }) => {
|
arrayOfStorageAndNoteKeys.forEach(({ storageKey, noteKey }) => {
|
||||||
@@ -247,7 +321,6 @@ class SideNav extends React.Component {
|
|||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error('Cannot Delete note: ' + err)
|
console.error('Cannot Delete note: ' + err)
|
||||||
})
|
})
|
||||||
console.log('Trash emptied')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFilterButtonContextMenu (event) {
|
handleFilterButtonContextMenu (event) {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
color $ui-active-color
|
color $ui-active-color
|
||||||
span
|
span
|
||||||
margin-left 5px
|
margin-left 5px
|
||||||
|
|
||||||
.update
|
.update
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
height 24px
|
height 24px
|
||||||
@@ -47,6 +47,14 @@
|
|||||||
.update-icon
|
.update-icon
|
||||||
color $brand-color
|
color $brand-color
|
||||||
|
|
||||||
|
body[data-theme="default"]
|
||||||
|
.zoom
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.zoom
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
@@ -80,3 +88,14 @@ body[data-theme="monokai"]
|
|||||||
color $ui-monokai-active-color
|
color $ui-monokai-active-color
|
||||||
&:active
|
&:active
|
||||||
color $ui-monokai-active-color
|
color $ui-monokai-active-color
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
navButtonColor()
|
||||||
|
.zoom
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
&:hover
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-dracula-active-color
|
||||||
|
&:active
|
||||||
|
color $ui-dracula-active-color
|
||||||
@@ -5,6 +5,7 @@ import styles from './StatusBar.styl'
|
|||||||
import ZoomManager from 'browser/main/lib/ZoomManager'
|
import ZoomManager from 'browser/main/lib/ZoomManager'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import context from 'browser/lib/context'
|
import context from 'browser/lib/context'
|
||||||
|
import EventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote, ipcRenderer } = electron
|
const { remote, ipcRenderer } = electron
|
||||||
@@ -13,6 +14,26 @@ const { dialog } = remote
|
|||||||
const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
|
const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
|
||||||
|
|
||||||
class StatusBar extends React.Component {
|
class StatusBar extends React.Component {
|
||||||
|
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.handleZoomInMenuItem = this.handleZoomInMenuItem.bind(this)
|
||||||
|
this.handleZoomOutMenuItem = this.handleZoomOutMenuItem.bind(this)
|
||||||
|
this.handleZoomResetMenuItem = this.handleZoomResetMenuItem.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
EventEmitter.on('status:zoomin', this.handleZoomInMenuItem)
|
||||||
|
EventEmitter.on('status:zoomout', this.handleZoomOutMenuItem)
|
||||||
|
EventEmitter.on('status:zoomreset', this.handleZoomResetMenuItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
EventEmitter.off('status:zoomin', this.handleZoomInMenuItem)
|
||||||
|
EventEmitter.off('status:zoomout', this.handleZoomOutMenuItem)
|
||||||
|
EventEmitter.off('status:zoomreset', this.handleZoomResetMenuItem)
|
||||||
|
}
|
||||||
|
|
||||||
updateApp () {
|
updateApp () {
|
||||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
@@ -48,6 +69,20 @@ class StatusBar extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleZoomInMenuItem () {
|
||||||
|
const zoomFactor = ZoomManager.getZoom() + 0.1
|
||||||
|
this.handleZoomMenuItemClick(zoomFactor)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleZoomOutMenuItem () {
|
||||||
|
const zoomFactor = ZoomManager.getZoom() - 0.1
|
||||||
|
this.handleZoomMenuItemClick(zoomFactor)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleZoomResetMenuItem () {
|
||||||
|
this.handleZoomMenuItemClick(1.0)
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { config, status } = this.context
|
const { config, status } = this.context
|
||||||
|
|
||||||
|
|||||||
@@ -256,3 +256,25 @@ body[data-theme="monokai"]
|
|||||||
input
|
input
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.root, .root--expanded
|
||||||
|
background-color $ui-dracula-noteList-backgroundColor
|
||||||
|
|
||||||
|
.control
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
|
.control-search
|
||||||
|
background-color $ui-dracula-noteList-backgroundColor
|
||||||
|
|
||||||
|
.control-search-icon
|
||||||
|
absolute top bottom left
|
||||||
|
line-height 32px
|
||||||
|
width 35px
|
||||||
|
color $ui-dracula-inactive-text-color
|
||||||
|
background-color $ui-dracula-noteList-backgroundColor
|
||||||
|
|
||||||
|
.control-search-input
|
||||||
|
background-color $ui-dracula-noteList-backgroundColor
|
||||||
|
input
|
||||||
|
background-color $ui-dracula-noteList-backgroundColor
|
||||||
|
color $ui-dracula-text-color
|
||||||
@@ -18,6 +18,9 @@ body
|
|||||||
::-webkit-scrollbar
|
::-webkit-scrollbar
|
||||||
width 12px
|
width 12px
|
||||||
|
|
||||||
|
::-webkit-scrollbar-corner
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb
|
::-webkit-scrollbar-thumb
|
||||||
background-color rgba(0, 0, 0, 0.15)
|
background-color rgba(0, 0, 0, 0.15)
|
||||||
|
|
||||||
@@ -162,6 +165,15 @@ body[data-theme="monokai"]
|
|||||||
.sortableItemHelper
|
.sortableItemHelper
|
||||||
color: $ui-monokai-text-color
|
color: $ui-monokai-text-color
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
::-webkit-scrollbar-thumb
|
||||||
|
background-color rgba(0, 0, 0, 0.3)
|
||||||
|
.ModalBase
|
||||||
|
.modalBack
|
||||||
|
background-color $ui-dracula-backgroundColor
|
||||||
|
.sortableItemHelper
|
||||||
|
color: $ui-dracula-text-color
|
||||||
|
|
||||||
body[data-theme="default"]
|
body[data-theme="default"]
|
||||||
.SideNav ::-webkit-scrollbar-thumb
|
.SideNav ::-webkit-scrollbar-thumb
|
||||||
background-color rgba(255, 255, 255, 0.3)
|
background-color rgba(255, 255, 255, 0.3)
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ function initAwsMobileAnalytics () {
|
|||||||
if (getSendEventCond()) return
|
if (getSendEventCond()) return
|
||||||
AWS.config.credentials.get((err) => {
|
AWS.config.credentials.get((err) => {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
console.log('Cognito Identity ID: ' + AWS.config.credentials.identityId)
|
|
||||||
recordDynamicCustomEvent('APP_STARTED')
|
recordDynamicCustomEvent('APP_STARTED')
|
||||||
recordStaticCustomEvent()
|
recordStaticCustomEvent()
|
||||||
}
|
}
|
||||||
@@ -58,7 +57,7 @@ function recordDynamicCustomEvent (type, options = {}) {
|
|||||||
mobileAnalyticsClient.recordEvent(type, options)
|
mobileAnalyticsClient.recordEvent(type, options)
|
||||||
} catch (analyticsError) {
|
} catch (analyticsError) {
|
||||||
if (analyticsError instanceof ReferenceError) {
|
if (analyticsError instanceof ReferenceError) {
|
||||||
console.log(analyticsError.name + ': ' + analyticsError.message)
|
console.error(analyticsError.name + ': ' + analyticsError.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +70,7 @@ function recordStaticCustomEvent () {
|
|||||||
})
|
})
|
||||||
} catch (analyticsError) {
|
} catch (analyticsError) {
|
||||||
if (analyticsError instanceof ReferenceError) {
|
if (analyticsError instanceof ReferenceError) {
|
||||||
console.log(analyticsError.name + ': ' + analyticsError.message)
|
console.error(analyticsError.name + ': ' + analyticsError.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ export const DEFAULT_CONFIG = {
|
|||||||
amaEnabled: true,
|
amaEnabled: true,
|
||||||
hotkey: {
|
hotkey: {
|
||||||
toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
|
toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
|
||||||
toggleMode: OSX ? 'Command + M' : 'Ctrl + M'
|
toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M',
|
||||||
|
deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace'
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
language: 'en',
|
language: 'en',
|
||||||
@@ -43,11 +44,15 @@ export const DEFAULT_CONFIG = {
|
|||||||
enableRulers: false,
|
enableRulers: false,
|
||||||
rulers: [80, 120],
|
rulers: [80, 120],
|
||||||
displayLineNumbers: true,
|
displayLineNumbers: true,
|
||||||
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
|
switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK'
|
||||||
|
delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE'
|
||||||
scrollPastEnd: false,
|
scrollPastEnd: false,
|
||||||
type: 'SPLIT',
|
type: 'SPLIT', // 'SPLIT', 'EDITOR_PREVIEW'
|
||||||
fetchUrlTitle: true,
|
fetchUrlTitle: true,
|
||||||
enableTableEditor: false
|
enableTableEditor: false,
|
||||||
|
enableFrontMatterTitle: true,
|
||||||
|
frontMatterTitleField: 'title',
|
||||||
|
spellcheck: false
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
fontSize: '14',
|
fontSize: '14',
|
||||||
@@ -60,12 +65,14 @@ export const DEFAULT_CONFIG = {
|
|||||||
latexBlockClose: '$$',
|
latexBlockClose: '$$',
|
||||||
plantUMLServerAddress: 'http://www.plantuml.com/plantuml',
|
plantUMLServerAddress: 'http://www.plantuml.com/plantuml',
|
||||||
scrollPastEnd: false,
|
scrollPastEnd: false,
|
||||||
|
scrollSync: true,
|
||||||
smartQuotes: true,
|
smartQuotes: true,
|
||||||
breaks: true,
|
breaks: true,
|
||||||
smartArrows: false,
|
smartArrows: false,
|
||||||
allowCustomCSS: false,
|
allowCustomCSS: false,
|
||||||
customCSS: '',
|
customCSS: '',
|
||||||
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'
|
sanitize: 'STRICT', // 'STRICT', 'ALLOW_STYLES', 'NONE'
|
||||||
|
lineThroughCheckbox: true
|
||||||
},
|
},
|
||||||
blog: {
|
blog: {
|
||||||
type: 'wordpress', // Available value: wordpress, add more types in the future plz
|
type: 'wordpress', // Available value: wordpress, add more types in the future plz
|
||||||
@@ -147,6 +154,8 @@ function set (updates) {
|
|||||||
document.body.setAttribute('data-theme', 'solarized-dark')
|
document.body.setAttribute('data-theme', 'solarized-dark')
|
||||||
} else if (newConfig.ui.theme === 'monokai') {
|
} else if (newConfig.ui.theme === 'monokai') {
|
||||||
document.body.setAttribute('data-theme', 'monokai')
|
document.body.setAttribute('data-theme', 'monokai')
|
||||||
|
} else if (newConfig.ui.theme === 'dracula') {
|
||||||
|
document.body.setAttribute('data-theme', 'dracula')
|
||||||
} else {
|
} else {
|
||||||
document.body.setAttribute('data-theme', 'default')
|
document.body.setAttribute('data-theme', 'default')
|
||||||
}
|
}
|
||||||
@@ -195,6 +204,7 @@ function rewriteHotkey (config) {
|
|||||||
const keys = [...Object.keys(config.hotkey)]
|
const keys = [...Object.keys(config.hotkey)]
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
config.hotkey[key] = config.hotkey[key].replace(/Cmd/g, 'Command')
|
config.hotkey[key] = config.hotkey[key].replace(/Cmd/g, 'Command')
|
||||||
|
config.hotkey[key] = config.hotkey[key].replace(/Opt\s/g, 'Option ')
|
||||||
})
|
})
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,118 @@ import i18n from 'browser/lib/i18n'
|
|||||||
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
|
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
|
||||||
const DESTINATION_FOLDER = 'attachments'
|
const DESTINATION_FOLDER = 'attachments'
|
||||||
const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp(path.win32.sep)
|
const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp(path.win32.sep)
|
||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* Create a Image element to get the real size of image.
|
||||||
|
* @param {File} file the File object dropped.
|
||||||
|
* @returns {Promise<Image>} Image element created
|
||||||
|
*/
|
||||||
|
function getImage (file) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const reader = new FileReader()
|
||||||
|
const img = new Image()
|
||||||
|
img.onload = () => resolve(img)
|
||||||
|
reader.onload = e => {
|
||||||
|
img.src = e.target.result
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* Get the orientation info from iamges's EXIF data.
|
||||||
|
* case 1: The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
|
||||||
|
* case 2: The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
|
||||||
|
* case 3: The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
|
||||||
|
* case 4: The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
|
||||||
|
* case 5: The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
|
||||||
|
* case 6: The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
|
||||||
|
* case 7: The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
|
||||||
|
* case 8: The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
|
||||||
|
* Other: reserved
|
||||||
|
* ref: http://sylvana.net/jpegcrop/exif_orientation.html
|
||||||
|
* @param {File} file the File object dropped.
|
||||||
|
* @returns {Promise<Number>} Orientation info
|
||||||
|
*/
|
||||||
|
function getOrientation (file) {
|
||||||
|
const getData = arrayBuffer => {
|
||||||
|
const view = new DataView(arrayBuffer)
|
||||||
|
|
||||||
|
// Not start with SOI(Start of image) Marker return fail value
|
||||||
|
if (view.getUint16(0, false) !== 0xFFD8) return -2
|
||||||
|
const length = view.byteLength
|
||||||
|
let offset = 2
|
||||||
|
while (offset < length) {
|
||||||
|
const marker = view.getUint16(offset, false)
|
||||||
|
offset += 2
|
||||||
|
// Loop and seed for APP1 Marker
|
||||||
|
if (marker === 0xFFE1) {
|
||||||
|
// return fail value if it isn't EXIF data
|
||||||
|
if (view.getUint32(offset += 2, false) !== 0x45786966) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
// Read TIFF header,
|
||||||
|
// First 2bytes defines byte align of TIFF data.
|
||||||
|
// If it is 0x4949="II", it means "Intel" type byte align.
|
||||||
|
// If it is 0x4d4d="MM", it means "Motorola" type byte align
|
||||||
|
const little = view.getUint16(offset += 6, false) === 0x4949
|
||||||
|
offset += view.getUint32(offset + 4, little)
|
||||||
|
const tags = view.getUint16(offset, little) // Get TAG number
|
||||||
|
offset += 2
|
||||||
|
for (let i = 0; i < tags; i++) {
|
||||||
|
// Loop to find Orientation TAG and return the value
|
||||||
|
if (view.getUint16(offset + (i * 12), little) === 0x0112) {
|
||||||
|
return view.getUint16(offset + (i * 12) + 8, little)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ((marker & 0xFF00) !== 0xFF00) { // If not start with 0xFF, not a Marker
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
offset += view.getUint16(offset, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = event => resolve(getData(event.target.result))
|
||||||
|
reader.readAsArrayBuffer(file.slice(0, 64 * 1024))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* Rotate image file to correct direction.
|
||||||
|
* Create a canvas and draw the image with correct direction, then export to base64 format.
|
||||||
|
* @param {*} file the File object dropped.
|
||||||
|
* @return {String} Base64 encoded image.
|
||||||
|
*/
|
||||||
|
function fixRotate (file) {
|
||||||
|
return Promise.all([getImage(file), getOrientation(file)])
|
||||||
|
.then(([img, orientation]) => {
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
if (orientation > 4 && orientation < 9) {
|
||||||
|
canvas.width = img.height
|
||||||
|
canvas.height = img.width
|
||||||
|
} else {
|
||||||
|
canvas.width = img.width
|
||||||
|
canvas.height = img.height
|
||||||
|
}
|
||||||
|
switch (orientation) {
|
||||||
|
case 2: ctx.transform(-1, 0, 0, 1, img.width, 0); break
|
||||||
|
case 3: ctx.transform(-1, 0, 0, -1, img.width, img.height); break
|
||||||
|
case 4: ctx.transform(1, 0, 0, -1, 0, img.height); break
|
||||||
|
case 5: ctx.transform(0, 1, 1, 0, 0, 0); break
|
||||||
|
case 6: ctx.transform(0, 1, -1, 0, img.height, 0); break
|
||||||
|
case 7: ctx.transform(0, -1, -1, 0, img.height, img.width); break
|
||||||
|
case 8: ctx.transform(0, -1, 1, 0, 0, img.width); break
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
ctx.drawImage(img, 0, 0)
|
||||||
|
return canvas.toDataURL()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
@@ -38,26 +150,34 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(sourceFilePath)) {
|
const isBase64 = typeof sourceFilePath === 'object' && sourceFilePath.type === 'base64'
|
||||||
reject('source file does not exist')
|
if (!fs.existsSync(sourceFilePath) && !isBase64) {
|
||||||
|
return reject('source file does not exist')
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetStorage = findStorage.findStorage(storageKey)
|
const targetStorage = findStorage.findStorage(storageKey)
|
||||||
|
|
||||||
const inputFileStream = fs.createReadStream(sourceFilePath)
|
|
||||||
let destinationName
|
let destinationName
|
||||||
if (useRandomName) {
|
if (useRandomName) {
|
||||||
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}`
|
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath.sourceFilePath || sourceFilePath)}`
|
||||||
} else {
|
} else {
|
||||||
destinationName = path.basename(sourceFilePath)
|
destinationName = path.basename(sourceFilePath.sourceFilePath || sourceFilePath)
|
||||||
}
|
}
|
||||||
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
||||||
createAttachmentDestinationFolder(targetStorage.path, noteKey)
|
createAttachmentDestinationFolder(targetStorage.path, noteKey)
|
||||||
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
|
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
|
||||||
inputFileStream.pipe(outputFile)
|
|
||||||
inputFileStream.on('end', () => {
|
if (isBase64) {
|
||||||
resolve(destinationName)
|
const base64Data = sourceFilePath.data.replace(/^data:image\/\w+;base64,/, '')
|
||||||
})
|
const dataBuffer = new Buffer(base64Data, 'base64')
|
||||||
|
outputFile.write(dataBuffer, () => {
|
||||||
|
resolve(destinationName)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const inputFileStream = fs.createReadStream(sourceFilePath)
|
||||||
|
inputFileStream.pipe(outputFile)
|
||||||
|
inputFileStream.on('end', () => {
|
||||||
|
resolve(destinationName)
|
||||||
|
})
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return reject(e)
|
return reject(e)
|
||||||
}
|
}
|
||||||
@@ -137,10 +257,17 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
|
|||||||
const filePath = file.path
|
const filePath = file.path
|
||||||
const originalFileName = path.basename(filePath)
|
const originalFileName = path.basename(filePath)
|
||||||
const fileType = file['type']
|
const fileType = file['type']
|
||||||
|
const isImage = fileType.startsWith('image')
|
||||||
copyAttachment(filePath, storageKey, noteKey).then((fileName) => {
|
let promise
|
||||||
const showPreview = fileType.startsWith('image')
|
if (isImage) {
|
||||||
const imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), showPreview)
|
promise = fixRotate(file).then(base64data => {
|
||||||
|
return copyAttachment({type: 'base64', data: base64data, sourceFilePath: filePath}, storageKey, noteKey)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
promise = copyAttachment(filePath, storageKey, noteKey)
|
||||||
|
}
|
||||||
|
promise.then((fileName) => {
|
||||||
|
const imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), isImage)
|
||||||
codeEditor.insertAttachmentMd(imageMd)
|
codeEditor.insertAttachmentMd(imageMd)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -402,7 +529,6 @@ function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
|
|||||||
return modifiedLinkText
|
return modifiedLinkText
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
console.log('One if the parameters was null -> Do nothing..')
|
|
||||||
return Promise.resolve(linkText)
|
return Promise.resolve(linkText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ function renameStorage (key, name) {
|
|||||||
cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||||
if (!_.isArray(cachedStorageList)) throw new Error('invalid storages')
|
if (!_.isArray(cachedStorageList)) throw new Error('invalid storages')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('error got')
|
|
||||||
console.error(err)
|
console.error(err)
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,13 +31,9 @@ function resolveStorageData (storageCache) {
|
|||||||
|
|
||||||
const version = parseInt(storage.version, 10)
|
const version = parseInt(storage.version, 10)
|
||||||
if (version >= 1) {
|
if (version >= 1) {
|
||||||
if (version > 1) {
|
|
||||||
console.log('The repository version is newer than one of current app.')
|
|
||||||
}
|
|
||||||
return Promise.resolve(storage)
|
return Promise.resolve(storage)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Transform Legacy storage', storage.path)
|
|
||||||
return migrateFromV6Storage(storage.path)
|
return migrateFromV6Storage(storage.path)
|
||||||
.then(() => storage)
|
.then(() => storage)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ function resolveStorageNotes (storage) {
|
|||||||
notePathList = sander.readdirSync(notesDirPath)
|
notePathList = sander.readdirSync(notesDirPath)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
console.log(notesDirPath, ' doesn\'t exist.')
|
console.error(notesDirPath, ' doesn\'t exist.')
|
||||||
sander.mkdirSync(notesDirPath)
|
sander.mkdirSync(notesDirPath)
|
||||||
} else {
|
} else {
|
||||||
console.warn('Failed to find note dir', notesDirPath, err)
|
console.warn('Failed to find note dir', notesDirPath, err)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ function toggleStorage (key, isOpen) {
|
|||||||
cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||||
if (!_.isArray(cachedStorageList)) throw new Error('invalid storages')
|
if (!_.isArray(cachedStorageList)) throw new Error('invalid storages')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('error got')
|
|
||||||
console.error(err)
|
console.error(err)
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ function once (name, listener) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function emit (name, ...args) {
|
function emit (name, ...args) {
|
||||||
console.log(name)
|
|
||||||
remote.getCurrentWindow().webContents.send(name, ...args)
|
remote.getCurrentWindow().webContents.send(name, ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,14 +14,13 @@ nodeIpc.connectTo(
|
|||||||
path.join(app.getPath('userData'), 'boostnote.service'),
|
path.join(app.getPath('userData'), 'boostnote.service'),
|
||||||
function () {
|
function () {
|
||||||
nodeIpc.of.node.on('error', function (err) {
|
nodeIpc.of.node.on('error', function (err) {
|
||||||
console.log(err)
|
console.error(err)
|
||||||
})
|
})
|
||||||
nodeIpc.of.node.on('connect', function () {
|
nodeIpc.of.node.on('connect', function () {
|
||||||
console.log('Connected successfully')
|
|
||||||
ipcRenderer.send('config-renew', {config: ConfigManager.get()})
|
ipcRenderer.send('config-renew', {config: ConfigManager.get()})
|
||||||
})
|
})
|
||||||
nodeIpc.of.node.on('disconnect', function () {
|
nodeIpc.of.node.on('disconnect', function () {
|
||||||
console.log('disconnected')
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,5 +3,8 @@ import ee from 'browser/main/lib/eventEmitter'
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
'toggleMode': () => {
|
'toggleMode': () => {
|
||||||
ee.emit('topbar:togglemodebutton')
|
ee.emit('topbar:togglemodebutton')
|
||||||
|
},
|
||||||
|
'deleteNote': () => {
|
||||||
|
ee.emit('hotkey:deletenote')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,3 +128,29 @@ body[data-theme="monokai"]
|
|||||||
|
|
||||||
.control-confirmButton
|
.control-confirmButton
|
||||||
colorMonokaiPrimaryButton()
|
colorMonokaiPrimaryButton()
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.root
|
||||||
|
modalDracula()
|
||||||
|
width 500px
|
||||||
|
height 270px
|
||||||
|
overflow hidden
|
||||||
|
position relative
|
||||||
|
|
||||||
|
.header
|
||||||
|
background-color transparent
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
|
.control-folder-label
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
|
.control-folder-input
|
||||||
|
border 1px solid $ui-input--create-folder-modal
|
||||||
|
color white
|
||||||
|
|
||||||
|
.description
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.control-confirmButton
|
||||||
|
colorDraculaPrimaryButton()
|
||||||
@@ -21,8 +21,8 @@ class NewNoteModal extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleMarkdownNoteButtonClick (e) {
|
handleMarkdownNoteButtonClick (e) {
|
||||||
const { storage, folder, dispatch, location } = this.props
|
const { storage, folder, dispatch, location, params, config } = this.props
|
||||||
createMarkdownNote(storage, folder, dispatch, location).then(() => {
|
createMarkdownNote(storage, folder, dispatch, location, params, config).then(() => {
|
||||||
setTimeout(this.props.close, 200)
|
setTimeout(this.props.close, 200)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -35,8 +35,8 @@ class NewNoteModal extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSnippetNoteButtonClick (e) {
|
handleSnippetNoteButtonClick (e) {
|
||||||
const { storage, folder, dispatch, location, config } = this.props
|
const { storage, folder, dispatch, location, params, config } = this.props
|
||||||
createSnippetNote(storage, folder, dispatch, location, config).then(() => {
|
createSnippetNote(storage, folder, dispatch, location, params, config).then(() => {
|
||||||
setTimeout(this.props.close, 200)
|
setTimeout(this.props.close, 200)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,3 +97,20 @@ body[data-theme="monokai"]
|
|||||||
|
|
||||||
.description
|
.description
|
||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.root
|
||||||
|
background-color transparent
|
||||||
|
|
||||||
|
.header
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
|
.control-button
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
background-color transparent
|
||||||
|
&:focus
|
||||||
|
colorDraculaPrimaryButton()
|
||||||
|
|
||||||
|
.description
|
||||||
|
color $ui-dracula-text-color
|
||||||
@@ -43,7 +43,7 @@ class Blog extends React.Component {
|
|||||||
this.handleSettingError = (err) => {
|
this.handleSettingError = (err) => {
|
||||||
this.setState({BlogAlert: {
|
this.setState({BlogAlert: {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: err.message != null ? err.message : i18n.__('Error occurs!')
|
message: err.message != null ? err.message : i18n.__('An error occurred!')
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
this.oldBlog = this.state.config.blog
|
this.oldBlog = this.state.config.blog
|
||||||
@@ -70,7 +70,7 @@ class Blog extends React.Component {
|
|||||||
this.props.haveToSave({
|
this.props.haveToSave({
|
||||||
tab: 'Blog',
|
tab: 'Blog',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: i18n.__('You have to save!')
|
message: i18n.__('Unsaved Changes!')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,9 +68,9 @@
|
|||||||
:global
|
:global
|
||||||
.alert
|
.alert
|
||||||
display inline-block
|
display inline-block
|
||||||
position absolute
|
position fixed
|
||||||
top 60px
|
top 130px
|
||||||
right 15px
|
right 100px
|
||||||
font-size 14px
|
font-size 14px
|
||||||
.success
|
.success
|
||||||
color #1EC38B
|
color #1EC38B
|
||||||
@@ -127,7 +127,7 @@ colorDarkControl()
|
|||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
background-color $ui-dark-backgroundColor
|
background-color $ui-dark-backgroundColor
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
colorSolarizedDarkControl()
|
colorSolarizedDarkControl()
|
||||||
border none
|
border none
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
@@ -138,6 +138,10 @@ colorMonokaiControl()
|
|||||||
background-color $ui-monokai-button-backgroundColor
|
background-color $ui-monokai-button-backgroundColor
|
||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
colorDraculaControl()
|
||||||
|
border none
|
||||||
|
background-color $ui-dracula-button-backgroundColor
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
@@ -165,27 +169,27 @@ body[data-theme="dark"]
|
|||||||
.group-section-control
|
.group-section-control
|
||||||
select, .group-section-control-input
|
select, .group-section-control-input
|
||||||
colorDarkControl()
|
colorDarkControl()
|
||||||
|
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
body[data-theme="solarized-dark"]
|
||||||
.root
|
.root
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
.group-header
|
.group-header
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
border-color $ui-solarized-dark-borderColor
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
|
||||||
.group-header2
|
.group-header2
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
.group-section-control-input
|
.group-section-control-input
|
||||||
border-color $ui-solarized-dark-borderColor
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
|
||||||
.group-control
|
.group-control
|
||||||
border-color $ui-solarized-dark-borderColor
|
border-color $ui-solarized-dark-borderColor
|
||||||
.group-control-leftButton
|
.group-control-leftButton
|
||||||
colorDarkDefaultButton()
|
colorDarkDefaultButton()
|
||||||
border-color $ui-solarized-dark-borderColor
|
border-color $ui-solarized-dark-borderColor
|
||||||
.group-control-rightButton
|
.group-control-rightButton
|
||||||
colorSolarizedDarkPrimaryButton()
|
colorSolarizedDarkPrimaryButton()
|
||||||
.group-hint
|
.group-hint
|
||||||
@@ -200,19 +204,19 @@ body[data-theme="monokai"]
|
|||||||
|
|
||||||
.group-header
|
.group-header
|
||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
border-color $ui-monokai-borderColor
|
border-color $ui-monokai-borderColor
|
||||||
|
|
||||||
.group-header2
|
.group-header2
|
||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
.group-section-control-input
|
.group-section-control-input
|
||||||
border-color $ui-monokai-borderColor
|
border-color $ui-monokai-borderColor
|
||||||
|
|
||||||
.group-control
|
.group-control
|
||||||
border-color $ui-monokai-borderColor
|
border-color $ui-monokai-borderColor
|
||||||
.group-control-leftButton
|
.group-control-leftButton
|
||||||
colorDarkDefaultButton()
|
colorDarkDefaultButton()
|
||||||
border-color $ui-monokai-borderColor
|
border-color $ui-monokai-borderColor
|
||||||
.group-control-rightButton
|
.group-control-rightButton
|
||||||
colorMonokaiPrimaryButton()
|
colorMonokaiPrimaryButton()
|
||||||
.group-hint
|
.group-hint
|
||||||
@@ -220,3 +224,30 @@ body[data-theme="monokai"]
|
|||||||
.group-section-control
|
.group-section-control
|
||||||
select, .group-section-control-input
|
select, .group-section-control-input
|
||||||
colorMonokaiControl()
|
colorMonokaiControl()
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.root
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
|
.group-header
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
|
|
||||||
|
.group-header2
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
|
.group-section-control-input
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
|
|
||||||
|
.group-control
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
|
.group-control-leftButton
|
||||||
|
colorDarkDefaultButton()
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
|
.group-control-rightButton
|
||||||
|
colorDraculaPrimaryButton()
|
||||||
|
.group-hint
|
||||||
|
colorDraculaControl()
|
||||||
|
.group-section-control
|
||||||
|
select, .group-section-control-input
|
||||||
|
colorDraculaControl()
|
||||||
@@ -23,21 +23,29 @@ class Crowdfunding extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div styleName='root'>
|
<div styleName='root'>
|
||||||
<div styleName='header'>{i18n.__('Crowdfunding')}</div>
|
<div styleName='header'>{i18n.__('Crowdfunding')}</div>
|
||||||
<p>{i18n.__('Dear everyone,')}</p>
|
|
||||||
<br />
|
|
||||||
<p>{i18n.__('Thank you for using Boostnote!')}</p>
|
<p>{i18n.__('Thank you for using Boostnote!')}</p>
|
||||||
<p>{i18n.__('Boostnote is used in about 200 different countries and regions by an awesome community of developers.')}</p>
|
|
||||||
<br />
|
<br />
|
||||||
<p>{i18n.__('To continue supporting this growth, and to satisfy community expectations,')}</p>
|
<p>{i18n.__('We launched IssueHunt which is an issue-based crowdfunding / sourcing platform for open source projects.')}</p>
|
||||||
<p>{i18n.__('we would like to invest more time and resources in this project.')}</p>
|
<p>{i18n.__('Anyone can put a bounty on not only a bug but also on OSS feature requests listed on IssueHunt. Collected funds will be distributed to project owners and contributors.')}</p>
|
||||||
<br />
|
<br />
|
||||||
<p>{i18n.__('If you like this project and see its potential, you can help by supporting us on OpenCollective!')}</p>
|
<p>{i18n.__('### Sustainable Open Source Ecosystem')}</p>
|
||||||
|
<p>{i18n.__('We discussed about open-source ecosystem and IssueHunt concept with the Boostnote team repeatedly. We actually also discussed with Matz who father of Ruby.')}</p>
|
||||||
|
<p>{i18n.__('The original reason why we made IssueHunt was to reward our contributors of Boostnote project. We’ve got tons of Github stars and hundred of contributors in two years.')}</p>
|
||||||
|
<p>{i18n.__('We thought that it will be nice if we can pay reward for our contributors.')}</p>
|
||||||
<br />
|
<br />
|
||||||
<p>{i18n.__('Thanks,')}</p>
|
<p>{i18n.__('### We believe Meritocracy')}</p>
|
||||||
<p>{i18n.__('Boostnote maintainers')}</p>
|
<p>{i18n.__('We think developers who has skill and did great things must be rewarded properly.')}</p>
|
||||||
|
<p>{i18n.__('OSS projects are used in everywhere on the internet, but no matter how they great, most of owners of those projects need to have another job to sustain their living.')}</p>
|
||||||
|
<p>{i18n.__('It sometimes looks like exploitation.')}</p>
|
||||||
|
<p>{i18n.__('We’ve realized IssueHunt could enhance sustainability of open-source ecosystem.')}</p>
|
||||||
|
<br />
|
||||||
|
<p>{i18n.__('As same as issues of Boostnote are already funded on IssueHunt, your open-source projects can be also started funding from now.')}</p>
|
||||||
|
<br />
|
||||||
|
<p>{i18n.__('Thank you,')}</p>
|
||||||
|
<p>{i18n.__('The Boostnote Team')}</p>
|
||||||
<br />
|
<br />
|
||||||
<button styleName='cf-link'>
|
<button styleName='cf-link'>
|
||||||
<a href='https://opencollective.com/boostnoteio' onClick={(e) => this.handleLinkClick(e)}>{i18n.__('Support via OpenCollective')}</a>
|
<a href='http://bit.ly/issuehunt-from-boostnote-app' onClick={(e) => this.handleLinkClick(e)}>{i18n.__('See IssueHunt')}</a>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ p
|
|||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
p
|
p
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
body[data-theme="solarized-dark"]
|
||||||
.root
|
.root
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
@@ -41,3 +41,9 @@ body[data-theme="monokai"]
|
|||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
p
|
p
|
||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.root
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
p
|
||||||
|
color $ui-dracula-text-color
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
height 35px
|
height 35px
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
padding 2.5px 15px
|
padding 2.5px 15px
|
||||||
|
display flex
|
||||||
&:hover
|
&:hover
|
||||||
background-color darken(white, 3%)
|
background-color darken(white, 3%)
|
||||||
|
|
||||||
@@ -18,7 +19,10 @@
|
|||||||
border-left solid 2px transparent
|
border-left solid 2px transparent
|
||||||
padding 0 10px
|
padding 0 10px
|
||||||
line-height 30px
|
line-height 30px
|
||||||
float left
|
flex 1
|
||||||
|
white-space nowrap
|
||||||
|
text-overflow ellipsis
|
||||||
|
overflow hidden
|
||||||
.folderItem-left-danger
|
.folderItem-left-danger
|
||||||
color $danger-color
|
color $danger-color
|
||||||
font-weight bold
|
font-weight bold
|
||||||
@@ -52,7 +56,8 @@
|
|||||||
outline none
|
outline none
|
||||||
|
|
||||||
.folderItem-right
|
.folderItem-right
|
||||||
float right
|
-webkit-box-flex: 1
|
||||||
|
white-space nowrap
|
||||||
|
|
||||||
.folderItem-right-button
|
.folderItem-right-button
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
@@ -149,3 +154,26 @@ body[data-theme="monokai"]
|
|||||||
|
|
||||||
.folderItem-right-dangerButton
|
.folderItem-right-dangerButton
|
||||||
colorMonokaiPrimaryButton()
|
colorMonokaiPrimaryButton()
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.folderItem
|
||||||
|
&:hover
|
||||||
|
background-color $ui-dracula-button-backgroundColor
|
||||||
|
|
||||||
|
.folderItem-left-danger
|
||||||
|
color $danger-color
|
||||||
|
|
||||||
|
.folderItem-left-key
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
|
.folderItem-left-colorButton
|
||||||
|
colorDraculaPrimaryButton()
|
||||||
|
|
||||||
|
.folderItem-right-button
|
||||||
|
colorDraculaPrimaryButton()
|
||||||
|
|
||||||
|
.folderItem-right-confirmButton
|
||||||
|
colorDraculaPrimaryButton()
|
||||||
|
|
||||||
|
.folderItem-right-dangerButton
|
||||||
|
colorDraculaPrimaryButton()
|
||||||
@@ -28,10 +28,20 @@ class HotkeyTab extends React.Component {
|
|||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
this.handleSettingError = (err) => {
|
this.handleSettingError = (err) => {
|
||||||
this.setState({keymapAlert: {
|
if (
|
||||||
type: 'error',
|
this.state.config.hotkey.toggleMain === '' ||
|
||||||
message: err.message != null ? err.message : i18n.__('Error occurs!')
|
this.state.config.hotkey.toggleMode === ''
|
||||||
}})
|
) {
|
||||||
|
this.setState({keymapAlert: {
|
||||||
|
type: 'success',
|
||||||
|
message: i18n.__('Successfully applied!')
|
||||||
|
}})
|
||||||
|
} else {
|
||||||
|
this.setState({keymapAlert: {
|
||||||
|
type: 'error',
|
||||||
|
message: err.message != null ? err.message : i18n.__('An error occurred!')
|
||||||
|
}})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.oldHotkey = this.state.config.hotkey
|
this.oldHotkey = this.state.config.hotkey
|
||||||
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||||
@@ -68,7 +78,8 @@ class HotkeyTab extends React.Component {
|
|||||||
const { config } = this.state
|
const { config } = this.state
|
||||||
config.hotkey = {
|
config.hotkey = {
|
||||||
toggleMain: this.refs.toggleMain.value,
|
toggleMain: this.refs.toggleMain.value,
|
||||||
toggleMode: this.refs.toggleMode.value
|
toggleMode: this.refs.toggleMode.value,
|
||||||
|
deleteNote: this.refs.deleteNote.value
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
config
|
config
|
||||||
@@ -79,7 +90,7 @@ class HotkeyTab extends React.Component {
|
|||||||
this.props.haveToSave({
|
this.props.haveToSave({
|
||||||
tab: 'Hotkey',
|
tab: 'Hotkey',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: i18n.__('You have to save!')
|
message: i18n.__('Unsaved Changes!')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,7 +128,7 @@ class HotkeyTab extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='group-section'>
|
<div styleName='group-section'>
|
||||||
<div styleName='group-section-label'>{i18n.__('Toggle editor mode')}</div>
|
<div styleName='group-section-label'>{i18n.__('Toggle Editor Mode')}</div>
|
||||||
<div styleName='group-section-control'>
|
<div styleName='group-section-control'>
|
||||||
<input styleName='group-section-control-input'
|
<input styleName='group-section-control-input'
|
||||||
onChange={(e) => this.handleHotkeyChange(e)}
|
onChange={(e) => this.handleHotkeyChange(e)}
|
||||||
@@ -127,6 +138,17 @@ class HotkeyTab extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>{i18n.__('Delete Note')}</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input styleName='group-section-control-input'
|
||||||
|
onChange={(e) => this.handleHotkeyChange(e)}
|
||||||
|
ref='deleteNote'
|
||||||
|
value={config.hotkey.deleteNote}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div styleName='group-control'>
|
<div styleName='group-control'>
|
||||||
<button styleName='group-control-leftButton'
|
<button styleName='group-control-leftButton'
|
||||||
onClick={(e) => this.handleHintToggleButtonClick(e)}
|
onClick={(e) => this.handleHintToggleButtonClick(e)}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class InfoTab extends React.Component {
|
|||||||
>{i18n.__('GitHub')}</a>
|
>{i18n.__('GitHub')}</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href='https://boostlog.io/@junp1234'
|
<a href='https://medium.com/boostnote'
|
||||||
onClick={(e) => this.handleLinkClick(e)}
|
onClick={(e) => this.handleLinkClick(e)}
|
||||||
>{i18n.__('Blog')}</a>
|
>{i18n.__('Blog')}</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
color alpha($tab--dark-text-color, 80%)
|
color alpha($tab--dark-text-color, 80%)
|
||||||
|
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
body[data-theme="solarized-dark"]
|
||||||
.root
|
.root
|
||||||
@@ -75,3 +75,10 @@ body[data-theme="monokai"]
|
|||||||
.list
|
.list
|
||||||
a
|
a
|
||||||
color $ui-monokai-active-color
|
color $ui-monokai-active-color
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.root
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
.list
|
||||||
|
a
|
||||||
|
color $ui-dracula-active-color
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ body[data-theme="dark"]
|
|||||||
background-color $dark-primary-button-background--active
|
background-color $dark-primary-button-background--active
|
||||||
&:hover
|
&:hover
|
||||||
color white
|
color white
|
||||||
|
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
body[data-theme="solarized-dark"]
|
||||||
.root
|
.root
|
||||||
@@ -139,3 +139,27 @@ body[data-theme="monokai"]
|
|||||||
background-color $ui-monokai-button--active-backgroundColor
|
background-color $ui-monokai-button--active-backgroundColor
|
||||||
&:hover
|
&:hover
|
||||||
color white
|
color white
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.root
|
||||||
|
background-color transparent
|
||||||
|
.top-bar
|
||||||
|
background-color transparent
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
|
p
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
.nav
|
||||||
|
background-color transparent
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
|
.nav-button
|
||||||
|
background-color transparent
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
&:hover
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
|
.nav-button--active
|
||||||
|
@extend .nav-button
|
||||||
|
color $ui-dracula-button--active-color
|
||||||
|
background-color $ui-dracula-button--active-backgroundColor
|
||||||
|
&:hover
|
||||||
|
color #f8f8f2
|
||||||
@@ -55,7 +55,11 @@ class SnippetList extends React.Component {
|
|||||||
|
|
||||||
defineSnippetStyleName (snippet) {
|
defineSnippetStyleName (snippet) {
|
||||||
const { currentSnippet } = this.props
|
const { currentSnippet } = this.props
|
||||||
if (currentSnippet == null) return
|
|
||||||
|
if (currentSnippet == null) {
|
||||||
|
return 'snippet-item'
|
||||||
|
}
|
||||||
|
|
||||||
if (currentSnippet.id === snippet.id) {
|
if (currentSnippet.id === snippet.id) {
|
||||||
return 'snippet-item-selected'
|
return 'snippet-item-selected'
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import i18n from 'browser/lib/i18n'
|
|||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import SnippetList from './SnippetList'
|
import SnippetList from './SnippetList'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
import copy from 'copy-to-clipboard'
|
||||||
|
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
class SnippetTab extends React.Component {
|
class SnippetTab extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -16,6 +19,17 @@ class SnippetTab extends React.Component {
|
|||||||
this.changeDelay = null
|
this.changeDelay = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notify (title, options) {
|
||||||
|
if (global.process.platform === 'win32') {
|
||||||
|
options.icon = path.join(
|
||||||
|
'file://',
|
||||||
|
global.__dirname,
|
||||||
|
'../../resources/app.png'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return new window.Notification(title, options)
|
||||||
|
}
|
||||||
|
|
||||||
handleSnippetNameOrPrefixChange () {
|
handleSnippetNameOrPrefixChange () {
|
||||||
clearTimeout(this.changeDelay)
|
clearTimeout(this.changeDelay)
|
||||||
this.changeDelay = setTimeout(() => {
|
this.changeDelay = setTimeout(() => {
|
||||||
@@ -27,12 +41,14 @@ class SnippetTab extends React.Component {
|
|||||||
|
|
||||||
handleSnippetSelect (snippet) {
|
handleSnippetSelect (snippet) {
|
||||||
const { currentSnippet } = this.state
|
const { currentSnippet } = this.state
|
||||||
if (currentSnippet === null || currentSnippet.id !== snippet.id) {
|
if (snippet !== null) {
|
||||||
dataApi.fetchSnippet(snippet.id).then(changedSnippet => {
|
if (currentSnippet === null || currentSnippet.id !== snippet.id) {
|
||||||
// notify the snippet editor to load the content of the new snippet
|
dataApi.fetchSnippet(snippet.id).then(changedSnippet => {
|
||||||
this.snippetEditor.onSnippetChanged(changedSnippet)
|
// notify the snippet editor to load the content of the new snippet
|
||||||
this.setState({currentSnippet: changedSnippet})
|
this.snippetEditor.onSnippetChanged(changedSnippet)
|
||||||
})
|
this.setState({currentSnippet: changedSnippet})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +70,17 @@ class SnippetTab extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleCopySnippet (e) {
|
||||||
|
const showCopyNotification = this.props.config.ui.showCopyNotification
|
||||||
|
copy(this.state.currentSnippet.content)
|
||||||
|
if (showCopyNotification) {
|
||||||
|
this.notify('Saved to Clipboard!', {
|
||||||
|
body: 'Paste it wherever you want!',
|
||||||
|
silent: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { config, storageKey } = this.props
|
const { config, storageKey } = this.props
|
||||||
const { currentSnippet } = this.state
|
const { currentSnippet } = this.state
|
||||||
@@ -70,6 +97,13 @@ class SnippetTab extends React.Component {
|
|||||||
onSnippetDeleted={this.handleDeleteSnippet.bind(this)}
|
onSnippetDeleted={this.handleDeleteSnippet.bind(this)}
|
||||||
currentSnippet={currentSnippet} />
|
currentSnippet={currentSnippet} />
|
||||||
<div styleName='snippet-detail' style={{visibility: currentSnippet ? 'visible' : 'hidden'}}>
|
<div styleName='snippet-detail' style={{visibility: currentSnippet ? 'visible' : 'hidden'}}>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<button styleName='group-control-rightButton'
|
||||||
|
onClick={e => this.handleCopySnippet(e)}>{i18n.__('Copy')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div styleName='group-section'>
|
<div styleName='group-section'>
|
||||||
<div styleName='group-section-label'>{i18n.__('Snippet name')}</div>
|
<div styleName='group-section-label'>{i18n.__('Snippet name')}</div>
|
||||||
<div styleName='group-section-control'>
|
<div styleName='group-section-control'>
|
||||||
|
|||||||
@@ -196,3 +196,19 @@ body[data-theme="monokai"]
|
|||||||
color white
|
color white
|
||||||
.group-control-button
|
.group-control-button
|
||||||
colorMonokaiPrimaryButton()
|
colorMonokaiPrimaryButton()
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.snippets
|
||||||
|
background $ui-dracula-backgroundColor
|
||||||
|
.snippet-item
|
||||||
|
color #f8f8f2
|
||||||
|
&::after
|
||||||
|
background $ui-dracula-borderColor
|
||||||
|
&:hover
|
||||||
|
background darken($ui-dracula-backgroundColor, 5)
|
||||||
|
.snippet-item-selected
|
||||||
|
background darken($ui-dracula-backgroundColor, 5)
|
||||||
|
.snippet-detail
|
||||||
|
color #f8f8f2
|
||||||
|
.group-control-button
|
||||||
|
colorDraculaPrimaryButton()
|
||||||
@@ -9,13 +9,17 @@
|
|||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
border-bottom $default-border
|
border-bottom $default-border
|
||||||
margin-bottom 5px
|
margin-bottom 5px
|
||||||
|
display flex
|
||||||
|
|
||||||
.header-label
|
.header-label
|
||||||
float left
|
|
||||||
cursor pointer
|
cursor pointer
|
||||||
&:hover
|
&:hover
|
||||||
.header-label-editButton
|
.header-label-editButton
|
||||||
opacity 1
|
opacity 1
|
||||||
|
flex 1
|
||||||
|
white-space nowrap
|
||||||
|
text-overflow ellipsis
|
||||||
|
overflow hidden
|
||||||
|
|
||||||
.header-label-path
|
.header-label-path
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
@@ -38,8 +42,8 @@
|
|||||||
outline none
|
outline none
|
||||||
|
|
||||||
.header-control
|
.header-control
|
||||||
float right
|
-webkit-box-flex: 1
|
||||||
|
white-space nowrap
|
||||||
.header-control-button
|
.header-control-button
|
||||||
width 30px
|
width 30px
|
||||||
height 25px
|
height 25px
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class StoragesTab extends React.Component {
|
|||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
<div styleName='list'>
|
<div styleName='list'>
|
||||||
<div styleName='header'>{i18n.__('Storages')}</div>
|
<div styleName='header'>{i18n.__('Storage Locations')}</div>
|
||||||
{storageList.length > 0
|
{storageList.length > 0
|
||||||
? storageList
|
? storageList
|
||||||
: <div styleName='list-empty'>{i18n.__('No storage found.')}</div>
|
: <div styleName='list-empty'>{i18n.__('No storage found.')}</div>
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ body[data-theme="dark"]
|
|||||||
.addStorage-body-control-cancelButton
|
.addStorage-body-control-cancelButton
|
||||||
colorDarkDefaultButton()
|
colorDarkDefaultButton()
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
body[data-theme="solarized-dark"]
|
||||||
@@ -236,3 +236,41 @@ body[data-theme="monokai"]
|
|||||||
.addStorage-body-control-cancelButton
|
.addStorage-body-control-cancelButton
|
||||||
colorDarkDefaultButton()
|
colorDarkDefaultButton()
|
||||||
border-color $ui-monokai-borderColor
|
border-color $ui-monokai-borderColor
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.root
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
|
.folderList-item
|
||||||
|
border-bottom $ui-dracula-borderColor
|
||||||
|
|
||||||
|
.folderList-empty
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
|
.list-empty
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
.list-control-addStorageButton
|
||||||
|
border-color $ui-dracula-button-backgroundColor
|
||||||
|
background-color $ui-dracula-button-backgroundColor
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
|
.addStorage-header
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
|
|
||||||
|
.addStorage-body-section-name-input
|
||||||
|
border-color $$ui-dracula-borderColor
|
||||||
|
|
||||||
|
.addStorage-body-section-type-description
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
|
.addStorage-body-section-path-button
|
||||||
|
colorPrimaryButton()
|
||||||
|
.addStorage-body-control
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
|
|
||||||
|
.addStorage-body-control-createButton
|
||||||
|
colorDarkPrimaryButton()
|
||||||
|
.addStorage-body-control-cancelButton
|
||||||
|
colorDarkDefaultButton()
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
@@ -40,7 +40,7 @@ class UiTab extends React.Component {
|
|||||||
this.handleSettingError = (err) => {
|
this.handleSettingError = (err) => {
|
||||||
this.setState({UiAlert: {
|
this.setState({UiAlert: {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: err.message != null ? err.message : i18n.__('Error occurs!')
|
message: err.message != null ? err.message : i18n.__('An error occurred!')
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||||
@@ -68,9 +68,13 @@ class UiTab extends React.Component {
|
|||||||
theme: this.refs.uiTheme.value,
|
theme: this.refs.uiTheme.value,
|
||||||
language: this.refs.uiLanguage.value,
|
language: this.refs.uiLanguage.value,
|
||||||
defaultNote: this.refs.defaultNote.value,
|
defaultNote: this.refs.defaultNote.value,
|
||||||
|
tagNewNoteWithFilteringTags: this.refs.tagNewNoteWithFilteringTags.checked,
|
||||||
showCopyNotification: this.refs.showCopyNotification.checked,
|
showCopyNotification: this.refs.showCopyNotification.checked,
|
||||||
confirmDeletion: this.refs.confirmDeletion.checked,
|
confirmDeletion: this.refs.confirmDeletion.checked,
|
||||||
showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked,
|
showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked,
|
||||||
|
showTagsAlphabetically: this.refs.showTagsAlphabetically.checked,
|
||||||
|
saveTagsAlphabetically: this.refs.saveTagsAlphabetically.checked,
|
||||||
|
enableLiveNoteCounts: this.refs.enableLiveNoteCounts.checked,
|
||||||
disableDirectWrite: this.refs.uiD2w != null
|
disableDirectWrite: this.refs.uiD2w != null
|
||||||
? this.refs.uiD2w.checked
|
? this.refs.uiD2w.checked
|
||||||
: false
|
: false
|
||||||
@@ -89,7 +93,10 @@ class UiTab extends React.Component {
|
|||||||
snippetDefaultLanguage: this.refs.editorSnippetDefaultLanguage.value,
|
snippetDefaultLanguage: this.refs.editorSnippetDefaultLanguage.value,
|
||||||
scrollPastEnd: this.refs.scrollPastEnd.checked,
|
scrollPastEnd: this.refs.scrollPastEnd.checked,
|
||||||
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked,
|
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked,
|
||||||
enableTableEditor: this.refs.enableTableEditor.checked
|
enableTableEditor: this.refs.enableTableEditor.checked,
|
||||||
|
enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked,
|
||||||
|
frontMatterTitleField: this.refs.frontMatterTitleField.value,
|
||||||
|
spellcheck: this.refs.spellcheck.checked
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
fontSize: this.refs.previewFontSize.value,
|
fontSize: this.refs.previewFontSize.value,
|
||||||
@@ -102,11 +109,13 @@ class UiTab extends React.Component {
|
|||||||
latexBlockClose: this.refs.previewLatexBlockClose.value,
|
latexBlockClose: this.refs.previewLatexBlockClose.value,
|
||||||
plantUMLServerAddress: this.refs.previewPlantUMLServerAddress.value,
|
plantUMLServerAddress: this.refs.previewPlantUMLServerAddress.value,
|
||||||
scrollPastEnd: this.refs.previewScrollPastEnd.checked,
|
scrollPastEnd: this.refs.previewScrollPastEnd.checked,
|
||||||
|
scrollSync: this.refs.previewScrollSync.checked,
|
||||||
smartQuotes: this.refs.previewSmartQuotes.checked,
|
smartQuotes: this.refs.previewSmartQuotes.checked,
|
||||||
breaks: this.refs.previewBreaks.checked,
|
breaks: this.refs.previewBreaks.checked,
|
||||||
smartArrows: this.refs.previewSmartArrows.checked,
|
smartArrows: this.refs.previewSmartArrows.checked,
|
||||||
sanitize: this.refs.previewSanitize.value,
|
sanitize: this.refs.previewSanitize.value,
|
||||||
allowCustomCSS: this.refs.previewAllowCustomCSS.checked,
|
allowCustomCSS: this.refs.previewAllowCustomCSS.checked,
|
||||||
|
lineThroughCheckbox: this.refs.lineThroughCheckbox.checked,
|
||||||
customCSS: this.customCSSCM.getCodeMirror().getValue()
|
customCSS: this.customCSSCM.getCodeMirror().getValue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +134,7 @@ class UiTab extends React.Component {
|
|||||||
this.props.haveToSave({
|
this.props.haveToSave({
|
||||||
tab: 'UI',
|
tab: 'UI',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: i18n.__('You have to save!')
|
message: i18n.__('Unsaved Changes!')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -187,6 +196,7 @@ class UiTab extends React.Component {
|
|||||||
<option value='white'>{i18n.__('White')}</option>
|
<option value='white'>{i18n.__('White')}</option>
|
||||||
<option value='solarized-dark'>{i18n.__('Solarized Dark')}</option>
|
<option value='solarized-dark'>{i18n.__('Solarized Dark')}</option>
|
||||||
<option value='monokai'>{i18n.__('Monokai')}</option>
|
<option value='monokai'>{i18n.__('Monokai')}</option>
|
||||||
|
<option value='dracula'>{i18n.__('Dracula')}</option>
|
||||||
<option value='dark'>{i18n.__('Dark')}</option>
|
<option value='dark'>{i18n.__('Dark')}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -244,16 +254,6 @@ class UiTab extends React.Component {
|
|||||||
{i18n.__('Show a confirmation dialog when deleting notes')}
|
{i18n.__('Show a confirmation dialog when deleting notes')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='group-checkBoxSection'>
|
|
||||||
<label>
|
|
||||||
<input onChange={(e) => this.handleUIChange(e)}
|
|
||||||
checked={this.state.config.ui.showOnlyRelatedTags}
|
|
||||||
ref='showOnlyRelatedTags'
|
|
||||||
type='checkbox'
|
|
||||||
/>
|
|
||||||
{i18n.__('Show only related tags')}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{
|
{
|
||||||
global.process.platform === 'win32'
|
global.process.platform === 'win32'
|
||||||
? <div styleName='group-checkBoxSection'>
|
? <div styleName='group-checkBoxSection'>
|
||||||
@@ -269,6 +269,64 @@ class UiTab extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<div styleName='group-header2'>Tags</div>
|
||||||
|
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.ui.saveTagsAlphabetically}
|
||||||
|
ref='saveTagsAlphabetically'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('Save tags of a note in alphabetical order')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.ui.showTagsAlphabetically}
|
||||||
|
ref='showTagsAlphabetically'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('Show tags of a note in alphabetical order')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.ui.showOnlyRelatedTags}
|
||||||
|
ref='showOnlyRelatedTags'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('Show only related tags')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.ui.enableLiveNoteCounts}
|
||||||
|
ref='enableLiveNoteCounts'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('Enable live count of notes')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.ui.tagNewNoteWithFilteringTags}
|
||||||
|
ref='tagNewNoteWithFilteringTags'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('New notes are tagged with the filtering tags')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div styleName='group-header2'>Editor</div>
|
<div styleName='group-header2'>Editor</div>
|
||||||
|
|
||||||
<div styleName='group-section'>
|
<div styleName='group-section'>
|
||||||
@@ -426,6 +484,31 @@ class UiTab extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>
|
||||||
|
{i18n.__('Front matter title field')}
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input styleName='group-section-control-input'
|
||||||
|
ref='frontMatterTitleField'
|
||||||
|
value={config.editor.frontMatterTitleField}
|
||||||
|
onChange={(e) => this.handleUIChange(e)}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.editor.enableFrontMatterTitle}
|
||||||
|
ref='enableFrontMatterTitle'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('Extract title from front matter')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div styleName='group-checkBoxSection'>
|
<div styleName='group-checkBoxSection'>
|
||||||
<label>
|
<label>
|
||||||
<input onChange={(e) => this.handleUIChange(e)}
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
@@ -469,6 +552,16 @@ class UiTab extends React.Component {
|
|||||||
{i18n.__('Enable smart table editor')}
|
{i18n.__('Enable smart table editor')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.editor.spellcheck}
|
||||||
|
ref='spellcheck'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('Enable spellcheck - Experimental feature!! :)')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div styleName='group-header2'>{i18n.__('Preview')}</div>
|
<div styleName='group-header2'>{i18n.__('Preview')}</div>
|
||||||
<div styleName='group-section'>
|
<div styleName='group-section'>
|
||||||
@@ -498,7 +591,7 @@ class UiTab extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='group-section'>
|
<div styleName='group-section'>
|
||||||
<div styleName='group-section-label'>{i18n.__('Code block Theme')}</div>
|
<div styleName='group-section-label'>{i18n.__('Code Block Theme')}</div>
|
||||||
<div styleName='group-section-control'>
|
<div styleName='group-section-control'>
|
||||||
<select value={config.preview.codeBlockTheme}
|
<select value={config.preview.codeBlockTheme}
|
||||||
ref='previewCodeBlockTheme'
|
ref='previewCodeBlockTheme'
|
||||||
@@ -512,6 +605,16 @@ class UiTab extends React.Component {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.preview.lineThroughCheckbox}
|
||||||
|
ref='lineThroughCheckbox'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('Allow line through checkbox')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div styleName='group-checkBoxSection'>
|
<div styleName='group-checkBoxSection'>
|
||||||
<label>
|
<label>
|
||||||
<input onChange={(e) => this.handleUIChange(e)}
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
@@ -522,6 +625,16 @@ class UiTab extends React.Component {
|
|||||||
{i18n.__('Allow preview to scroll past the last line')}
|
{i18n.__('Allow preview to scroll past the last line')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.preview.scrollSync}
|
||||||
|
ref='previewScrollSync'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
{i18n.__('When scrolling, synchronize preview with editor')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div styleName='group-checkBoxSection'>
|
<div styleName='group-checkBoxSection'>
|
||||||
<label>
|
<label>
|
||||||
<input onChange={(e) => this.handleUIChange(e)}
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ function data (state = defaultDataMap(), action) {
|
|||||||
|
|
||||||
// If storage chanced, origin key must be discarded
|
// If storage chanced, origin key must be discarded
|
||||||
if (originKey !== uniqueKey) {
|
if (originKey !== uniqueKey) {
|
||||||
console.log('diffrent storage')
|
|
||||||
// From isStarred
|
// From isStarred
|
||||||
if (originNote.isStarred) {
|
if (originNote.isStarred) {
|
||||||
state.starredSet = new Set(state.starredSet)
|
state.starredSet = new Set(state.starredSet)
|
||||||
|
|||||||
@@ -14,18 +14,18 @@
|
|||||||
list-style none
|
list-style none
|
||||||
padding 0
|
padding 0
|
||||||
margin 0
|
margin 0
|
||||||
|
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
margin .2em 0 0
|
margin .5rem 0 0
|
||||||
background-color $ui-noteList-backgroundColor
|
background-color $ui-noteList-backgroundColor
|
||||||
border 1px solid rgba(0,0,0,.3)
|
border 1px solid rgba(0,0,0,.3)
|
||||||
box-shadow .05em .2em .6em rgba(0,0,0,.2)
|
box-shadow .05em .2em .6em rgba(0,0,0,.2)
|
||||||
text-shadow none
|
text-shadow none
|
||||||
|
|
||||||
&:empty,
|
&:empty,
|
||||||
&[hidden]
|
&[hidden]
|
||||||
display none
|
display none
|
||||||
|
|
||||||
&:before
|
&:before
|
||||||
content ""
|
content ""
|
||||||
position absolute
|
position absolute
|
||||||
@@ -39,12 +39,12 @@
|
|||||||
border-bottom 0
|
border-bottom 0
|
||||||
-webkit-transform rotate(45deg)
|
-webkit-transform rotate(45deg)
|
||||||
transform rotate(45deg)
|
transform rotate(45deg)
|
||||||
|
|
||||||
li
|
li
|
||||||
position relative
|
position relative
|
||||||
padding 6px 18px 6px 10px
|
padding 6px 18px 6px 10px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
|
||||||
li[aria-selected="true"]
|
li[aria-selected="true"]
|
||||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
@@ -53,15 +53,15 @@ body[data-theme="dark"]
|
|||||||
.TagSelect
|
.TagSelect
|
||||||
.react-autosuggest__input
|
.react-autosuggest__input
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
ul
|
ul
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
&:before
|
&:before
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
|
|
||||||
li[aria-selected="true"]
|
li[aria-selected="true"]
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
@@ -70,32 +70,49 @@ body[data-theme="monokai"]
|
|||||||
.TagSelect
|
.TagSelect
|
||||||
.react-autosuggest__input
|
.react-autosuggest__input
|
||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
ul
|
ul
|
||||||
border-color $ui-monokai-borderColor
|
border-color $ui-monokai-borderColor
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
&:before
|
&:before
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
|
|
||||||
li[aria-selected="true"]
|
li[aria-selected="true"]
|
||||||
background-color $ui-monokai-button-backgroundColor
|
background-color $ui-monokai-button-backgroundColor
|
||||||
color $ui-monokai-text-color
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
body[data-theme="dracula"]
|
||||||
|
.TagSelect
|
||||||
|
.react-autosuggest__input
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
|
ul
|
||||||
|
border-color $ui-dracula-borderColor
|
||||||
|
background-color $ui-dracula-noteList-backgroundColor
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
|
&:before
|
||||||
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
|
|
||||||
|
li[aria-selected="true"]
|
||||||
|
background-color $ui-dracula-button-backgroundColor
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
body[data-theme="solarized-dark"]
|
||||||
.TagSelect
|
.TagSelect
|
||||||
.react-autosuggest__input
|
.react-autosuggest__input
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
ul
|
ul
|
||||||
border-color $ui-solarized-dark-borderColor
|
border-color $ui-solarized-dark-borderColor
|
||||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
&:before
|
&:before
|
||||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
|
||||||
li[aria-selected="true"]
|
li[aria-selected="true"]
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
@@ -104,6 +121,6 @@ body[data-theme="white"]
|
|||||||
.TagSelect
|
.TagSelect
|
||||||
ul
|
ul
|
||||||
background-color $ui-white-noteList-backgroundColor
|
background-color $ui-white-noteList-backgroundColor
|
||||||
|
|
||||||
li[aria-selected="true"]
|
li[aria-selected="true"]
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color $ui-button--active-backgroundColor
|
||||||
@@ -128,6 +128,16 @@ colorMonokaiPrimaryButton()
|
|||||||
&:active:hover
|
&:active:hover
|
||||||
background-color $dark-primary-button-background--active
|
background-color $dark-primary-button-background--active
|
||||||
|
|
||||||
|
colorDraculaPrimaryButton()
|
||||||
|
color $ui-dracula-text-color
|
||||||
|
background-color $ui-dracula-button-backgroundColor
|
||||||
|
border none
|
||||||
|
&:hover
|
||||||
|
background-color $ui-dracula-button--active-backgroundColor
|
||||||
|
&:active
|
||||||
|
&:active:hover
|
||||||
|
background-color $ui-dracula-button--active-backgroundColor
|
||||||
|
|
||||||
|
|
||||||
// Danger button(Brand color)
|
// Danger button(Brand color)
|
||||||
$danger-button-background = #c9302c
|
$danger-button-background = #c9302c
|
||||||
@@ -369,7 +379,7 @@ $ui-monokai-active-color = #f92672
|
|||||||
|
|
||||||
$ui-monokai-borderColor = #373831
|
$ui-monokai-borderColor = #373831
|
||||||
|
|
||||||
$ui-monokai-tag-backgroundColor = #f92672
|
$ui-monokai-tag-backgroundColor = #373831
|
||||||
|
|
||||||
$ui-monokai-button-backgroundColor = #373831
|
$ui-monokai-button-backgroundColor = #373831
|
||||||
$ui-monokai-button--active-color = white
|
$ui-monokai-button--active-color = white
|
||||||
@@ -383,4 +393,30 @@ modalMonokai()
|
|||||||
width 100%
|
width 100%
|
||||||
background-color $ui-monokai-backgroundColor
|
background-color $ui-monokai-backgroundColor
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
border-radius $modal-border-radius
|
||||||
|
|
||||||
|
/******* Dracula theme ********/
|
||||||
|
$ui-dracula-backgroundColor = #282a36
|
||||||
|
$ui-dracula-noteList-backgroundColor = #282a36
|
||||||
|
$ui-dracula-noteDetail-backgroundColor = #282a36
|
||||||
|
|
||||||
|
$ui-dracula-text-color = #f8f8f2
|
||||||
|
$ui-dracula-active-color = #bd93f9
|
||||||
|
|
||||||
|
$ui-dracula-borderColor = #44475a
|
||||||
|
|
||||||
|
$ui-dracula-tag-backgroundColor = #8be9fd
|
||||||
|
|
||||||
|
$ui-dracula-button-backgroundColor = #44475a
|
||||||
|
$ui-dracula-button--active-color = #f8f8f2
|
||||||
|
$ui-dracula-button--active-backgroundColor = #bd93f9
|
||||||
|
$ui-dracula-button--hover-backgroundColor = lighten($ui-dracula-backgroundColor, 10%)
|
||||||
|
$ui-dracula-button--focus-borderColor = lighten(#44475a, 25%)
|
||||||
|
|
||||||
|
modalDracula()
|
||||||
|
position relative
|
||||||
|
z-index $modal-z-index
|
||||||
|
width 100%
|
||||||
|
background-color $ui-dracula-backgroundColor
|
||||||
|
overflow hidden
|
||||||
border-radius $modal-border-radius
|
border-radius $modal-border-radius
|
||||||
@@ -1,18 +1,30 @@
|
|||||||
# Contributing to Boostnote (English)
|
# Contributing to Boostnote (English)
|
||||||
|
|
||||||
### When you open an issue of a bug report
|
### When you open an issue or a bug report
|
||||||
There are no issue template. But there is a request.
|
There is an issue template for you to follow. Please provide as much information as you can according to the template.
|
||||||
|
|
||||||
**Please paste screenshots of Boostnote with developer tool open**
|
Thank you in advance for your help.
|
||||||
|
|
||||||
Thank you for your help in advance.
|
### When you open a pull request
|
||||||
|
There is a pull request template for your to follow. Please fill in the template before submitting your code. Your pull request will be reviewed faster if we know exactly what it does.
|
||||||
|
|
||||||
### About copyright of Pull Request
|
Make sure that you have:
|
||||||
|
- Checked [`code_style.md`](docs/code_style.md) for information on code style
|
||||||
|
- Write tests for your code and run test with the following command
|
||||||
|
```
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
- Lint your code using the following command
|
||||||
|
```
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
If you make a pull request, It means you agree to transfer the copyright of the code changes to BoostIO.
|
### Concerning Copyright
|
||||||
|
|
||||||
It doesn't mean Boostnote will become a paid app. If we want to earn some money, We will try other way, which is some kind of cloud storage, Mobile app integration or some SPECIAL features.
|
By making a pull request you agree to transfer ownership of your code to BoostIO.
|
||||||
Because GPL v3 is too strict to be compatible with any other License, We thought this is needed to replace the license with much freer one(like BSD, MIT) somewhen.
|
|
||||||
|
This doesn't mean Boostnote will become a paid app. If we want to earn money, we will find other way. Potentially some kind of cloud storage, mobile app integration, or some premium features.
|
||||||
|
GPL v3 is too strict to be compatible with another license, so we thought it might be necessary to replace the license with a more open one (like BSD, MIT) eventually.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ function startServer () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startElectron () {
|
function startElectron () {
|
||||||
spawn(electron, ['--hot', './index.js'])
|
spawn(electron, ['--hot', './index.js'], { stdio: 'inherit' })
|
||||||
.on('close', () => {
|
.on('close', () => {
|
||||||
server.close()
|
server.close()
|
||||||
})
|
})
|
||||||
|
|||||||
342
dictionaries/de_DE/LICENSE
Normal file
342
dictionaries/de_DE/LICENSE
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
From https://github.com/elastic/hunspell/blob/master/dicts/de_DE/README_de_DE.txt
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||||
|
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Library General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) 19yy <name of author>
|
||||||
|
|
||||||
|
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
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) 19yy name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Library General
|
||||||
|
Public License instead of this License.
|
||||||
501
dictionaries/de_DE/de_DE.aff
Normal file
501
dictionaries/de_DE/de_DE.aff
Normal file
@@ -0,0 +1,501 @@
|
|||||||
|
SET ISO8859-1
|
||||||
|
TRY esianrtolcdugmphbyfvkwäüößáéêàâñESIANRTOLCDUGMPHBYFVKWÄÜÖ
|
||||||
|
|
||||||
|
|
||||||
|
PFX G N 1
|
||||||
|
PFX G 0 ge .
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PFX U Y 1
|
||||||
|
PFX U 0 un .
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PFX V Y 1
|
||||||
|
PFX V 0 ver .
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SFX F N 7
|
||||||
|
SFX F 0 nen in
|
||||||
|
SFX F e in e
|
||||||
|
SFX F e innen e
|
||||||
|
SFX F 0 in [^i]n
|
||||||
|
SFX F 0 innen [^i]n
|
||||||
|
SFX F 0 in [^en]
|
||||||
|
SFX F 0 innen [^en]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SFX L N 12
|
||||||
|
SFX L 0 tlich n
|
||||||
|
SFX L 0 tliche n
|
||||||
|
SFX L 0 tlicher n
|
||||||
|
SFX L 0 tliches n
|
||||||
|
SFX L 0 tlichem n
|
||||||
|
SFX L 0 tlichen n
|
||||||
|
SFX L 0 lich [^n]
|
||||||
|
SFX L 0 liche [^n]
|
||||||
|
SFX L 0 licher [^n]
|
||||||
|
SFX L 0 liches [^n]
|
||||||
|
SFX L 0 lichem [^n]
|
||||||
|
SFX L 0 lichen [^n]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SFX H N 2
|
||||||
|
SFX H 0 heit .
|
||||||
|
SFX H 0 heiten .
|
||||||
|
|
||||||
|
|
||||||
|
SFX K N 2
|
||||||
|
SFX K 0 keit .
|
||||||
|
SFX K 0 keiten .
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SFX M N 12
|
||||||
|
SFX M 0 chen [^s][^s]
|
||||||
|
SFX M 0 chens [^s][^s]
|
||||||
|
SFX M 0 chen [^e]
|
||||||
|
SFX M 0 chens [^e]
|
||||||
|
SFX M ass ässchen ass
|
||||||
|
SFX M ass ässchens ass
|
||||||
|
SFX M oss össchen oss
|
||||||
|
SFX M oss össchens oss
|
||||||
|
SFX M uss üsschen uss
|
||||||
|
SFX M uss üsschens uss
|
||||||
|
SFX M e chen e
|
||||||
|
SFX M e chens e
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SFX A Y 46
|
||||||
|
SFX A 0 r e
|
||||||
|
SFX A 0 n e
|
||||||
|
SFX A 0 m e
|
||||||
|
SFX A 0 s e
|
||||||
|
SFX A 0 e [^elr]
|
||||||
|
SFX A 0 er [^elr]
|
||||||
|
SFX A 0 en [^elr]
|
||||||
|
SFX A 0 em [^elr]
|
||||||
|
SFX A 0 es [^elr]
|
||||||
|
SFX A 0 e [^e][rl]
|
||||||
|
SFX A 0 er [^e][rl]
|
||||||
|
SFX A 0 en [^e][rl]
|
||||||
|
SFX A 0 em [^e][rl]
|
||||||
|
SFX A 0 es [^e][rl]
|
||||||
|
SFX A 0 e [^u]er
|
||||||
|
SFX A 0 er [^u]er
|
||||||
|
SFX A 0 en [^u]er
|
||||||
|
SFX A 0 em [^u]er
|
||||||
|
SFX A 0 es [^u]er
|
||||||
|
SFX A er re uer
|
||||||
|
SFX A er rer uer
|
||||||
|
SFX A er ren uer
|
||||||
|
SFX A er rem uer
|
||||||
|
SFX A er res uer
|
||||||
|
SFX A 0 e [eil]el
|
||||||
|
SFX A 0 er [eil]el
|
||||||
|
SFX A 0 en [eil]el
|
||||||
|
SFX A 0 em [eil]el
|
||||||
|
SFX A 0 es [eil]el
|
||||||
|
SFX A el le [^eil]el
|
||||||
|
SFX A el ler [^eil]el
|
||||||
|
SFX A el len [^eil]el
|
||||||
|
SFX A el lem [^eil]el
|
||||||
|
SFX A el les [^eil]el
|
||||||
|
SFX A lig elig [^aeiouhlräüö]lig
|
||||||
|
SFX A lig elige [^aeiouhlräüö]lig
|
||||||
|
SFX A lig eliger [^aeiouhlräüö]lig
|
||||||
|
SFX A lig eligen [^aeiouhlräüö]lig
|
||||||
|
SFX A lig eligem [^aeiouhlräüö]lig
|
||||||
|
SFX A lig eliges [^aeiouhlräüö]lig
|
||||||
|
SFX A erig rig [^hi]erig
|
||||||
|
SFX A erig rige [^hi]erig
|
||||||
|
SFX A erig riger [^hi]erig
|
||||||
|
SFX A erig rigen [^hi]erig
|
||||||
|
SFX A erig rigem [^hi]erig
|
||||||
|
SFX A erig riges [^hi]erig
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SFX C Y 80
|
||||||
|
SFX C 0 ere [^elr]
|
||||||
|
SFX C 0 erer [^elr]
|
||||||
|
SFX C 0 eren [^elr]
|
||||||
|
SFX C 0 erem [^elr]
|
||||||
|
SFX C 0 eres [^elr]
|
||||||
|
SFX C 0 re e
|
||||||
|
SFX C 0 rer e
|
||||||
|
SFX C 0 ren e
|
||||||
|
SFX C 0 rem e
|
||||||
|
SFX C 0 res e
|
||||||
|
SFX C 0 ere [^e][lr]
|
||||||
|
SFX C 0 erer [^e][lr]
|
||||||
|
SFX C 0 eren [^e][lr]
|
||||||
|
SFX C 0 erem [^e][lr]
|
||||||
|
SFX C 0 eres [^e][lr]
|
||||||
|
SFX C el lere el
|
||||||
|
SFX C el lerer el
|
||||||
|
SFX C el leren el
|
||||||
|
SFX C el lerem el
|
||||||
|
SFX C el leres el
|
||||||
|
SFX C er rere uer
|
||||||
|
SFX C er rerer uer
|
||||||
|
SFX C er reren uer
|
||||||
|
SFX C er rerem uer
|
||||||
|
SFX C er reres uer
|
||||||
|
SFX C 0 ere [^u]er
|
||||||
|
SFX C 0 erer [^u]er
|
||||||
|
SFX C 0 eren [^u]er
|
||||||
|
SFX C 0 erem [^u]er
|
||||||
|
SFX C 0 eres [^u]er
|
||||||
|
SFX C lig eligere [^aeiouhlräüö]lig
|
||||||
|
SFX C lig eligerer [^aeiouhlräüö]lig
|
||||||
|
SFX C lig eligeren [^aeiouhlräüö]lig
|
||||||
|
SFX C lig eligerem [^aeiouhlräüö]lig
|
||||||
|
SFX C lig eligeres [^aeiouhlräüö]lig
|
||||||
|
SFX C erig rigere [^hi]erig
|
||||||
|
SFX C erig rigerer [^hi]erig
|
||||||
|
SFX C erig rigeren [^hi]erig
|
||||||
|
SFX C erig rigerem [^hi]erig
|
||||||
|
SFX C erig rigeres [^hi]erig
|
||||||
|
SFX C 0 este [kßsuxz]
|
||||||
|
SFX C 0 ester [kßsuxz]
|
||||||
|
SFX C 0 esten [kßsuxz]
|
||||||
|
SFX C 0 estem [kßsuxz]
|
||||||
|
SFX C 0 estes [kßsuxz]
|
||||||
|
SFX C 0 ste et
|
||||||
|
SFX C 0 ster et
|
||||||
|
SFX C 0 sten et
|
||||||
|
SFX C 0 stem et
|
||||||
|
SFX C 0 stes et
|
||||||
|
SFX C 0 este [^e]t
|
||||||
|
SFX C 0 ester [^e]t
|
||||||
|
SFX C 0 esten [^e]t
|
||||||
|
SFX C 0 estem [^e]t
|
||||||
|
SFX C 0 estes [^e]t
|
||||||
|
SFX C 0 ste [^kßstxz]
|
||||||
|
SFX C 0 ster [^kßstxz]
|
||||||
|
SFX C 0 sten [^kßstxz]
|
||||||
|
SFX C 0 stem [^kßstxz]
|
||||||
|
SFX C 0 stes [^kßstxz]
|
||||||
|
SFX C 0 ste nd
|
||||||
|
SFX C 0 ster nd
|
||||||
|
SFX C 0 sten nd
|
||||||
|
SFX C 0 stem nd
|
||||||
|
SFX C 0 stes nd
|
||||||
|
SFX C 0 este [^n]d
|
||||||
|
SFX C 0 ester [^n]d
|
||||||
|
SFX C 0 esten [^n]d
|
||||||
|
SFX C 0 estem [^n]d
|
||||||
|
SFX C 0 estes [^n]d
|
||||||
|
SFX C lig eligste [^aeiouhlräüö]lig
|
||||||
|
SFX C lig eligster [^aeiouhlräüö]lig
|
||||||
|
SFX C lig eligsten [^aeiouhlräüö]lig
|
||||||
|
SFX C lig eligstem [^aeiouhlräüö]lig
|
||||||
|
SFX C lig eligstes [^aeiouhlräüö]lig
|
||||||
|
SFX C erig rigste [^hi]erig
|
||||||
|
SFX C erig rigster [^hi]erig
|
||||||
|
SFX C erig rigsten [^hi]erig
|
||||||
|
SFX C erig rigstem [^hi]erig
|
||||||
|
SFX C erig rigstes [^hi]erig
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SFX E Y 1
|
||||||
|
SFX E 0 e .
|
||||||
|
|
||||||
|
|
||||||
|
SFX f Y 4
|
||||||
|
SFX f ph f ph
|
||||||
|
SFX f ph fen ph
|
||||||
|
SFX f phie fie phie
|
||||||
|
SFX f phie fien phie
|
||||||
|
|
||||||
|
|
||||||
|
SFX N Y 1
|
||||||
|
SFX N 0 n .
|
||||||
|
|
||||||
|
|
||||||
|
SFX P Y 1
|
||||||
|
SFX P 0 en .
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SFX p Y 24
|
||||||
|
SFX p auf äufe auf
|
||||||
|
SFX p auf äufen auf
|
||||||
|
SFX p aus äuser haus
|
||||||
|
SFX p aus äusern haus
|
||||||
|
SFX p arkt ärkte markt
|
||||||
|
SFX p arkt ärkten markt
|
||||||
|
SFX p ang änge ang
|
||||||
|
SFX p ang ängen ang
|
||||||
|
SFX p uß üße uß
|
||||||
|
SFX p uß üßen uß
|
||||||
|
SFX p oß öße oß
|
||||||
|
SFX p oß ößen oß
|
||||||
|
SFX p aum äume aum
|
||||||
|
SFX p aum äumen aum
|
||||||
|
SFX p ag äge ag
|
||||||
|
SFX p ag ägen ag
|
||||||
|
SFX p ug üge ug
|
||||||
|
SFX p ug ügen ug
|
||||||
|
SFX p all älle all
|
||||||
|
SFX p all ällen all
|
||||||
|
SFX p ass ässe ass
|
||||||
|
SFX p ass ässen ass
|
||||||
|
SFX p uss üsse uss
|
||||||
|
SFX p uss üssen uss
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SFX R Y 3
|
||||||
|
SFX R 0 er [^e]
|
||||||
|
SFX R 0 ern [^e]
|
||||||
|
SFX R 0 r e
|
||||||
|
|
||||||
|
|
||||||
|
SFX S Y 1
|
||||||
|
SFX S 0 s .
|
||||||
|
|
||||||
|
|
||||||
|
SFX q Y 2
|
||||||
|
SFX q s sse s
|
||||||
|
SFX q s ssen s
|
||||||
|
|
||||||
|
|
||||||
|
SFX Q Y 3
|
||||||
|
SFX Q s sse s
|
||||||
|
SFX Q s ssen s
|
||||||
|
SFX Q s sses s
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SFX T Y 1
|
||||||
|
SFX T 0 es .
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SFX J Y 12
|
||||||
|
SFX J n ung [bgkpßsz]eln
|
||||||
|
SFX J n ungen [bgkpßsz]eln
|
||||||
|
SFX J eln lung eln
|
||||||
|
SFX J n ung ern
|
||||||
|
SFX J en ung en
|
||||||
|
SFX J eln lungen eln
|
||||||
|
SFX J n ungen ern
|
||||||
|
SFX J en ungen en
|
||||||
|
SFX J 0 ung [^n]
|
||||||
|
SFX J 0 ungen [^n]
|
||||||
|
SFX J el lung el
|
||||||
|
SFX J el lungen el
|
||||||
|
|
||||||
|
|
||||||
|
SFX B N 12
|
||||||
|
SFX B n bar e[lr]n
|
||||||
|
SFX B n bare e[lr]n
|
||||||
|
SFX B n baren e[lr]n
|
||||||
|
SFX B n barer e[lr]n
|
||||||
|
SFX B n bares e[lr]n
|
||||||
|
SFX B n barem e[lr]n
|
||||||
|
SFX B en bar en
|
||||||
|
SFX B en bare en
|
||||||
|
SFX B en baren en
|
||||||
|
SFX B en barer en
|
||||||
|
SFX B en bares en
|
||||||
|
SFX B en barem en
|
||||||
|
|
||||||
|
|
||||||
|
SFX D Y 6
|
||||||
|
SFX D 0 d n
|
||||||
|
SFX D 0 de n
|
||||||
|
SFX D 0 den n
|
||||||
|
SFX D 0 der n
|
||||||
|
SFX D 0 des n
|
||||||
|
SFX D 0 dem n
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SFX W Y 8
|
||||||
|
SFX W en 0 [^imn]en
|
||||||
|
SFX W en 0 eien
|
||||||
|
SFX W en 0 [^bght][mn]en
|
||||||
|
SFX W en 0 [^c]hnen
|
||||||
|
SFX W n 0 e[lr]n
|
||||||
|
SFX W st 0 [^s]st
|
||||||
|
SFX W t 0 sst
|
||||||
|
SFX W t 0 [^s]t
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SFX I Y 16
|
||||||
|
SFX I n 0 en
|
||||||
|
SFX I eln le eln
|
||||||
|
SFX I n e eln
|
||||||
|
SFX I ern re ern
|
||||||
|
SFX I n e ern
|
||||||
|
SFX I n t e[lr]n
|
||||||
|
SFX I n t [dt]en
|
||||||
|
SFX I en t [^dimnt]en
|
||||||
|
SFX I en t eien
|
||||||
|
SFX I n t [^e]ien
|
||||||
|
SFX I n t chnen
|
||||||
|
SFX I en t [^c]h[mn]en
|
||||||
|
SFX I n t [^aäehilmnoöuür][mn]en
|
||||||
|
SFX I en t [aäeilmnoöuür][mn]en
|
||||||
|
SFX I n e un
|
||||||
|
SFX I n t un
|
||||||
|
|
||||||
|
|
||||||
|
SFX X Y 26
|
||||||
|
SFX X n t e[lr]n
|
||||||
|
SFX X n t [dtw]en
|
||||||
|
SFX X en t eien
|
||||||
|
SFX X n t [^e]ien
|
||||||
|
SFX X en t [^ditmnw]en
|
||||||
|
SFX X n t chnen
|
||||||
|
SFX X en t [^c]h[mn]en
|
||||||
|
SFX X n t [^aäehilmnoöuür][mn]en
|
||||||
|
SFX X en t [aäeilmnoöuür][mn]en
|
||||||
|
SFX X n t un
|
||||||
|
SFX X st 0 tst
|
||||||
|
SFX X n st e[lr]n
|
||||||
|
SFX X n st [dtw]en
|
||||||
|
SFX X en st [^dimnßstwzx]en
|
||||||
|
SFX X en st eien
|
||||||
|
SFX X n st [^e]ien
|
||||||
|
SFX X n st chnen
|
||||||
|
SFX X en st [^c]h[mn]en
|
||||||
|
SFX X n st [^aäehilmnoöuür][mn]en
|
||||||
|
SFX X en st [aäeilmnoöuür][mn]en
|
||||||
|
SFX X n st un
|
||||||
|
SFX X n st [ßsxz]en
|
||||||
|
SFX X n st ssen
|
||||||
|
SFX X n st schen
|
||||||
|
SFX X t st [^sz]t
|
||||||
|
SFX X t est zt
|
||||||
|
|
||||||
|
|
||||||
|
SFX Y Y 36
|
||||||
|
SFX Y n te e[lr]n
|
||||||
|
SFX Y n te [dtw]en
|
||||||
|
SFX Y en te [^dimntw]en
|
||||||
|
SFX Y en te eien
|
||||||
|
SFX Y n te [^e]ien
|
||||||
|
SFX Y n te chnen
|
||||||
|
SFX Y en te [^c]h[mn]en
|
||||||
|
SFX Y n te [^aäehilmnoöuür][mn]en
|
||||||
|
SFX Y en te [aäeilmnoöuür][mn]en
|
||||||
|
SFX Y n test e[lr]n
|
||||||
|
SFX Y n test [dtw]en
|
||||||
|
SFX Y en test [^dimntw]en
|
||||||
|
SFX Y en test eien
|
||||||
|
SFX Y n test [^e]ien
|
||||||
|
SFX Y n test chnen
|
||||||
|
SFX Y en test [^c]h[mn]en
|
||||||
|
SFX Y n test [^aäehilmnoöuür][mn]en
|
||||||
|
SFX Y en test [aäeilmnoöuür][mn]en
|
||||||
|
SFX Y n tet e[lr]n
|
||||||
|
SFX Y n tet [dtw]en
|
||||||
|
SFX Y en tet [^dimntw]en
|
||||||
|
SFX Y en tet eien
|
||||||
|
SFX Y n tet [^e]ien
|
||||||
|
SFX Y n tet chnen
|
||||||
|
SFX Y en tet [^c]h[mn]en
|
||||||
|
SFX Y n tet [^aäehilmnoöuür][mn]en
|
||||||
|
SFX Y en tet [aäeilmnoöuür][mn]en
|
||||||
|
SFX Y n ten e[lr]n
|
||||||
|
SFX Y n ten [dtw]en
|
||||||
|
SFX Y en ten [^dimntw]en
|
||||||
|
SFX Y en ten eien
|
||||||
|
SFX Y n ten [^e]ien
|
||||||
|
SFX Y n ten chnen
|
||||||
|
SFX Y en ten [^c]h[mn]en
|
||||||
|
SFX Y n ten [^aäehilmnoöuür][mn]en
|
||||||
|
SFX Y en ten [aäeilmnoöuür][mn]en
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SFX Z Y 13
|
||||||
|
SFX Z 0 st [^hßsz]
|
||||||
|
SFX Z 0 st [^c]h
|
||||||
|
SFX Z 0 st [^s]ch
|
||||||
|
SFX Z 0 est [dfkstz]
|
||||||
|
SFX Z 0 est ch
|
||||||
|
SFX Z 0 est [au]ß
|
||||||
|
SFX Z 0 est ieß
|
||||||
|
SFX Z 0 est [io]ss
|
||||||
|
SFX Z 0 t [^dt]
|
||||||
|
SFX Z 0 et [dt]
|
||||||
|
SFX Z 0 n e
|
||||||
|
SFX Z 0 en ie
|
||||||
|
SFX Z 0 en [^e]
|
||||||
|
|
||||||
|
|
||||||
|
SFX O Y 21
|
||||||
|
SFX O n tes e[lr]n
|
||||||
|
SFX O n tes [dtw]en
|
||||||
|
SFX O en tes [^dmntw]en
|
||||||
|
SFX O n tes chnen
|
||||||
|
SFX O en tes [^c]h[mn]en
|
||||||
|
SFX O n tes [^aäehilmnoöuür][mn]en
|
||||||
|
SFX O en tes [aäeilmnoöuür][mn]en
|
||||||
|
SFX O n ter e[lr]n
|
||||||
|
SFX O n ter [dtw]en
|
||||||
|
SFX O en ter [^dmntw]en
|
||||||
|
SFX O n ter chnen
|
||||||
|
SFX O en ter [^c]h[mn]en
|
||||||
|
SFX O n ter [^aäehilmnoöuür][mn]en
|
||||||
|
SFX O en ter [aäeilmnoöuür][mn]en
|
||||||
|
SFX O n tem e[lr]n
|
||||||
|
SFX O n tem [dtw]en
|
||||||
|
SFX O en tem [^dmntw]en
|
||||||
|
SFX O n tem chnen
|
||||||
|
SFX O en tem [^c]h[mn]en
|
||||||
|
SFX O n tem [^aäehilmnoöuür][mn]en
|
||||||
|
SFX O en tem [aäeilmnoöuür][mn]en
|
||||||
|
|
||||||
|
REP 15
|
||||||
|
REP f ph
|
||||||
|
REP ph f
|
||||||
|
REP ß ss
|
||||||
|
REP ss ß
|
||||||
|
REP s ss
|
||||||
|
REP ss s
|
||||||
|
REP i ie
|
||||||
|
REP ie i
|
||||||
|
REP ee e
|
||||||
|
REP o oh
|
||||||
|
REP oh o
|
||||||
|
REP a ah
|
||||||
|
REP ah a
|
||||||
|
REP e eh
|
||||||
|
REP eh e
|
||||||
|
|
||||||
80471
dictionaries/de_DE/de_DE.dic
Normal file
80471
dictionaries/de_DE/de_DE.dic
Normal file
File diff suppressed because it is too large
Load Diff
282
dictionaries/en_GB/LICENSE
Normal file
282
dictionaries/en_GB/LICENSE
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
From https://cgit.freedesktop.org/libreoffice/dictionaries/tree/en/
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Lesser General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
1176
dictionaries/en_GB/en_GB.aff
Normal file
1176
dictionaries/en_GB/en_GB.aff
Normal file
File diff suppressed because it is too large
Load Diff
62171
dictionaries/en_GB/en_GB.dic
Normal file
62171
dictionaries/en_GB/en_GB.dic
Normal file
File diff suppressed because it is too large
Load Diff
10767
dictionaries/fr_FR/fr_FR.aff
Normal file
10767
dictionaries/fr_FR/fr_FR.aff
Normal file
File diff suppressed because it is too large
Load Diff
63063
dictionaries/fr_FR/fr_FR.dic
Normal file
63063
dictionaries/fr_FR/fr_FR.dic
Normal file
File diff suppressed because it is too large
Load Diff
82
docs/code_style.md
Normal file
82
docs/code_style.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Boostnote Code Style
|
||||||
|
When submitting your PR, please make sure that your code is well tested and follow the code style of Boostnote.
|
||||||
|
|
||||||
|
The code style of Boostnote is listed in [`.eslintrc`](.eslintrc). We also have additional code styles that is not mentioned in that file.
|
||||||
|
|
||||||
|
## Additional code styles
|
||||||
|
### Use ES6 Object Destructing
|
||||||
|
|
||||||
|
Please use Object Destructing whenever it's possible.
|
||||||
|
|
||||||
|
**Example**: Importing from library
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
// BAD
|
||||||
|
import Code from 'library'
|
||||||
|
const subCode = Code.subCode
|
||||||
|
subCode()
|
||||||
|
|
||||||
|
// GOOD
|
||||||
|
import { subCode } from 'library'
|
||||||
|
subCode()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example**: Extract data from react component state
|
||||||
|
|
||||||
|
```
|
||||||
|
// BAD
|
||||||
|
<h1>{this.state.name}</h1>
|
||||||
|
|
||||||
|
// GOOD
|
||||||
|
const { name } = this.state
|
||||||
|
<h1>{name}</h1>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use meaningful name
|
||||||
|
This is actually not a "code style" but rather a requirement in every projects. Please name your variables carefully.
|
||||||
|
|
||||||
|
**Example**: Naming callback function for event
|
||||||
|
|
||||||
|
```js
|
||||||
|
// BAD
|
||||||
|
<h1 onclick={onClick}>Name</h1>
|
||||||
|
|
||||||
|
// GOOD
|
||||||
|
<h1 onclick={onNameClick}>Name</h1>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Don't write long conditional statement
|
||||||
|
When writing a conditional statement, if it's too long, cut it into small meaningful variables.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// BAD
|
||||||
|
if (note.type == 'markdown' && note.index == 2 && note.content.indexOf('string') != -1)
|
||||||
|
|
||||||
|
// GOOD
|
||||||
|
const isSecondMarkdownNote = note.type == 'markdown' && note.index == 2
|
||||||
|
const isNoteHasString = note.content.indexOf('string') != -1
|
||||||
|
if (isSecondMarkdownNote && isNoteHasString)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use class property instead of class methods
|
||||||
|
When writing React components, try to use class property instead of class methods. The reason for this is explained perfectly here:
|
||||||
|
https://codeburst.io/use-class-properties-to-clean-up-your-classes-and-react-components-93185879f688
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// BAD
|
||||||
|
class MyComponent extends React.Component {
|
||||||
|
myMethod () {
|
||||||
|
// code goes here...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD
|
||||||
|
class MyComponent extends React.Component {
|
||||||
|
myMethod = () => {
|
||||||
|
// code goes here...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user