mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 01:36:22 +00:00
Merge branch 'master' into precommit-command
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
|
||||
|
||||
<!--
|
||||
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
|
||||
|
||||
<!--
|
||||
Let us know what you think should happen!
|
||||
-->
|
||||
|
||||
# Steps to reproduce
|
||||
|
||||
<!--
|
||||
Please be thorough, issues we can reproduce are easier to fix!
|
||||
-->
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
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 crypto from 'crypto'
|
||||
import consts from 'browser/lib/consts'
|
||||
import styles from '../components/CodeEditor.styl'
|
||||
import fs from 'fs'
|
||||
const { ipcRenderer } = require('electron')
|
||||
const { ipcRenderer, remote } = require('electron')
|
||||
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'
|
||||
|
||||
@@ -28,7 +33,7 @@ export default class CodeEditor extends React.Component {
|
||||
leading: false,
|
||||
trailing: true
|
||||
})
|
||||
this.changeHandler = e => this.handleChange(e)
|
||||
this.changeHandler = (editor, changeObject) => this.handleChange(editor, changeObject)
|
||||
this.focusHandler = () => {
|
||||
ipcRenderer.send('editor:focused', true)
|
||||
}
|
||||
@@ -57,9 +62,18 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
this.searchHandler = (e, msg) => this.handleSearch(msg)
|
||||
this.searchState = null
|
||||
this.scrollToLineHandeler = this.scrollToLine.bind(this)
|
||||
|
||||
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.turndownService = new TurndownService()
|
||||
}
|
||||
|
||||
handleSearch (msg) {
|
||||
@@ -125,6 +139,7 @@ export default class CodeEditor extends React.Component {
|
||||
componentDidMount () {
|
||||
const { rulers, enableRulers } = this.props
|
||||
const expandSnippet = this.expandSnippet.bind(this)
|
||||
eventEmitter.on('line:jump', this.scrollToLineHandeler)
|
||||
|
||||
const defaultSnippet = [
|
||||
{
|
||||
@@ -226,6 +241,7 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.on('blur', this.blurHandler)
|
||||
this.editor.on('change', this.changeHandler)
|
||||
this.editor.on('paste', this.pasteHandler)
|
||||
this.editor.on('contextmenu', this.contextMenuHandler)
|
||||
eventEmitter.on('top:search', this.searchHandler)
|
||||
|
||||
eventEmitter.emit('code:init')
|
||||
@@ -242,6 +258,10 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
this.textEditorInterface = new TextEditorInterface(this.editor)
|
||||
this.tableEditor = new TableEditor(this.textEditorInterface)
|
||||
if (this.props.spellCheck) {
|
||||
this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'})
|
||||
}
|
||||
|
||||
eventEmitter.on('code:format-table', this.formatTable)
|
||||
|
||||
this.tableEditorOptions = options({
|
||||
@@ -311,22 +331,28 @@ export default class CodeEditor extends React.Component {
|
||||
const snippetLines = snippets[i].content.split('\n')
|
||||
let cursorLineNumber = 0
|
||||
let cursorLinePosition = 0
|
||||
|
||||
let cursorIndex
|
||||
for (let j = 0; j < snippetLines.length; j++) {
|
||||
const cursorIndex = snippetLines[j].indexOf(templateCursorString)
|
||||
cursorIndex = snippetLines[j].indexOf(templateCursorString)
|
||||
|
||||
if (cursorIndex !== -1) {
|
||||
cursorLineNumber = j
|
||||
cursorLinePosition = cursorIndex
|
||||
cm.replaceRange(
|
||||
snippets[i].content.replace(templateCursorString, ''),
|
||||
wordBeforeCursor.range.from,
|
||||
wordBeforeCursor.range.to
|
||||
)
|
||||
cm.setCursor({
|
||||
line: cursor.line + cursorLineNumber,
|
||||
ch: cursorLinePosition
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cm.replaceRange(
|
||||
snippets[i].content.replace(templateCursorString, ''),
|
||||
wordBeforeCursor.range.from,
|
||||
wordBeforeCursor.range.to
|
||||
)
|
||||
cm.setCursor({
|
||||
line: cursor.line + cursorLineNumber,
|
||||
ch: cursorLinePosition + cursor.ch - wordBeforeCursor.text.length
|
||||
})
|
||||
} else {
|
||||
cm.replaceRange(
|
||||
snippets[i].content,
|
||||
@@ -383,9 +409,11 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.off('paste', this.pasteHandler)
|
||||
eventEmitter.off('top:search', this.searchHandler)
|
||||
this.editor.off('scroll', this.scrollHandler)
|
||||
this.editor.off('contextmenu', this.contextMenuHandler)
|
||||
const editorTheme = document.getElementById('editorTheme')
|
||||
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
||||
|
||||
spellcheck.setLanguage(null, spellcheck.SPELLCHECK_DISABLED)
|
||||
eventEmitter.off('code:format-table', this.formatTable)
|
||||
}
|
||||
|
||||
@@ -453,6 +481,16 @@ export default class CodeEditor extends React.Component {
|
||||
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) {
|
||||
this.editor.refresh()
|
||||
}
|
||||
@@ -466,16 +504,23 @@ export default class CodeEditor extends React.Component {
|
||||
CodeMirror.autoLoadMode(this.editor, syntax.mode)
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
this.value = this.editor.getValue()
|
||||
handleChange (editor, changeObject) {
|
||||
spellcheck.handleChange(editor, changeObject)
|
||||
this.value = editor.getValue()
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(e)
|
||||
this.props.onChange(editor)
|
||||
}
|
||||
}
|
||||
|
||||
moveCursorTo (row, col) {}
|
||||
|
||||
scrollToLine (num) {}
|
||||
scrollToLine (event, num) {
|
||||
const cursor = {
|
||||
line: num,
|
||||
ch: 1
|
||||
}
|
||||
this.editor.setCursor(cursor)
|
||||
}
|
||||
|
||||
focus () {
|
||||
this.editor.focus()
|
||||
@@ -538,7 +583,11 @@ export default class CodeEditor extends React.Component {
|
||||
)
|
||||
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(
|
||||
this,
|
||||
storageKey,
|
||||
@@ -608,6 +657,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) {
|
||||
return this.decodeResponse(response).then(body => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -690,6 +745,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 = {
|
||||
@@ -700,7 +774,8 @@ CodeEditor.propTypes = {
|
||||
className: PropTypes.string,
|
||||
onBlur: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
readOnly: PropTypes.bool
|
||||
readOnly: PropTypes.bool,
|
||||
spellCheck: PropTypes.bool
|
||||
}
|
||||
|
||||
CodeEditor.defaultProps = {
|
||||
@@ -710,5 +785,6 @@ CodeEditor.defaultProps = {
|
||||
fontSize: 14,
|
||||
fontFamily: 'Monaco, Consolas',
|
||||
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 eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import { findStorage } from 'browser/lib/findStorage'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
|
||||
class MarkdownEditor extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -18,7 +19,7 @@ class MarkdownEditor extends React.Component {
|
||||
this.supportMdSelectionBold = [16, 17, 186]
|
||||
|
||||
this.state = {
|
||||
status: 'PREVIEW',
|
||||
status: props.config.editor.switchPreview === 'RIGHTCLICK' ? props.config.editor.delfaultStatus : 'PREVIEW',
|
||||
renderValue: props.value,
|
||||
keyPressed: new Set(),
|
||||
isLocked: false
|
||||
@@ -64,6 +65,10 @@ class MarkdownEditor extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
setValue (value) {
|
||||
this.refs.code.setValue(value)
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
this.value = this.refs.code.value
|
||||
this.props.onChange(e)
|
||||
@@ -72,9 +77,7 @@ class MarkdownEditor extends React.Component {
|
||||
handleContextMenu (e) {
|
||||
const { config } = this.props
|
||||
if (config.editor.switchPreview === 'RIGHTCLICK') {
|
||||
const newStatus = this.state.status === 'PREVIEW'
|
||||
? 'CODE'
|
||||
: 'PREVIEW'
|
||||
const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW'
|
||||
this.setState({
|
||||
status: newStatus
|
||||
}, () => {
|
||||
@@ -84,6 +87,10 @@ class MarkdownEditor extends React.Component {
|
||||
this.refs.preview.focus()
|
||||
}
|
||||
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.stopPropagation()
|
||||
const idMatch = /checkbox-([0-9]+)/
|
||||
const checkedMatch = /\[x\]/i
|
||||
const uncheckedMatch = /\[ \]/
|
||||
const checkedMatch = /^\s*[\+\-\*] \[x\]/i
|
||||
const uncheckedMatch = /^\s*[\+\-\*] \[ \]/
|
||||
const checkReplace = /\[x\]/i
|
||||
const uncheckReplace = /\[ \]/
|
||||
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||
const lines = this.refs.code.value
|
||||
@@ -150,10 +159,10 @@ class MarkdownEditor extends React.Component {
|
||||
const targetLine = lines[lineIndex]
|
||||
|
||||
if (targetLine.match(checkedMatch)) {
|
||||
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
|
||||
lines[lineIndex] = targetLine.replace(checkReplace, '[ ]')
|
||||
}
|
||||
if (targetLine.match(uncheckedMatch)) {
|
||||
lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]')
|
||||
lines[lineIndex] = targetLine.replace(uncheckReplace, '[x]')
|
||||
}
|
||||
this.refs.code.setValue(lines.join('\n'))
|
||||
}
|
||||
@@ -250,7 +259,7 @@ class MarkdownEditor extends React.Component {
|
||||
: 'codeEditor--hide'
|
||||
}
|
||||
ref='code'
|
||||
mode='GitHub Flavored Markdown'
|
||||
mode='Boost Flavored Markdown'
|
||||
value={value}
|
||||
theme={config.editor.theme}
|
||||
keyMap={config.editor.keyMap}
|
||||
@@ -268,6 +277,7 @@ class MarkdownEditor extends React.Component {
|
||||
enableTableEditor={config.editor.enableTableEditor}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
onBlur={(e) => this.handleBlur(e)}
|
||||
spellCheck={config.editor.spellcheck}
|
||||
/>
|
||||
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
|
||||
? 'preview'
|
||||
@@ -300,6 +310,7 @@ class MarkdownEditor extends React.Component {
|
||||
noteKey={noteKey}
|
||||
customCSS={config.preview.customCSS}
|
||||
allowCustomCSS={config.preview.allowCustomCSS}
|
||||
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
.preview
|
||||
display block
|
||||
absolute top bottom left right
|
||||
z-index 100
|
||||
background-color white
|
||||
height 100%
|
||||
width 100%
|
||||
|
||||
@@ -17,9 +17,11 @@ import copy from 'copy-to-clipboard'
|
||||
import mdurl from 'mdurl'
|
||||
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
||||
import { escapeHtmlCharacters } from 'browser/lib/utils'
|
||||
import 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, shell } = require('electron')
|
||||
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
|
||||
@@ -80,7 +82,6 @@ function buildStyle (
|
||||
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
|
||||
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
|
||||
}
|
||||
${allowCustomCSS ? customCSS : ''}
|
||||
${markdownStyle}
|
||||
|
||||
body {
|
||||
@@ -88,6 +89,11 @@ body {
|
||||
font-size: ${fontSize}px;
|
||||
${scrollPastEnd && 'padding-bottom: 90vh;'}
|
||||
}
|
||||
@media print {
|
||||
body {
|
||||
padding-bottom: initial;
|
||||
}
|
||||
}
|
||||
code {
|
||||
font-family: '${codeBlockFontFamily.join("','")}';
|
||||
background-color: rgba(0,0,0,0.04);
|
||||
@@ -144,6 +150,8 @@ body p {
|
||||
display: none
|
||||
}
|
||||
}
|
||||
|
||||
${allowCustomCSS ? customCSS : ''}
|
||||
`
|
||||
}
|
||||
|
||||
@@ -256,6 +264,10 @@ export default class MarkdownPreview extends React.Component {
|
||||
}
|
||||
|
||||
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) {
|
||||
switch (e.target.tagName) {
|
||||
case 'A':
|
||||
@@ -325,9 +337,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
allowCustomCSS,
|
||||
customCSS
|
||||
)
|
||||
let body = this.markdown.render(
|
||||
escapeHtmlCharacters(noteContent, { detectCodeBlock: true })
|
||||
)
|
||||
let body = this.markdown.render(noteContent)
|
||||
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
|
||||
noteContent,
|
||||
@@ -425,6 +435,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
case 'dark':
|
||||
case 'solarized-dark':
|
||||
case 'monokai':
|
||||
case 'dracula':
|
||||
return scrollBarDarkStyle
|
||||
default:
|
||||
return scrollBarStyle
|
||||
@@ -526,7 +537,8 @@ export default class MarkdownPreview extends React.Component {
|
||||
prevProps.smartQuotes !== this.props.smartQuotes ||
|
||||
prevProps.sanitize !== this.props.sanitize ||
|
||||
prevProps.smartArrows !== this.props.smartArrows ||
|
||||
prevProps.breaks !== this.props.breaks
|
||||
prevProps.breaks !== this.props.breaks ||
|
||||
prevProps.lineThroughCheckbox !== this.props.lineThroughCheckbox
|
||||
) {
|
||||
this.initMarkdown()
|
||||
this.rewriteIframe()
|
||||
@@ -732,7 +744,6 @@ export default class MarkdownPreview extends React.Component {
|
||||
el.addEventListener('click', this.linkClickHandler)
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
el.className = 'flowchart-error'
|
||||
el.innerHTML = 'Flowchart parse error: ' + e.message
|
||||
}
|
||||
@@ -753,7 +764,6 @@ export default class MarkdownPreview extends React.Component {
|
||||
el.addEventListener('click', this.linkClickHandler)
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
el.className = 'sequence-error'
|
||||
el.innerHTML = 'Sequence diagram parse error: ' + e.message
|
||||
}
|
||||
@@ -764,14 +774,21 @@ export default class MarkdownPreview extends React.Component {
|
||||
this.refs.root.contentWindow.document.querySelectorAll('.chart'),
|
||||
el => {
|
||||
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 = ''
|
||||
var canvas = document.createElement('canvas')
|
||||
|
||||
const canvas = document.createElement('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) {
|
||||
console.error(e)
|
||||
el.className = 'chart-error'
|
||||
el.innerHTML = 'chartjs diagram parse error: ' + e.message
|
||||
}
|
||||
@@ -855,6 +872,15 @@ export default class MarkdownPreview extends React.Component {
|
||||
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
|
||||
// e.g.
|
||||
// 877f99c3268608328037-1c211eb7dcb463de6490
|
||||
|
||||
@@ -20,12 +20,18 @@ class MarkdownSplitEditor extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
setValue (value) {
|
||||
this.refs.code.setValue(value)
|
||||
}
|
||||
|
||||
handleOnChange () {
|
||||
this.value = this.refs.code.value
|
||||
this.props.onChange()
|
||||
}
|
||||
|
||||
handleScroll (e) {
|
||||
if (!this.props.config.preview.scrollSync) return
|
||||
|
||||
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
|
||||
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
||||
let srcTop, srcHeight, targetTop, targetHeight
|
||||
@@ -72,8 +78,10 @@ class MarkdownSplitEditor extends React.Component {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const idMatch = /checkbox-([0-9]+)/
|
||||
const checkedMatch = /\[x\]/i
|
||||
const uncheckedMatch = /\[ \]/
|
||||
const checkedMatch = /^\s*[\+\-\*] \[x\]/i
|
||||
const uncheckedMatch = /^\s*[\+\-\*] \[ \]/
|
||||
const checkReplace = /\[x\]/i
|
||||
const uncheckReplace = /\[ \]/
|
||||
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||
const lines = this.refs.code.value
|
||||
@@ -82,10 +90,10 @@ class MarkdownSplitEditor extends React.Component {
|
||||
const targetLine = lines[lineIndex]
|
||||
|
||||
if (targetLine.match(checkedMatch)) {
|
||||
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
|
||||
lines[lineIndex] = targetLine.replace(checkReplace, '[ ]')
|
||||
}
|
||||
if (targetLine.match(uncheckedMatch)) {
|
||||
lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]')
|
||||
lines[lineIndex] = targetLine.replace(uncheckReplace, '[x]')
|
||||
}
|
||||
this.refs.code.setValue(lines.join('\n'))
|
||||
}
|
||||
@@ -145,7 +153,7 @@ class MarkdownSplitEditor extends React.Component {
|
||||
styleName='codeEditor'
|
||||
ref='code'
|
||||
width={this.state.codeEditorWidthInPercent + '%'}
|
||||
mode='GitHub Flavored Markdown'
|
||||
mode='Boost Flavored Markdown'
|
||||
value={value}
|
||||
theme={config.editor.theme}
|
||||
keyMap={config.editor.keyMap}
|
||||
@@ -163,6 +171,7 @@ class MarkdownSplitEditor extends React.Component {
|
||||
noteKey={noteKey}
|
||||
onChange={this.handleOnChange.bind(this)}
|
||||
onScroll={this.handleScroll.bind(this)}
|
||||
spellCheck={config.editor.spellcheck}
|
||||
/>
|
||||
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
||||
<div styleName='slider-hitbox' />
|
||||
@@ -192,6 +201,7 @@ class MarkdownSplitEditor extends React.Component {
|
||||
noteKey={noteKey}
|
||||
customCSS={config.preview.customCSS}
|
||||
allowCustomCSS={config.preview.allowCustomCSS}
|
||||
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -24,16 +24,19 @@ const TagElement = ({ tagName }) => (
|
||||
/**
|
||||
* @description Tag element list component.
|
||||
* @param {Array|null} tags
|
||||
* @param {boolean} showTagsAlphabetically
|
||||
* @return {React.Component}
|
||||
*/
|
||||
const TagElementList = tags => {
|
||||
const TagElementList = (tags, showTagsAlphabetically) => {
|
||||
if (!isArray(tags)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const tagElements = tags.map(tag => TagElement({ tagName: tag }))
|
||||
|
||||
return tagElements
|
||||
if (showTagsAlphabetically) {
|
||||
return _.sortBy(tags).map(tag => TagElement({ tagName: tag }))
|
||||
} else {
|
||||
return tags.map(tag => TagElement({ tagName: tag }))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,7 +58,8 @@ const NoteItem = ({
|
||||
pathname,
|
||||
storageName,
|
||||
folderName,
|
||||
viewType
|
||||
viewType,
|
||||
showTagsAlphabetically
|
||||
}) => (
|
||||
<div
|
||||
styleName={isActive ? 'item--active' : 'item'}
|
||||
@@ -93,7 +97,7 @@ const NoteItem = ({
|
||||
<div styleName='item-bottom'>
|
||||
<div styleName='item-bottom-tagList'>
|
||||
{note.tags.length > 0
|
||||
? TagElementList(note.tags)
|
||||
? TagElementList(note.tags, showTagsAlphabetically)
|
||||
: <span
|
||||
style={{ fontStyle: 'italic', opacity: 0.5 }}
|
||||
styleName='item-bottom-tagList-empty'
|
||||
|
||||
@@ -394,3 +394,76 @@ body[data-theme="monokai"]
|
||||
.item-bottom-tagList-empty
|
||||
color $ui-inactive-text-color
|
||||
vertical-align middle
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
border-color $ui-dracula-borderColor
|
||||
background-color $ui-dracula-noteList-backgroundColor
|
||||
|
||||
.item
|
||||
border-color $ui-dracula-borderColor
|
||||
background-color $ui-dracula-noteList-backgroundColor
|
||||
&:hover
|
||||
transition 0.15s
|
||||
// background-color alpha($ui-dracula-noteList-backgroundColor, 20%)
|
||||
color $ui-dracula-text-color
|
||||
.item-title
|
||||
.item-title-icon
|
||||
.item-bottom-time
|
||||
transition 0.15s
|
||||
color $ui-dracula-text-color
|
||||
.item-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha($ui-dracula-noteList-backgroundColor, 20%)
|
||||
color $ui-dracula-text-color
|
||||
&:active
|
||||
transition 0.15s
|
||||
background-color $ui-dracula-noteList-backgroundColor
|
||||
color $ui-dracula-text-color
|
||||
.item-title
|
||||
.item-title-icon
|
||||
.item-bottom-time
|
||||
transition 0.15s
|
||||
color $ui-dracula-text-color
|
||||
.item-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha($ui-dracula-noteList-backgroundColor, 10%)
|
||||
color $ui-dracula-text-color
|
||||
|
||||
.item-wrapper
|
||||
border-color alpha($ui-dracula-button-backgroundColor, 60%)
|
||||
|
||||
.item--active
|
||||
border-color $ui-dracula-borderColor
|
||||
background-color $ui-dracula-button-backgroundColor
|
||||
.item-wrapper
|
||||
border-color transparent
|
||||
.item-title
|
||||
.item-title-icon
|
||||
.item-bottom-time
|
||||
color $ui-dracula-active-color
|
||||
.item-bottom-tagList-item
|
||||
background-color alpha(#f8f8f2, 10%)
|
||||
color $ui-dracula-text-color
|
||||
&:hover
|
||||
// background-color alpha($ui-dracula-button--active-backgroundColor, 60%)
|
||||
color #ff79c6
|
||||
.item-bottom-tagList-item
|
||||
background-color alpha(#f8f8f2, 20%)
|
||||
|
||||
.item-title
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.item-title-icon
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.item-title-empty
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.item-bottom-tagList-item
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.item-bottom-tagList-empty
|
||||
color $ui-inactive-text-color
|
||||
vertical-align middle
|
||||
@@ -286,3 +286,67 @@ body[data-theme="monokai"]
|
||||
.item-simple-right-storageName
|
||||
padding-left 4px
|
||||
opacity 0.4
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
border-color $ui-dracula-borderColor
|
||||
background-color $ui-dracula-noteList-backgroundColor
|
||||
|
||||
.item-simple
|
||||
border-color $ui-dracula-borderColor
|
||||
background-color $ui-dracula-noteList-backgroundColor
|
||||
&:hover
|
||||
transition 0.15s
|
||||
background-color alpha($ui-dracula-button-backgroundColor, 60%)
|
||||
color $ui-dracula-text-color
|
||||
.item-simple-title
|
||||
.item-simple-title-empty
|
||||
.item-simple-title-icon
|
||||
.item-simple-bottom-time
|
||||
transition 0.15s
|
||||
color $ui-dracula-text-color
|
||||
.item-simple-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha(#f8f8f2, 20%)
|
||||
color $ui-dracula-text-color
|
||||
&:active
|
||||
transition 0.15s
|
||||
background-color $ui-dracula-button--active-backgroundColor
|
||||
color $ui-dracula-text-color
|
||||
.item-simple-title
|
||||
.item-simple-title-empty
|
||||
.item-simple-title-icon
|
||||
.item-simple-bottom-time
|
||||
transition 0.15s
|
||||
color $ui-dracula-text-color
|
||||
.item-simple-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha(#f8f8f2, 10%)
|
||||
color $ui-dracula-text-color
|
||||
|
||||
.item-simple--active
|
||||
border-color $ui-dracula-borderColor
|
||||
background-color $ui-dracula-button--active-backgroundColor
|
||||
.item-simple-wrapper
|
||||
border-color transparent
|
||||
.item-simple-title
|
||||
.item-simple-title-empty
|
||||
.item-simple-title-icon
|
||||
.item-simple-bottom-time
|
||||
color $ui-dracula-text-color
|
||||
.item-simple-bottom-tagList-item
|
||||
background-color alpha(#f8f8f2, 10%)
|
||||
color $ui-dracula-text-color
|
||||
&:hover
|
||||
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||
color #c0392b
|
||||
.item-simple-bottom-tagList-item
|
||||
background-color alpha(#f8f8f2, 20%)
|
||||
.item-simple-title
|
||||
color $ui-dark-text-color
|
||||
border-bottom $ui-dark-borderColor
|
||||
.item-simple-right
|
||||
float right
|
||||
.item-simple-right-storageName
|
||||
padding-left 4px
|
||||
opacity 0.4
|
||||
@@ -51,4 +51,15 @@ body[data-theme="monokai"]
|
||||
border none
|
||||
background-color $ui-monokai-button-backgroundColor
|
||||
&: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
|
||||
color $ui-monokai-text-color
|
||||
.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
|
||||
|
||||
.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
|
||||
&:hover
|
||||
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'
|
||||
|
||||
/**
|
||||
* @param {Array} storgaeList
|
||||
* @param {Array} storageList
|
||||
*/
|
||||
|
||||
const StorageList = ({storageList, isFolded}) => (
|
||||
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
|
||||
{storageList.length > 0 ? storageList : (
|
||||
<div styleName='storgaeList-empty'>No storage mount.</div>
|
||||
<div styleName='storageList-empty'>No storage mount.</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
StorageList.propTypes = {
|
||||
storgaeList: PropTypes.arrayOf(PropTypes.element).isRequired
|
||||
storageList: PropTypes.arrayOf(PropTypes.element).isRequired
|
||||
}
|
||||
export default CSSModules(StorageList, styles)
|
||||
|
||||
@@ -14,8 +14,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
* @param {bool} isRelated
|
||||
*/
|
||||
|
||||
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isActive, isRelated, count}) => (
|
||||
<div styleName='tagList-itemContainer'>
|
||||
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count}) => (
|
||||
<div styleName='tagList-itemContainer' onContextMenu={e => handleContextMenu(e, name)}>
|
||||
{isRelated
|
||||
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
|
||||
<i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
|
||||
@@ -25,7 +25,7 @@ const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isAc
|
||||
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
|
||||
<span styleName='tagList-item-name'>
|
||||
{`# ${name}`}
|
||||
<span styleName='tagList-item-count'>{count}</span>
|
||||
<span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ import styles from './TodoListPercentage.styl'
|
||||
*/
|
||||
|
||||
const TodoListPercentage = ({
|
||||
percentageOfTodo
|
||||
percentageOfTodo, onClearCheckboxClick
|
||||
}) => (
|
||||
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
|
||||
<div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}>
|
||||
@@ -20,11 +20,15 @@ const TodoListPercentage = ({
|
||||
<p styleName='percentageText'>{percentageOfTodo}%</p>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='todoClear'>
|
||||
<p styleName='todoClearText' onClick={(e) => onClearCheckboxClick(e)}>clear</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
TodoListPercentage.propTypes = {
|
||||
percentageOfTodo: PropTypes.number.isRequired
|
||||
percentageOfTodo: PropTypes.number.isRequired,
|
||||
onClearCheckboxClick: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(TodoListPercentage, styles)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
.percentageBar
|
||||
display: flex
|
||||
position absolute
|
||||
top 72px
|
||||
right 0px
|
||||
@@ -30,6 +31,20 @@
|
||||
color #f4f4f4
|
||||
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"]
|
||||
.percentageBar
|
||||
background-color #444444
|
||||
@@ -39,6 +54,9 @@ body[data-theme="dark"]
|
||||
|
||||
.percentageText
|
||||
color $ui-dark-text-color
|
||||
|
||||
.todoClearText
|
||||
color $ui-dark-text-color
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.percentageBar
|
||||
@@ -50,6 +68,9 @@ body[data-theme="solarized-dark"]
|
||||
.percentageText
|
||||
color #fdf6e3
|
||||
|
||||
.todoClearText
|
||||
color #fdf6e3
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.percentageBar
|
||||
background-color: $ui-monokai-borderColor
|
||||
@@ -58,4 +79,17 @@ body[data-theme="monokai"]
|
||||
background-color $ui-monokai-active-color
|
||||
|
||||
.percentageText
|
||||
color $ui-monokai-text-color
|
||||
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
|
||||
text-decoration line-through
|
||||
opacity 0.5
|
||||
&.taskListItem.checked
|
||||
text-decoration line-through
|
||||
opacity 0.5
|
||||
div.math-rendered
|
||||
text-align center
|
||||
.math-failed
|
||||
@@ -206,41 +209,39 @@ code
|
||||
text-decoration none
|
||||
margin-right 2px
|
||||
pre
|
||||
padding 0.5em !important
|
||||
padding 0.5rem !important
|
||||
border solid 1px #D1D1D1
|
||||
border-radius 5px
|
||||
overflow-x auto
|
||||
margin 0 0 1em
|
||||
margin 0 0 1rem
|
||||
display flex
|
||||
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
|
||||
background-color inherit
|
||||
margin 0
|
||||
padding 0
|
||||
border none
|
||||
border-radius 0
|
||||
&.CodeMirror
|
||||
height initial
|
||||
flex-wrap wrap
|
||||
&>code
|
||||
flex 1
|
||||
overflow-x auto
|
||||
&.mermaid svg
|
||||
max-width 100% !important
|
||||
&>span.filename
|
||||
width 100%
|
||||
border-radius: 5px 0px 0px 0px
|
||||
margin -8px 100% 8px -8px
|
||||
padding 0px 6px
|
||||
margin -0.5rem 100% 0.5rem -0.5rem
|
||||
padding 0.125rem 0.375rem
|
||||
background-color #777;
|
||||
color white
|
||||
&:empty
|
||||
display none
|
||||
&>span.lineNumber
|
||||
display none
|
||||
font-size 1em
|
||||
padding 0.5em 0
|
||||
margin -0.5em 0.5em -0.5em -0.5em
|
||||
padding 0.5rem 0
|
||||
margin -0.5rem 0.5rem -0.5rem -0.5rem
|
||||
border-right 1px solid
|
||||
text-align right
|
||||
border-top-left-radius 4px
|
||||
@@ -361,7 +362,7 @@ for name, val in admonition_types
|
||||
.admonition.{name}
|
||||
@extend $admonition
|
||||
border-left-color: val[color]
|
||||
|
||||
|
||||
.admonition.{name}>.admonition-title
|
||||
@extend $admonition-title
|
||||
border-bottom-color: .1rem solid rgba(val[color], 0.2)
|
||||
@@ -372,6 +373,49 @@ for name, val in admonition_types
|
||||
color: val[color]
|
||||
content: val[icon]
|
||||
|
||||
dl
|
||||
margin 2rem 0
|
||||
padding 0
|
||||
display flex
|
||||
width 100%
|
||||
flex-wrap wrap
|
||||
align-items flex-start
|
||||
border-bottom 1px solid borderColor
|
||||
background-color tableHeadBgColor
|
||||
|
||||
dt
|
||||
border-top 1px solid borderColor
|
||||
font-weight bold
|
||||
text-align right
|
||||
overflow hidden
|
||||
flex-basis 20%
|
||||
padding 0.4rem 0.9rem
|
||||
box-sizing border-box
|
||||
|
||||
dd
|
||||
border-top 1px solid borderColor
|
||||
flex-basis 80%
|
||||
padding 0.4rem 0.9rem
|
||||
min-height 2.5rem
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
box-sizing border-box
|
||||
|
||||
dd + dd
|
||||
margin-left 20%
|
||||
|
||||
pre.fence
|
||||
flex-wrap wrap
|
||||
|
||||
.chart, .flowchart, .mermaid, .sequence
|
||||
display flex
|
||||
justify-content center
|
||||
background-color white
|
||||
max-width 100%
|
||||
flex-grow 1
|
||||
|
||||
canvas, svg
|
||||
max-width 100% !important
|
||||
|
||||
themeDarkBackground = darken(#21252B, 10%)
|
||||
themeDarkText = #f9f9f9
|
||||
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
||||
@@ -422,6 +466,14 @@ body[data-theme="dark"]
|
||||
kbd
|
||||
background-color themeDarkBorder
|
||||
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
|
||||
themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%)
|
||||
@@ -449,6 +501,14 @@ body[data-theme="solarized-dark"]
|
||||
border-color themeSolarizedDarkTableBorder
|
||||
&:last-child
|
||||
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
|
||||
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
|
||||
@@ -477,4 +537,49 @@ body[data-theme="monokai"]
|
||||
&:last-child
|
||||
border-right solid 1px themeMonokaiTableBorder
|
||||
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
|
||||
const darkThemeStyling = `
|
||||
.loopText tspan {
|
||||
fill: white;
|
||||
.loopText tspan {
|
||||
fill: white;
|
||||
}`
|
||||
|
||||
function getRandomInt (min, max) {
|
||||
@@ -11,9 +11,9 @@ function getRandomInt (min, max) {
|
||||
}
|
||||
|
||||
function getId () {
|
||||
var pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
var id = 'm-'
|
||||
for (var i = 0; i < 7; i++) {
|
||||
const pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
let id = 'm-'
|
||||
for (let i = 0; i < 7; i++) {
|
||||
id += pool[getRandomInt(0, 16)]
|
||||
}
|
||||
return id
|
||||
@@ -21,16 +21,20 @@ function getId () {
|
||||
|
||||
function render (element, content, theme) {
|
||||
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({
|
||||
theme: isDarkTheme ? 'dark' : 'default',
|
||||
themeCSS: isDarkTheme ? darkThemeStyling : ''
|
||||
themeCSS: isDarkTheme ? darkThemeStyling : '',
|
||||
useMaxWidth: false
|
||||
})
|
||||
mermaidAPI.render(getId(), content, (svgGraph) => {
|
||||
element.innerHTML = svgGraph
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
element.className = 'mermaid-error'
|
||||
element.innerHTML = 'mermaid diagram parse error: ' + e.message
|
||||
}
|
||||
|
||||
@@ -48,8 +48,12 @@ const languages = [
|
||||
locale: 'pl'
|
||||
},
|
||||
{
|
||||
name: 'Portuguese',
|
||||
locale: 'pt'
|
||||
name: 'Portuguese (PT-BR)',
|
||||
locale: 'pt-BR'
|
||||
},
|
||||
{
|
||||
name: 'Portuguese (PT-PT)',
|
||||
locale: 'pt-PT'
|
||||
},
|
||||
{
|
||||
name: 'Russian',
|
||||
@@ -61,6 +65,9 @@ const languages = [
|
||||
}, {
|
||||
name: 'Turkish',
|
||||
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')
|
||||
let title = null
|
||||
let isInsideCodeBlock = false
|
||||
@@ -6,6 +6,11 @@ export function findNoteTitle (value) {
|
||||
if (splitted[0] === '---') {
|
||||
let line = 0
|
||||
while (++line < splitted.length) {
|
||||
if (enableFrontMatterTitle && splitted[line].startsWith(frontMatterTitleField + ':')) {
|
||||
title = splitted[line].substring(frontMatterTitleField.length + 1).trim()
|
||||
|
||||
break
|
||||
}
|
||||
if (splitted[line] === '---') {
|
||||
splitted.splice(0, line + 1)
|
||||
|
||||
@@ -14,17 +19,19 @@ export function findNoteTitle (value) {
|
||||
}
|
||||
}
|
||||
|
||||
splitted.some((line, index) => {
|
||||
const trimmedLine = line.trim()
|
||||
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
|
||||
if (trimmedLine.match('```')) {
|
||||
isInsideCodeBlock = !isInsideCodeBlock
|
||||
}
|
||||
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
|
||||
title = trimmedLine
|
||||
return true
|
||||
}
|
||||
})
|
||||
if (title === null) {
|
||||
splitted.some((line, index) => {
|
||||
const trimmedLine = line.trim()
|
||||
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
|
||||
if (trimmedLine.match('```')) {
|
||||
isInsideCodeBlock = !isInsideCodeBlock
|
||||
}
|
||||
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
|
||||
title = trimmedLine
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (title === null) {
|
||||
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 { escapeHtmlCharacters } from './utils'
|
||||
import url from 'url'
|
||||
|
||||
module.exports = function sanitizePlugin (md, options) {
|
||||
options = options || {}
|
||||
@@ -14,7 +15,7 @@ module.exports = function sanitizePlugin (md, options) {
|
||||
options
|
||||
)
|
||||
}
|
||||
if (state.tokens[tokenIdx].type === 'fence') {
|
||||
if (state.tokens[tokenIdx].type === '_fence') {
|
||||
// escapeHtmlCharacters has better performance
|
||||
state.tokens[tokenIdx].content = escapeHtmlCharacters(
|
||||
state.tokens[tokenIdx].content,
|
||||
@@ -25,7 +26,7 @@ module.exports = function sanitizePlugin (md, options) {
|
||||
const inlineTokens = state.tokens[tokenIdx].children
|
||||
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
|
||||
if (inlineTokens[childIdx].type === 'html_inline') {
|
||||
inlineTokens[childIdx].content = sanitizeHtml(
|
||||
inlineTokens[childIdx].content = sanitizeInline(
|
||||
inlineTokens[childIdx].content,
|
||||
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 diacritics from 'diacritics-map'
|
||||
import stripColor from 'strip-color'
|
||||
import mdlink from 'markdown-link'
|
||||
|
||||
const EOL = require('os').EOL
|
||||
|
||||
@@ -42,6 +43,12 @@ function caseSensitiveSlugify (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_END = '<!-- tocstop -->'
|
||||
|
||||
@@ -84,7 +91,7 @@ export function generateInEditor (editor) {
|
||||
* @returns generatedTOC String containing generated TOC
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import _ from 'lodash'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import katex from 'katex'
|
||||
import { lastFindInArray } from './utils'
|
||||
import anchor from '@enyaxu/markdown-it-anchor'
|
||||
|
||||
function createGutter (str, firstLineNumber) {
|
||||
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
||||
@@ -27,32 +28,6 @@ class Markdown {
|
||||
html: true,
|
||||
xhtmlOut: true,
|
||||
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'
|
||||
}
|
||||
|
||||
@@ -105,7 +80,11 @@ class Markdown {
|
||||
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
||||
'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,19 +118,60 @@ class Markdown {
|
||||
this.md.use(require('markdown-it-imsize'))
|
||||
this.md.use(require('markdown-it-footnote'))
|
||||
this.md.use(require('markdown-it-multimd-table'))
|
||||
this.md.use(require('markdown-it-named-headers'), {
|
||||
slugify: (header) => {
|
||||
return encodeURI(header.trim()
|
||||
this.md.use(anchor, {
|
||||
slugify: (title) => {
|
||||
var slug = encodeURI(title.trim()
|
||||
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
|
||||
.replace(/\s+/g, '-'))
|
||||
.replace(/\-+$/, '')
|
||||
return slug
|
||||
}
|
||||
})
|
||||
this.md.use(require('markdown-it-kbd'))
|
||||
|
||||
this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error']})
|
||||
this.md.use(require('markdown-it-abbr'))
|
||||
this.md.use(require('markdown-it-sub'))
|
||||
this.md.use(require('markdown-it-sup'))
|
||||
this.md.use(require('./markdown-it-deflist'))
|
||||
this.md.use(require('./markdown-it-frontmatter'))
|
||||
|
||||
this.md.use(require('./markdown-it-fence'), {
|
||||
chart: token => {
|
||||
if (token.parameters.hasOwnProperty('yaml')) {
|
||||
token.parameters.format = 'yaml'
|
||||
}
|
||||
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="chart" data-height="${token.parameters.height}" data-format="${token.parameters.format || 'json'}">${token.content}</div>
|
||||
</pre>`
|
||||
},
|
||||
flowchart: token => {
|
||||
return `<pre class="fence" data-line="${token.map[0]}">
|
||||
<span class="filename">${token.fileName}</span>
|
||||
<div class="flowchart" data-height="${token.parameters.height}">${token.content}</div>
|
||||
</pre>`
|
||||
},
|
||||
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')
|
||||
this.md.use(require('markdown-it-plantuml'), '', {
|
||||
generateSource: function (umlCode) {
|
||||
@@ -223,7 +243,11 @@ class Markdown {
|
||||
if (!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>`
|
||||
}
|
||||
@@ -248,9 +272,12 @@ class Markdown {
|
||||
this.md.renderer.render = (tokens, options, env) => {
|
||||
tokens.forEach((token) => {
|
||||
switch (token.type) {
|
||||
case 'heading_open':
|
||||
case 'paragraph_open':
|
||||
case 'blockquote_open':
|
||||
case 'dd_open':
|
||||
case 'dt_open':
|
||||
case 'heading_open':
|
||||
case 'list_item_open':
|
||||
case 'paragraph_open':
|
||||
case 'table_open':
|
||||
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 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_ALLNOTE')
|
||||
|
||||
let tags = []
|
||||
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
|
||||
tags = params.tagname.split(' ')
|
||||
}
|
||||
|
||||
return dataApi
|
||||
.createNote(storage, {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
folder: folder,
|
||||
title: '',
|
||||
tags,
|
||||
content: ''
|
||||
})
|
||||
.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_ALLNOTE')
|
||||
|
||||
let tags = []
|
||||
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
|
||||
tags = params.tagname.split(' ')
|
||||
}
|
||||
|
||||
return dataApi
|
||||
.createNote(storage, {
|
||||
type: 'SNIPPET_NOTE',
|
||||
folder: folder,
|
||||
title: '',
|
||||
tags,
|
||||
description: '',
|
||||
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
|
||||
.empty-message
|
||||
color $ui-dark-inactive-text-color
|
||||
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
@@ -37,3 +37,10 @@ body[data-theme="monokai"]
|
||||
border-left 1px solid $ui-monokai-borderColor
|
||||
.empty-message
|
||||
color $ui-monokai-text-color
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
border-left 1px solid $ui-dracula-borderColor
|
||||
.empty-message
|
||||
color $ui-dracula-text-color
|
||||
@@ -36,7 +36,7 @@
|
||||
height 34px
|
||||
width 20px
|
||||
line-height 34px
|
||||
|
||||
|
||||
.search-input
|
||||
vertical-align middle
|
||||
position relative
|
||||
@@ -71,7 +71,7 @@
|
||||
overflow ellipsis
|
||||
cursor pointer
|
||||
&:hover
|
||||
background-color $ui-button--hover-backgroundColor
|
||||
background-color $ui-button--hover-backgroundColor
|
||||
|
||||
.search-optionList-item--active
|
||||
@extend .search-optionList-item
|
||||
@@ -159,3 +159,29 @@ body[data-theme="monokai"]
|
||||
color $ui-monokai-button--active-color
|
||||
.search-optionList-item-name-surfix
|
||||
color $ui-monokai-inactive-text-color
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
color $ui-dracula-text-color
|
||||
&:hover
|
||||
color #f8f8f2
|
||||
background-color $ui-dark-button--hover-backgroundColor
|
||||
border-color $ui-dracula-borderColor
|
||||
|
||||
.search-optionList
|
||||
color #f8f8f2
|
||||
border-color $ui-dracula-borderColor
|
||||
background-color $ui-dracula-button-backgroundColor
|
||||
|
||||
.search-optionList-item
|
||||
&:hover
|
||||
background-color lighten($ui-dracula-button--hover-backgroundColor, 15%)
|
||||
|
||||
.search-optionList-item--active
|
||||
background-color $ui-dracula-button--active-backgroundColor
|
||||
color $ui-dracula-button--active-color
|
||||
&:hover
|
||||
background-color $ui-dark-button--hover-backgroundColor
|
||||
color $ui-dracula-button--active-color
|
||||
.search-optionList-item-name-surfix
|
||||
color $ui-dracula-inactive-text-color
|
||||
|
||||
@@ -257,3 +257,43 @@ body[data-theme="monokai"]
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
color $ui-monokai-text-color
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.control-infoButton-panel
|
||||
background-color $ui-dracula-noteList-backgroundColor
|
||||
|
||||
.control-infoButton-panel-trash
|
||||
background-color $ui-dracula-noteList-backgroundColor
|
||||
|
||||
.modification-date
|
||||
color $ui-dracula-text-color
|
||||
|
||||
.modification-date-desc
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-defaul-count
|
||||
color $ui-dracula-text-color
|
||||
|
||||
.infoPanel-sub-count
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-default
|
||||
color $ui-dracula-text-color
|
||||
|
||||
.infoPanel-sub
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-noteLink
|
||||
background-color alpha($ui-dracula-borderColor, 20%)
|
||||
color $ui-dracula-text-color
|
||||
|
||||
[id=export-wrap]
|
||||
button
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-dracula-borderColor, 20%)
|
||||
color $ui-dracula-text-color
|
||||
p
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
color $ui-dracula-text-color
|
||||
@@ -61,11 +61,14 @@ class MarkdownNoteDetail extends React.Component {
|
||||
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
||||
this.handleSwitchMode(reversedType)
|
||||
})
|
||||
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
|
||||
ee.on('code:generate-toc', this.generateToc)
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
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()
|
||||
this.setState({
|
||||
note: Object.assign({}, nextProps.note)
|
||||
@@ -91,7 +94,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
handleUpdateContent () {
|
||||
const { note } = this.state
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -187,6 +190,36 @@ class MarkdownNoteDetail extends React.Component {
|
||||
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) {
|
||||
const { note } = this.state
|
||||
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 () {
|
||||
const { config, ignorePreviewPointerEvents } = this.props
|
||||
const { note } = this.state
|
||||
|
||||
if (this.state.editorType === 'EDITOR_PREVIEW') {
|
||||
return <MarkdownEditor
|
||||
ref='content'
|
||||
@@ -321,7 +378,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { data, location } = this.props
|
||||
const { data, location, config } = this.props
|
||||
const { note, editorType } = this.state
|
||||
const storageKey = note.storage
|
||||
const folderKey = note.folder
|
||||
@@ -372,10 +429,12 @@ class MarkdownNoteDetail extends React.Component {
|
||||
<TagSelect
|
||||
ref='tags'
|
||||
value={this.state.note.tags}
|
||||
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
||||
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||
data={data}
|
||||
onChange={this.handleUpdateTag.bind(this)}
|
||||
/>
|
||||
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
||||
<TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
||||
@@ -429,6 +488,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
<div className='NoteDetail'
|
||||
style={this.props.style}
|
||||
styleName='root'
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
>
|
||||
|
||||
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
||||
|
||||
@@ -76,3 +76,8 @@ body[data-theme="monokai"]
|
||||
.root
|
||||
border-left 1px solid $ui-monokai-borderColor
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
border-left 1px solid $ui-dracula-borderColor
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
@@ -98,8 +98,13 @@ body[data-theme="solarized-dark"]
|
||||
.info
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.info
|
||||
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
|
||||
note.description = this.refs.description.value
|
||||
note.updatedAt = new Date()
|
||||
note.title = findNoteTitle(note.description)
|
||||
note.title = findNoteTitle(note.description, false)
|
||||
|
||||
this.setState({
|
||||
note
|
||||
@@ -354,12 +354,10 @@ class SnippetNoteDetail extends React.Component {
|
||||
this.refs['code-' + this.state.snippetIndex].reload()
|
||||
|
||||
if (this.visibleTabs.offsetWidth > this.allTabs.scrollWidth) {
|
||||
console.log('no need for arrows')
|
||||
this.moveTabBarBy(0)
|
||||
} else {
|
||||
const lastTab = this.allTabs.lastChild
|
||||
if (lastTab.offsetLeft + lastTab.offsetWidth < this.visibleTabs.offsetWidth) {
|
||||
console.log('need to scroll')
|
||||
const width = this.visibleTabs.offsetWidth
|
||||
const newLeft = lastTab.offsetLeft + lastTab.offsetWidth - width
|
||||
this.moveTabBarBy(newLeft > 0 ? -newLeft : 0)
|
||||
@@ -436,6 +434,18 @@ class SnippetNoteDetail extends React.Component {
|
||||
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
|
||||
// L key
|
||||
case 76:
|
||||
{
|
||||
@@ -627,7 +637,6 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
focusEditor () {
|
||||
console.log('code-' + this.state.snippetIndex)
|
||||
this.refs['code-' + this.state.snippetIndex].focus()
|
||||
}
|
||||
|
||||
@@ -759,6 +768,8 @@ class SnippetNoteDetail extends React.Component {
|
||||
<TagSelect
|
||||
ref='tags'
|
||||
value={this.state.note.tags}
|
||||
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
||||
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||
data={data}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
/>
|
||||
|
||||
@@ -169,4 +169,21 @@ body[data-theme="monokai"]
|
||||
|
||||
.tabList
|
||||
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
|
||||
@@ -179,10 +179,10 @@ class TagSelect extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { value, className } = this.props
|
||||
const { value, className, showTagsAlphabetically } = this.props
|
||||
|
||||
const tagList = _.isArray(value)
|
||||
? value.map((tag) => {
|
||||
? (showTagsAlphabetically ? _.sortBy(value) : value).map((tag) => {
|
||||
return (
|
||||
<span styleName='tag'
|
||||
key={tag}
|
||||
|
||||
@@ -76,3 +76,14 @@ body[data-theme="monokai"]
|
||||
|
||||
.tag-label
|
||||
color $ui-monokai-text-color
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.tag
|
||||
background-color $ui-dracula-tag-backgroundColor
|
||||
|
||||
.tag-removeButton
|
||||
border-color $ui-dracula-button--focus-borderColor
|
||||
background-color transparent
|
||||
|
||||
.tag-label
|
||||
color $ui-dracula-borderColor
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ToggleModeButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const ToggleModeButton = ({
|
||||
onClick, editorType
|
||||
}) => (
|
||||
<div styleName='control-toggleModeButton'>
|
||||
<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' : ''} />
|
||||
</div>
|
||||
<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'} />
|
||||
</div>
|
||||
<span styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
ToggleModeButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
editorType: PropTypes.string.Required
|
||||
}
|
||||
|
||||
export default CSSModules(ToggleModeButton, styles)
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ToggleModeButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const ToggleModeButton = ({
|
||||
onClick, editorType
|
||||
}) => (
|
||||
<div styleName='control-toggleModeButton'>
|
||||
<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' : ''} />
|
||||
</div>
|
||||
<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'} />
|
||||
</div>
|
||||
<span styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
ToggleModeButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
editorType: PropTypes.string.Required
|
||||
}
|
||||
|
||||
export default CSSModules(ToggleModeButton, styles)
|
||||
|
||||
@@ -63,3 +63,10 @@ body[data-theme="monokai"]
|
||||
.active
|
||||
background-color #f92672
|
||||
box-shadow 2px 0px 7px #222222
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.control-toggleModeButton
|
||||
background-color #44475a
|
||||
.active
|
||||
background-color #bd93f9
|
||||
box-shadow 2px 0px 7px #222222
|
||||
|
||||
@@ -80,7 +80,6 @@ class Main extends React.Component {
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
console.log(data)
|
||||
store.dispatch({
|
||||
type: 'ADD_STORAGE',
|
||||
storage: data.storage,
|
||||
@@ -141,7 +140,7 @@ class Main extends React.Component {
|
||||
componentDidMount () {
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -297,7 +298,7 @@ class Main extends React.Component {
|
||||
onMouseUp={e => this.handleMouseUp(e)}
|
||||
>
|
||||
<SideNav
|
||||
{..._.pick(this.props, ['dispatch', 'data', 'config', 'location'])}
|
||||
{..._.pick(this.props, ['dispatch', 'data', 'config', 'params', 'location'])}
|
||||
width={this.state.navWidth}
|
||||
/>
|
||||
{!config.isSideNavFolded &&
|
||||
|
||||
@@ -79,3 +79,7 @@ body[data-theme="solarized-dark"]
|
||||
body[data-theme="monokai"]
|
||||
.root, .root--expanded
|
||||
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) {
|
||||
const { location, dispatch, config } = this.props
|
||||
const { location, params, dispatch, config } = this.props
|
||||
const { storage, folder } = this.resolveTargetFolder()
|
||||
|
||||
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') {
|
||||
createSnippetNote(storage.key, folder.key, dispatch, location, config)
|
||||
createSnippetNote(storage.key, folder.key, dispatch, location, params, config)
|
||||
} else {
|
||||
modal.open(NewNoteModal, {
|
||||
storage: storage.key,
|
||||
folder: folder.key,
|
||||
dispatch,
|
||||
location,
|
||||
params,
|
||||
config
|
||||
})
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ body[data-theme="dark"]
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
color $ui-dark-text-color
|
||||
|
||||
|
||||
.control-button--active
|
||||
color $ui-dark-text-color
|
||||
&:active
|
||||
@@ -109,7 +109,7 @@ body[data-theme="solarized-dark"]
|
||||
color $ui-solarized-dark-inactive-text-color
|
||||
&:hover
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
|
||||
.control-button--active
|
||||
color $ui-solarized-dark-text-color
|
||||
&:active
|
||||
@@ -138,3 +138,27 @@ body[data-theme="monokai"]
|
||||
color $ui-monokai-text-color
|
||||
&:active
|
||||
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)
|
||||
|
||||
this.selectNextNoteHandler = () => {
|
||||
console.log('fired next')
|
||||
this.selectNextNote()
|
||||
}
|
||||
this.selectPriorNoteHandler = () => {
|
||||
@@ -84,7 +83,9 @@ class NoteList extends React.Component {
|
||||
|
||||
// TODO: not Selected noteKeys but SelectedNote(for reusing)
|
||||
this.state = {
|
||||
ctrlKeyDown: false,
|
||||
shiftKeyDown: false,
|
||||
prevShiftNoteIndex: -1,
|
||||
selectedNoteKeys: []
|
||||
}
|
||||
|
||||
@@ -267,7 +268,7 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
handleNoteListKeyDown (e) {
|
||||
if (e.metaKey || e.ctrlKey) return true
|
||||
if (e.metaKey) return true
|
||||
|
||||
// A key
|
||||
if (e.keyCode === 65 && !e.shiftKey) {
|
||||
@@ -307,6 +308,8 @@ class NoteList extends React.Component {
|
||||
|
||||
if (e.shiftKey) {
|
||||
this.setState({ shiftKeyDown: true })
|
||||
} else if (e.ctrlKey) {
|
||||
this.setState({ ctrlKeyDown: true })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +317,10 @@ class NoteList extends React.Component {
|
||||
if (!e.shiftKey) {
|
||||
this.setState({ shiftKeyDown: false })
|
||||
}
|
||||
|
||||
if (!e.ctrlKey) {
|
||||
this.setState({ ctrlKeyDown: false })
|
||||
}
|
||||
}
|
||||
|
||||
getNotes () {
|
||||
@@ -390,25 +397,65 @@ class NoteList extends React.Component {
|
||||
return pinnedNotes.concat(unpinnedNotes)
|
||||
}
|
||||
|
||||
getNoteIndexByKey (noteKey) {
|
||||
return this.notes.findIndex((note) => {
|
||||
if (!note) return -1
|
||||
|
||||
return note.key === noteKey
|
||||
})
|
||||
}
|
||||
|
||||
handleNoteClick (e, uniqueKey) {
|
||||
const { router } = this.context
|
||||
const { location } = this.props
|
||||
let { selectedNoteKeys } = this.state
|
||||
const { shiftKeyDown } = this.state
|
||||
let { selectedNoteKeys, prevShiftNoteIndex } = 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)
|
||||
this.setState({
|
||||
selectedNoteKeys: newSelectedNoteKeys
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!shiftKeyDown) {
|
||||
if (!ctrlKeyDown && !shiftKeyDown) {
|
||||
selectedNoteKeys = []
|
||||
}
|
||||
|
||||
if (!shiftKeyDown) {
|
||||
prevShiftNoteIndex = -1
|
||||
}
|
||||
|
||||
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({
|
||||
selectedNoteKeys
|
||||
selectedNoteKeys,
|
||||
prevShiftNoteIndex
|
||||
})
|
||||
|
||||
router.push({
|
||||
@@ -616,7 +663,6 @@ class NoteList extends React.Component {
|
||||
.catch((err) => {
|
||||
console.error('Cannot Delete note: ' + err)
|
||||
})
|
||||
console.log('Notes were all deleted')
|
||||
} else {
|
||||
if (!confirmDeleteNote(confirmDeletion, false)) return
|
||||
|
||||
@@ -636,7 +682,6 @@ class NoteList extends React.Component {
|
||||
})
|
||||
})
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
||||
console.log('Notes went to trash')
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Notes could not go to trash: ' + err)
|
||||
@@ -996,6 +1041,7 @@ class NoteList extends React.Component {
|
||||
folderName={this.getNoteFolder(note).name}
|
||||
storageName={this.getNoteStorage(note).name}
|
||||
viewType={viewType}
|
||||
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
text-align center
|
||||
|
||||
|
||||
|
||||
|
||||
.top-menu-label
|
||||
margin-left 5px
|
||||
overflow ellipsis
|
||||
@@ -122,3 +122,8 @@ body[data-theme="monokai"]
|
||||
.root, .root--folded
|
||||
background-color $ui-monokai-backgroundColor
|
||||
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 SortableStorageItemChild = SortableElement(StorageItemChild)
|
||||
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 noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
||||
|
||||
|
||||
@@ -18,6 +18,12 @@ import TagButton from './TagButton'
|
||||
import {SortableContainer} from 'react-sortable-hoc'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
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 {
|
||||
// TODO: should not use electron stuff v0.7
|
||||
@@ -30,6 +36,52 @@ class SideNav extends React.Component {
|
||||
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) {
|
||||
openModal(PreferencesModal)
|
||||
}
|
||||
@@ -44,6 +96,17 @@ class SideNav extends React.Component {
|
||||
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) {
|
||||
const { dispatch, config } = this.props
|
||||
|
||||
@@ -144,12 +207,20 @@ class SideNav extends React.Component {
|
||||
|
||||
tagListComponent () {
|
||||
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(
|
||||
(tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
|
||||
), ['name']).filter(
|
||||
).filter(
|
||||
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') {
|
||||
tagList = _.sortBy(tagList, item => (0 - item.size))
|
||||
}
|
||||
@@ -165,6 +236,7 @@ class SideNav extends React.Component {
|
||||
name={tag.name}
|
||||
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
|
||||
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
|
||||
handleContextMenu={this.handleTagContextMenu.bind(this)}
|
||||
isActive={this.getTagActive(location.pathname, tag.name)}
|
||||
isRelated={tag.related}
|
||||
key={tag.name}
|
||||
@@ -198,7 +270,7 @@ class SideNav extends React.Component {
|
||||
const tags = pathSegments[pathSegments.length - 1]
|
||||
return (tags === 'alltags')
|
||||
? []
|
||||
: tags.split(' ').map(tag => decodeURIComponent(tag))
|
||||
: decodeURIComponent(tags).split(' ')
|
||||
}
|
||||
|
||||
handleClickTagListItem (name) {
|
||||
@@ -230,7 +302,7 @@ class SideNav extends React.Component {
|
||||
} else {
|
||||
listOfTags.push(tag)
|
||||
}
|
||||
router.push(`/tags/${listOfTags.map(tag => encodeURIComponent(tag)).join(' ')}`)
|
||||
router.push(`/tags/${encodeURIComponent(listOfTags.join(' '))}`)
|
||||
}
|
||||
|
||||
emptyTrash (entries) {
|
||||
@@ -238,6 +310,8 @@ class SideNav extends React.Component {
|
||||
const deletionPromises = entries.map((note) => {
|
||||
return dataApi.deleteNote(note.storage, note.key)
|
||||
})
|
||||
const { confirmDeletion } = this.props.config.ui
|
||||
if (!confirmDeleteNote(confirmDeletion, true)) return
|
||||
Promise.all(deletionPromises)
|
||||
.then((arrayOfStorageAndNoteKeys) => {
|
||||
arrayOfStorageAndNoteKeys.forEach(({ storageKey, noteKey }) => {
|
||||
@@ -247,7 +321,6 @@ class SideNav extends React.Component {
|
||||
.catch((err) => {
|
||||
console.error('Cannot Delete note: ' + err)
|
||||
})
|
||||
console.log('Trash emptied')
|
||||
}
|
||||
|
||||
handleFilterButtonContextMenu (event) {
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
color $ui-active-color
|
||||
span
|
||||
margin-left 5px
|
||||
|
||||
|
||||
.update
|
||||
navButtonColor()
|
||||
height 24px
|
||||
@@ -47,6 +47,14 @@
|
||||
.update-icon
|
||||
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"]
|
||||
.root
|
||||
border-color $ui-dark-borderColor
|
||||
@@ -80,3 +88,14 @@ body[data-theme="monokai"]
|
||||
color $ui-monokai-active-color
|
||||
&:active
|
||||
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 i18n from 'browser/lib/i18n'
|
||||
import context from 'browser/lib/context'
|
||||
import EventEmitter from 'browser/main/lib/eventEmitter'
|
||||
|
||||
const electron = require('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]
|
||||
|
||||
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 () {
|
||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
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 () {
|
||||
const { config, status } = this.context
|
||||
|
||||
|
||||
@@ -256,3 +256,25 @@ body[data-theme="monokai"]
|
||||
input
|
||||
background-color $ui-monokai-noteList-backgroundColor
|
||||
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
|
||||
width 12px
|
||||
|
||||
::-webkit-scrollbar-corner
|
||||
background-color: transparent;
|
||||
|
||||
::-webkit-scrollbar-thumb
|
||||
background-color rgba(0, 0, 0, 0.15)
|
||||
|
||||
@@ -162,6 +165,15 @@ body[data-theme="monokai"]
|
||||
.sortableItemHelper
|
||||
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"]
|
||||
.SideNav ::-webkit-scrollbar-thumb
|
||||
background-color rgba(255, 255, 255, 0.3)
|
||||
|
||||
@@ -45,7 +45,6 @@ function initAwsMobileAnalytics () {
|
||||
if (getSendEventCond()) return
|
||||
AWS.config.credentials.get((err) => {
|
||||
if (!err) {
|
||||
console.log('Cognito Identity ID: ' + AWS.config.credentials.identityId)
|
||||
recordDynamicCustomEvent('APP_STARTED')
|
||||
recordStaticCustomEvent()
|
||||
}
|
||||
@@ -58,7 +57,7 @@ function recordDynamicCustomEvent (type, options = {}) {
|
||||
mobileAnalyticsClient.recordEvent(type, options)
|
||||
} catch (analyticsError) {
|
||||
if (analyticsError instanceof ReferenceError) {
|
||||
console.log(analyticsError.name + ': ' + analyticsError.message)
|
||||
console.error(analyticsError.name + ': ' + analyticsError.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +70,7 @@ function recordStaticCustomEvent () {
|
||||
})
|
||||
} catch (analyticsError) {
|
||||
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,
|
||||
hotkey: {
|
||||
toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
|
||||
toggleMode: OSX ? 'Command + Option + M' : 'Ctrl + M'
|
||||
toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M',
|
||||
deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace'
|
||||
},
|
||||
ui: {
|
||||
language: 'en',
|
||||
@@ -43,11 +44,15 @@ export const DEFAULT_CONFIG = {
|
||||
enableRulers: false,
|
||||
rulers: [80, 120],
|
||||
displayLineNumbers: true,
|
||||
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
|
||||
switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK'
|
||||
delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE'
|
||||
scrollPastEnd: false,
|
||||
type: 'SPLIT',
|
||||
type: 'SPLIT', // 'SPLIT', 'EDITOR_PREVIEW'
|
||||
fetchUrlTitle: true,
|
||||
enableTableEditor: false
|
||||
enableTableEditor: false,
|
||||
enableFrontMatterTitle: true,
|
||||
frontMatterTitleField: 'title',
|
||||
spellcheck: false
|
||||
},
|
||||
preview: {
|
||||
fontSize: '14',
|
||||
@@ -60,12 +65,14 @@ export const DEFAULT_CONFIG = {
|
||||
latexBlockClose: '$$',
|
||||
plantUMLServerAddress: 'http://www.plantuml.com/plantuml',
|
||||
scrollPastEnd: false,
|
||||
scrollSync: true,
|
||||
smartQuotes: true,
|
||||
breaks: true,
|
||||
smartArrows: false,
|
||||
allowCustomCSS: false,
|
||||
customCSS: '',
|
||||
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'
|
||||
sanitize: 'STRICT', // 'STRICT', 'ALLOW_STYLES', 'NONE'
|
||||
lineThroughCheckbox: true
|
||||
},
|
||||
blog: {
|
||||
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')
|
||||
} else if (newConfig.ui.theme === 'monokai') {
|
||||
document.body.setAttribute('data-theme', 'monokai')
|
||||
} else if (newConfig.ui.theme === 'dracula') {
|
||||
document.body.setAttribute('data-theme', 'dracula')
|
||||
} else {
|
||||
document.body.setAttribute('data-theme', 'default')
|
||||
}
|
||||
@@ -195,7 +204,7 @@ function rewriteHotkey (config) {
|
||||
const keys = [...Object.keys(config.hotkey)]
|
||||
keys.forEach(key => {
|
||||
config.hotkey[key] = config.hotkey[key].replace(/Cmd/g, 'Command')
|
||||
config.hotkey[key] = config.hotkey[key].replace(/Opt/g, 'Alt')
|
||||
config.hotkey[key] = config.hotkey[key].replace(/Opt\s/g, 'Option ')
|
||||
})
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -529,7 +529,6 @@ function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
|
||||
return modifiedLinkText
|
||||
})
|
||||
} else {
|
||||
console.log('One if the parameters was null -> Do nothing..')
|
||||
return Promise.resolve(linkText)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ function renameStorage (key, name) {
|
||||
cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(cachedStorageList)) throw new Error('invalid storages')
|
||||
} catch (err) {
|
||||
console.log('error got')
|
||||
console.error(err)
|
||||
return Promise.reject(err)
|
||||
}
|
||||
|
||||
@@ -31,13 +31,9 @@ function resolveStorageData (storageCache) {
|
||||
|
||||
const version = parseInt(storage.version, 10)
|
||||
if (version >= 1) {
|
||||
if (version > 1) {
|
||||
console.log('The repository version is newer than one of current app.')
|
||||
}
|
||||
return Promise.resolve(storage)
|
||||
}
|
||||
|
||||
console.log('Transform Legacy storage', storage.path)
|
||||
return migrateFromV6Storage(storage.path)
|
||||
.then(() => storage)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ function resolveStorageNotes (storage) {
|
||||
notePathList = sander.readdirSync(notesDirPath)
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
console.log(notesDirPath, ' doesn\'t exist.')
|
||||
console.error(notesDirPath, ' doesn\'t exist.')
|
||||
sander.mkdirSync(notesDirPath)
|
||||
} else {
|
||||
console.warn('Failed to find note dir', notesDirPath, err)
|
||||
|
||||
@@ -12,7 +12,6 @@ function toggleStorage (key, isOpen) {
|
||||
cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(cachedStorageList)) throw new Error('invalid storages')
|
||||
} catch (err) {
|
||||
console.log('error got')
|
||||
console.error(err)
|
||||
return Promise.reject(err)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ function once (name, listener) {
|
||||
}
|
||||
|
||||
function emit (name, ...args) {
|
||||
console.log(name)
|
||||
remote.getCurrentWindow().webContents.send(name, ...args)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,14 +14,13 @@ nodeIpc.connectTo(
|
||||
path.join(app.getPath('userData'), 'boostnote.service'),
|
||||
function () {
|
||||
nodeIpc.of.node.on('error', function (err) {
|
||||
console.log(err)
|
||||
console.error(err)
|
||||
})
|
||||
nodeIpc.of.node.on('connect', function () {
|
||||
console.log('Connected successfully')
|
||||
ipcRenderer.send('config-renew', {config: ConfigManager.get()})
|
||||
})
|
||||
nodeIpc.of.node.on('disconnect', function () {
|
||||
console.log('disconnected')
|
||||
return
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -3,5 +3,8 @@ import ee from 'browser/main/lib/eventEmitter'
|
||||
module.exports = {
|
||||
'toggleMode': () => {
|
||||
ee.emit('topbar:togglemodebutton')
|
||||
},
|
||||
'deleteNote': () => {
|
||||
ee.emit('hotkey:deletenote')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,3 +128,29 @@ body[data-theme="monokai"]
|
||||
|
||||
.control-confirmButton
|
||||
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) {
|
||||
const { storage, folder, dispatch, location } = this.props
|
||||
createMarkdownNote(storage, folder, dispatch, location).then(() => {
|
||||
const { storage, folder, dispatch, location, params, config } = this.props
|
||||
createMarkdownNote(storage, folder, dispatch, location, params, config).then(() => {
|
||||
setTimeout(this.props.close, 200)
|
||||
})
|
||||
}
|
||||
@@ -35,8 +35,8 @@ class NewNoteModal extends React.Component {
|
||||
}
|
||||
|
||||
handleSnippetNoteButtonClick (e) {
|
||||
const { storage, folder, dispatch, location, config } = this.props
|
||||
createSnippetNote(storage, folder, dispatch, location, config).then(() => {
|
||||
const { storage, folder, dispatch, location, params, config } = this.props
|
||||
createSnippetNote(storage, folder, dispatch, location, params, config).then(() => {
|
||||
setTimeout(this.props.close, 200)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -97,3 +97,20 @@ body[data-theme="monokai"]
|
||||
|
||||
.description
|
||||
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
|
||||
@@ -138,6 +138,10 @@ colorMonokaiControl()
|
||||
background-color $ui-monokai-button-backgroundColor
|
||||
color $ui-monokai-text-color
|
||||
|
||||
colorDraculaControl()
|
||||
border none
|
||||
background-color $ui-dracula-button-backgroundColor
|
||||
color $ui-dracula-text-color
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
@@ -220,3 +224,30 @@ body[data-theme="monokai"]
|
||||
.group-section-control
|
||||
select, .group-section-control-input
|
||||
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 (
|
||||
<div styleName='root'>
|
||||
<div styleName='header'>{i18n.__('Crowdfunding')}</div>
|
||||
<p>{i18n.__('Dear Boostnote users,')}</p>
|
||||
<br />
|
||||
<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 />
|
||||
<p>{i18n.__('To support our growing userbase, and satisfy community expectations,')}</p>
|
||||
<p>{i18n.__('we would like to invest more time and resources in this project.')}</p>
|
||||
<p>{i18n.__('We launched IssueHunt which is an issue-based crowdfunding / sourcing platform for open source projects.')}</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 />
|
||||
<p>{i18n.__('If you use Boostnote and see its potential, help us out by supporting the project 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 />
|
||||
<p>{i18n.__('Thanks,')}</p>
|
||||
<p>{i18n.__('### We believe Meritocracy')}</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 />
|
||||
<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>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -29,7 +29,7 @@ p
|
||||
body[data-theme="dark"]
|
||||
p
|
||||
color $ui-dark-text-color
|
||||
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
color $ui-solarized-dark-text-color
|
||||
@@ -41,3 +41,9 @@ body[data-theme="monokai"]
|
||||
color $ui-monokai-text-color
|
||||
p
|
||||
color $ui-monokai-text-color
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
color $ui-dracula-text-color
|
||||
p
|
||||
color $ui-dracula-text-color
|
||||
@@ -154,3 +154,26 @@ body[data-theme="monokai"]
|
||||
|
||||
.folderItem-right-dangerButton
|
||||
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.setState({keymapAlert: {
|
||||
type: 'error',
|
||||
message: err.message != null ? err.message : i18n.__('An error occurred!')
|
||||
}})
|
||||
if (
|
||||
this.state.config.hotkey.toggleMain === '' ||
|
||||
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
|
||||
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||
@@ -68,7 +78,8 @@ class HotkeyTab extends React.Component {
|
||||
const { config } = this.state
|
||||
config.hotkey = {
|
||||
toggleMain: this.refs.toggleMain.value,
|
||||
toggleMode: this.refs.toggleMode.value
|
||||
toggleMode: this.refs.toggleMode.value,
|
||||
deleteNote: this.refs.deleteNote.value
|
||||
}
|
||||
this.setState({
|
||||
config
|
||||
@@ -127,6 +138,17 @@ class HotkeyTab extends React.Component {
|
||||
/>
|
||||
</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'>
|
||||
<button styleName='group-control-leftButton'
|
||||
onClick={(e) => this.handleHintToggleButtonClick(e)}
|
||||
|
||||
@@ -84,7 +84,7 @@ class InfoTab extends React.Component {
|
||||
>{i18n.__('GitHub')}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://boostlog.io/@junp1234'
|
||||
<a href='https://medium.com/boostnote'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>{i18n.__('Blog')}</a>
|
||||
</li>
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
color alpha($tab--dark-text-color, 80%)
|
||||
|
||||
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
@@ -75,3 +75,10 @@ body[data-theme="monokai"]
|
||||
.list
|
||||
a
|
||||
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
|
||||
&:hover
|
||||
color white
|
||||
|
||||
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
@@ -139,3 +139,27 @@ body[data-theme="monokai"]
|
||||
background-color $ui-monokai-button--active-backgroundColor
|
||||
&:hover
|
||||
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) {
|
||||
const { currentSnippet } = this.props
|
||||
if (currentSnippet == null) return
|
||||
|
||||
if (currentSnippet == null) {
|
||||
return 'snippet-item'
|
||||
}
|
||||
|
||||
if (currentSnippet.id === snippet.id) {
|
||||
return 'snippet-item-selected'
|
||||
} else {
|
||||
|
||||
@@ -6,6 +6,9 @@ import i18n from 'browser/lib/i18n'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import SnippetList from './SnippetList'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import copy from 'copy-to-clipboard'
|
||||
|
||||
const path = require('path')
|
||||
|
||||
class SnippetTab extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -16,6 +19,17 @@ class SnippetTab extends React.Component {
|
||||
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 () {
|
||||
clearTimeout(this.changeDelay)
|
||||
this.changeDelay = setTimeout(() => {
|
||||
@@ -27,12 +41,14 @@ class SnippetTab extends React.Component {
|
||||
|
||||
handleSnippetSelect (snippet) {
|
||||
const { currentSnippet } = this.state
|
||||
if (currentSnippet === null || currentSnippet.id !== snippet.id) {
|
||||
dataApi.fetchSnippet(snippet.id).then(changedSnippet => {
|
||||
// notify the snippet editor to load the content of the new snippet
|
||||
this.snippetEditor.onSnippetChanged(changedSnippet)
|
||||
this.setState({currentSnippet: changedSnippet})
|
||||
})
|
||||
if (snippet !== null) {
|
||||
if (currentSnippet === null || currentSnippet.id !== snippet.id) {
|
||||
dataApi.fetchSnippet(snippet.id).then(changedSnippet => {
|
||||
// notify the snippet editor to load the content of the new snippet
|
||||
this.snippetEditor.onSnippetChanged(changedSnippet)
|
||||
this.setState({currentSnippet: changedSnippet})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 () {
|
||||
const { config, storageKey } = this.props
|
||||
const { currentSnippet } = this.state
|
||||
@@ -70,6 +97,13 @@ class SnippetTab extends React.Component {
|
||||
onSnippetDeleted={this.handleDeleteSnippet.bind(this)}
|
||||
currentSnippet={currentSnippet} />
|
||||
<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-label'>{i18n.__('Snippet name')}</div>
|
||||
<div styleName='group-section-control'>
|
||||
|
||||
@@ -196,3 +196,19 @@ body[data-theme="monokai"]
|
||||
color white
|
||||
.group-control-button
|
||||
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()
|
||||
@@ -158,7 +158,7 @@ body[data-theme="dark"]
|
||||
.addStorage-body-control-cancelButton
|
||||
colorDarkDefaultButton()
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
|
||||
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
@@ -236,3 +236,41 @@ body[data-theme="monokai"]
|
||||
.addStorage-body-control-cancelButton
|
||||
colorDarkDefaultButton()
|
||||
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
|
||||
@@ -68,9 +68,13 @@ class UiTab extends React.Component {
|
||||
theme: this.refs.uiTheme.value,
|
||||
language: this.refs.uiLanguage.value,
|
||||
defaultNote: this.refs.defaultNote.value,
|
||||
tagNewNoteWithFilteringTags: this.refs.tagNewNoteWithFilteringTags.checked,
|
||||
showCopyNotification: this.refs.showCopyNotification.checked,
|
||||
confirmDeletion: this.refs.confirmDeletion.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
|
||||
? this.refs.uiD2w.checked
|
||||
: false
|
||||
@@ -89,7 +93,10 @@ class UiTab extends React.Component {
|
||||
snippetDefaultLanguage: this.refs.editorSnippetDefaultLanguage.value,
|
||||
scrollPastEnd: this.refs.scrollPastEnd.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: {
|
||||
fontSize: this.refs.previewFontSize.value,
|
||||
@@ -102,11 +109,13 @@ class UiTab extends React.Component {
|
||||
latexBlockClose: this.refs.previewLatexBlockClose.value,
|
||||
plantUMLServerAddress: this.refs.previewPlantUMLServerAddress.value,
|
||||
scrollPastEnd: this.refs.previewScrollPastEnd.checked,
|
||||
scrollSync: this.refs.previewScrollSync.checked,
|
||||
smartQuotes: this.refs.previewSmartQuotes.checked,
|
||||
breaks: this.refs.previewBreaks.checked,
|
||||
smartArrows: this.refs.previewSmartArrows.checked,
|
||||
sanitize: this.refs.previewSanitize.value,
|
||||
allowCustomCSS: this.refs.previewAllowCustomCSS.checked,
|
||||
lineThroughCheckbox: this.refs.lineThroughCheckbox.checked,
|
||||
customCSS: this.customCSSCM.getCodeMirror().getValue()
|
||||
}
|
||||
}
|
||||
@@ -187,6 +196,7 @@ class UiTab extends React.Component {
|
||||
<option value='white'>{i18n.__('White')}</option>
|
||||
<option value='solarized-dark'>{i18n.__('Solarized Dark')}</option>
|
||||
<option value='monokai'>{i18n.__('Monokai')}</option>
|
||||
<option value='dracula'>{i18n.__('Dracula')}</option>
|
||||
<option value='dark'>{i18n.__('Dark')}</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -244,16 +254,6 @@ class UiTab extends React.Component {
|
||||
{i18n.__('Show a confirmation dialog when deleting notes')}
|
||||
</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>
|
||||
{
|
||||
global.process.platform === 'win32'
|
||||
? <div styleName='group-checkBoxSection'>
|
||||
@@ -269,6 +269,64 @@ class UiTab extends React.Component {
|
||||
</div>
|
||||
: 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-section'>
|
||||
@@ -426,6 +484,31 @@ class UiTab extends React.Component {
|
||||
</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'>
|
||||
<label>
|
||||
<input onChange={(e) => this.handleUIChange(e)}
|
||||
@@ -469,6 +552,16 @@ class UiTab extends React.Component {
|
||||
{i18n.__('Enable smart table editor')}
|
||||
</label>
|
||||
</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-section'>
|
||||
@@ -512,6 +605,16 @@ class UiTab extends React.Component {
|
||||
</select>
|
||||
</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'>
|
||||
<label>
|
||||
<input onChange={(e) => this.handleUIChange(e)}
|
||||
@@ -522,6 +625,16 @@ class UiTab extends React.Component {
|
||||
{i18n.__('Allow preview to scroll past the last line')}
|
||||
</label>
|
||||
</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'>
|
||||
<label>
|
||||
<input onChange={(e) => this.handleUIChange(e)}
|
||||
|
||||
@@ -113,7 +113,6 @@ function data (state = defaultDataMap(), action) {
|
||||
|
||||
// If storage chanced, origin key must be discarded
|
||||
if (originKey !== uniqueKey) {
|
||||
console.log('diffrent storage')
|
||||
// From isStarred
|
||||
if (originNote.isStarred) {
|
||||
state.starredSet = new Set(state.starredSet)
|
||||
|
||||
@@ -14,18 +14,18 @@
|
||||
list-style none
|
||||
padding 0
|
||||
margin 0
|
||||
|
||||
|
||||
border-radius 4px
|
||||
margin .2em 0 0
|
||||
margin .5rem 0 0
|
||||
background-color $ui-noteList-backgroundColor
|
||||
border 1px solid rgba(0,0,0,.3)
|
||||
box-shadow .05em .2em .6em rgba(0,0,0,.2)
|
||||
text-shadow none
|
||||
|
||||
|
||||
&:empty,
|
||||
&[hidden]
|
||||
display none
|
||||
|
||||
|
||||
&:before
|
||||
content ""
|
||||
position absolute
|
||||
@@ -39,12 +39,12 @@
|
||||
border-bottom 0
|
||||
-webkit-transform rotate(45deg)
|
||||
transform rotate(45deg)
|
||||
|
||||
|
||||
li
|
||||
position relative
|
||||
padding 6px 18px 6px 10px
|
||||
cursor pointer
|
||||
|
||||
|
||||
li[aria-selected="true"]
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color $ui-text-color
|
||||
@@ -53,15 +53,15 @@ body[data-theme="dark"]
|
||||
.TagSelect
|
||||
.react-autosuggest__input
|
||||
color $ui-dark-text-color
|
||||
|
||||
|
||||
ul
|
||||
border-color $ui-dark-borderColor
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
color $ui-dark-text-color
|
||||
|
||||
|
||||
&:before
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
|
||||
|
||||
li[aria-selected="true"]
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
color $ui-dark-text-color
|
||||
@@ -70,32 +70,49 @@ body[data-theme="monokai"]
|
||||
.TagSelect
|
||||
.react-autosuggest__input
|
||||
color $ui-monokai-text-color
|
||||
|
||||
|
||||
ul
|
||||
border-color $ui-monokai-borderColor
|
||||
background-color $ui-monokai-noteList-backgroundColor
|
||||
color $ui-monokai-text-color
|
||||
|
||||
|
||||
&:before
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
|
||||
|
||||
li[aria-selected="true"]
|
||||
background-color $ui-monokai-button-backgroundColor
|
||||
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"]
|
||||
.TagSelect
|
||||
.react-autosuggest__input
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
|
||||
ul
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
|
||||
&:before
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
|
||||
|
||||
li[aria-selected="true"]
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
@@ -104,6 +121,6 @@ body[data-theme="white"]
|
||||
.TagSelect
|
||||
ul
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
|
||||
li[aria-selected="true"]
|
||||
background-color $ui-button--active-backgroundColor
|
||||
@@ -128,6 +128,16 @@ colorMonokaiPrimaryButton()
|
||||
&:active:hover
|
||||
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-background = #c9302c
|
||||
@@ -383,4 +393,30 @@ modalMonokai()
|
||||
width 100%
|
||||
background-color $ui-monokai-backgroundColor
|
||||
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
|
||||
@@ -1,12 +1,24 @@
|
||||
# Contributing to Boostnote (English)
|
||||
|
||||
### When you open an issue or a bug report
|
||||
There is no issue template, but there is a request.
|
||||
|
||||
**Please paste screenshots of Boostnote with the developer tool open**
|
||||
There is an issue template for you to follow. Please provide as much information as you can according to the template.
|
||||
|
||||
Thank you in advance for your help.
|
||||
|
||||
### 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.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
### Concerning Copyright
|
||||
|
||||
By making a pull request you agree to transfer ownership of your code to BoostIO.
|
||||
|
||||
@@ -49,7 +49,7 @@ function startServer () {
|
||||
}
|
||||
|
||||
function startElectron () {
|
||||
spawn(electron, ['--hot', './index.js'])
|
||||
spawn(electron, ['--hot', './index.js'], { stdio: 'inherit' })
|
||||
.on('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...
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,6 +1,8 @@
|
||||
# How to debug Boostnote (Electron app)
|
||||
|
||||
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) and [German](https://github.com/BoostIO/Boostnote/blob/master/docs/de/debug.md).
|
||||
|
||||
## Debug with Google Chrome developer Tools
|
||||
Boostnote is an Electron app so it's based on Chromium; developers can use `Developer Tools` just like Google Chrome.
|
||||
|
||||
You can toggle the `Developer Tools` like this:
|
||||
@@ -11,12 +13,24 @@ The `Developer Tools` will look like this:
|
||||
|
||||
When errors occur, the error messages are displayed at the `console`.
|
||||
|
||||
## Debugging
|
||||
### Debugging
|
||||
For example, you can use the `debugger` to set a breakpoint in the code like this:
|
||||
|
||||

|
||||
|
||||
This is just an illustrative example, you should find a way to debug which fits your style.
|
||||
|
||||
## References
|
||||
### References
|
||||
* [Official document of Google Chrome about debugging](https://developer.chrome.com/devtools)
|
||||
|
||||
## Debug with Visual Studio Code
|
||||
|
||||
1. Install **[Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome "Install Debugger for Chrome")** plugin for Visual Studio Code. Then restart it.
|
||||
2. Pressing **Shift+Command+B** or running **Run Build Task** from the global **Terminal** menu, then pick the task named **Build Boostnote**. Or run `yarn run watch` from the terminal.
|
||||
3. When above task is running, open **Debug view** in **Activity Bar** on the side of VS Code or use shortcut **Shift+Command+D**.
|
||||
4. Select the configuration named **Boostnote All** from the **Debug configuration**, then click the green arrow button or press **F5** to start debugging.
|
||||
5. Now you should find **Boostnote** is running. You will see two processes running, one named **Boostnote Main** and the other named **Boostnote Renderer**. Now you can set **debug breakpoints** in vscode. If you find your **breakpoints** is unverified, you need to switch to the appropriate process between **Boostnote Renderer** and **Boostnote Main**.
|
||||
|
||||
### References
|
||||
* [Electron application debugging](https://electronjs.org/docs/tutorial/application-debugging)
|
||||
* [Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome)
|
||||
@@ -52,7 +52,10 @@
|
||||
|
||||
if (className.indexOf('cm-url') !== -1) {
|
||||
const match = /^\((.*)\)|\[(.*)\]|(.*)$/.exec(el.textContent)
|
||||
return match[1] || match[2] || match[3]
|
||||
const url = match[1] || match[2] || match[3]
|
||||
|
||||
// `:storage` is the value of the variable `STORAGE_FOLDER_PLACEHOLDER` defined in `browser/main/lib/dataApi/attachmentManagement`
|
||||
return /^:storage(?:\/|%5C)/.test(url) ? null : url
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
45
extra_scripts/codemirror/mode/bfm/bfm.css
vendored
Normal file
45
extra_scripts/codemirror/mode/bfm/bfm.css
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
.cm-table-row-even { background-color: rgb(242, 242, 242); }
|
||||
.cm-s-3024-day.CodeMirror .cm-table-row-even { background-color: rgb(238, 237, 237); }
|
||||
.cm-s-3024-night.CodeMirror .cm-table-row-even { background-color: rgb(30, 24, 21); }
|
||||
.cm-s-abcdef.CodeMirror .cm-table-row-even { background-color: rgb(36, 39, 37); }
|
||||
.cm-s-ambiance.CodeMirror .cm-table-row-even { background-color: rgb(242, 242, 242); }
|
||||
.cm-s-base16-dark.CodeMirror .cm-table-row-even { background-color: rgb(41, 41, 41); }
|
||||
.cm-s-base16-light.CodeMirror .cm-table-row-even { background-color: rgb(234, 234, 234); }
|
||||
.cm-s-bespin.CodeMirror .cm-table-row-even { background-color: rgb(52, 45, 40); }
|
||||
.cm-s-blackboard.CodeMirror .cm-table-row-even { background-color: rgb(36, 39, 55); }
|
||||
.cm-s-cobalt.CodeMirror .cm-table-row-even { background-color: rgb(26, 56, 83); }
|
||||
.cm-s-colorforth.CodeMirror .cm-table-row-even { background-color: rgb(25, 25, 25); }
|
||||
.cm-s-darcula.CodeMirror .cm-table-row-even { background-color: rgb(56, 57, 59); }
|
||||
.cm-s-dracula.CodeMirror .cm-table-row-even { background-color: rgb(61, 63, 73); }
|
||||
.cm-s-duotone-dark.CodeMirror .cm-table-row-even { background-color: rgb(49, 45, 60); }
|
||||
.cm-s-duotone-light.CodeMirror .cm-table-row-even { background-color: rgb(246, 243, 238); }
|
||||
.cm-s-erlang-dark.CodeMirror .cm-table-row-even { background-color: rgb(26, 56, 83); }
|
||||
.cm-s-gruvbox-dark.CodeMirror .cm-table-row-even { background-color: rgb(55, 53, 51); }
|
||||
.cm-s-hopscotch.CodeMirror .cm-table-row-even { background-color: rgb(66, 58, 65); }
|
||||
.cm-s-isotope.CodeMirror .cm-table-row-even { background-color: rgb(22, 22, 22); }
|
||||
.cm-s-lesser-dark.CodeMirror .cm-table-row-even { background-color: rgb(58, 58, 57); }
|
||||
.cm-s-liquibyte.CodeMirror .cm-table-row-even { background-color: rgb(26, 26, 26); }
|
||||
.cm-s-lucario.CodeMirror .cm-table-row-even { background-color: rgb(64, 81, 96); }
|
||||
.cm-s-material.CodeMirror .cm-table-row-even { background-color: rgb(58, 69, 74); }
|
||||
.cm-s-mbo.CodeMirror .cm-table-row-even { background-color: rgb(65, 65, 63); }
|
||||
.cm-s-midnight.CodeMirror .cm-table-row-even { background-color: rgb(34, 46, 63); }
|
||||
.cm-s-monokai.CodeMirror .cm-table-row-even { background-color: rgb(60, 61, 55); }
|
||||
.cm-s-neo.CodeMirror .cm-table-row-even { background-color: rgb(245, 245, 245); }
|
||||
.cm-s-night.CodeMirror .cm-table-row-even { background-color: rgb(34, 25, 53); }
|
||||
.cm-s-oceanic-next.CodeMirror .cm-table-row-even { background-color: rgb(68, 83, 89); }
|
||||
.cm-s-paraiso-dark.CodeMirror .cm-table-row-even { background-color: rgb(61, 45, 59); }
|
||||
.cm-s-paraiso-light.CodeMirror .cm-table-row-even { background-color: rgb(223, 224, 211); }
|
||||
.cm-s-pastel-on-dark.CodeMirror .cm-table-row-even { background-color: rgb(54, 51, 49); }
|
||||
.cm-s-railscasts.CodeMirror .cm-table-row-even { background-color: rgb(63, 63, 62); }
|
||||
.cm-s-rubyblue.CodeMirror .cm-table-row-even { background-color: rgb(41, 58, 73); }
|
||||
.cm-s-seti.CodeMirror .cm-table-row-even { background-color: rgb(40, 42, 43); }
|
||||
.cm-s-shadowfox.CodeMirror .cm-table-row-even { background-color: rgb(56, 56, 59); }
|
||||
.cm-s-the-matrix.CodeMirror .cm-table-row-even { background-color: rgb(0, 26, 0); }
|
||||
.cm-s-tomorrow-night-bright.CodeMirror .cm-table-row-even { background-color: rgb(23, 23, 23); }
|
||||
.cm-s-tomorrow-night-eighties.CodeMirror .cm-table-row-even { background-color: rgb(20, 20, 20); }
|
||||
.cm-s-twilight.CodeMirror .cm-table-row-even { background-color: rgb(43, 43, 43); }
|
||||
.cm-s-vibrant-ink.CodeMirror .cm-table-row-even { background-color: rgb(26, 26, 26); }
|
||||
.cm-s-xq-dark.CodeMirror .cm-table-row-even { background-color: rgb(34, 25, 53); }
|
||||
.cm-s-yeti.CodeMirror .cm-table-row-even { background-color: rgb(235, 232, 230); }
|
||||
.cm-s-solarized.cm-s-dark .cm-table-row-even { background-color: rgb(13, 54, 64); }
|
||||
.cm-s-solarized.cm-s-light .cm-table-row-even { background-color: rgb(245, 240, 222); }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user