diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 1f5ada57..5554c4b8 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,15 +1,25 @@ # Current behavior # Expected behavior + + # Steps to reproduce + + 1. 2. 3. diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index aa33aec1..d676a1a4 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -5,7 +5,7 @@ import CodeMirror from 'codemirror' import 'codemirror-mode-elixir' import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement' import convertModeName from 'browser/lib/convertModeName' -import { options, TableEditor } from '@susisu/mte-kernel' +import { options, TableEditor, Alignment } from '@susisu/mte-kernel' import TextEditorInterface from 'browser/lib/TextEditorInterface' import eventEmitter from 'browser/main/lib/eventEmitter' import iconv from 'iconv-lite' @@ -17,6 +17,8 @@ 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' @@ -60,15 +62,16 @@ 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() } handleSearch (msg) { @@ -109,9 +112,32 @@ export default class CodeEditor extends React.Component { this.tableEditor.formatAll(options({textWidthOptions: {}})) } + handleEditorActivity () { + if (!this.textEditorInterface.transaction) { + this.updateTableEditorState() + } + } + + updateTableEditorState () { + const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions) + if (active) { + if (this.extraKeysMode !== 'editor') { + this.extraKeysMode = 'editor' + this.editor.setOption('extraKeys', this.editorKeyMap) + } + } else { + if (this.extraKeysMode !== 'default') { + this.extraKeysMode = 'default' + this.editor.setOption('extraKeys', this.defaultKeyMap) + this.tableEditor.resetSmartCursor() + } + } + } + componentDidMount () { const { rulers, enableRulers } = this.props const expandSnippet = this.expandSnippet.bind(this) + eventEmitter.on('line:jump', this.scrollToLineHandeler) const defaultSnippet = [ { @@ -129,6 +155,59 @@ export default class CodeEditor extends React.Component { ) } + this.defaultKeyMap = CodeMirror.normalizeKeyMap({ + Tab: function (cm) { + const cursor = cm.getCursor() + const line = cm.getLine(cursor.line) + const cursorPosition = cursor.ch + const charBeforeCursor = line.substr(cursorPosition - 1, 1) + if (cm.somethingSelected()) cm.indentSelection('add') + else { + const tabs = cm.getOption('indentWithTabs') + if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) { + cm.execCommand('goLineStart') + if (tabs) { + cm.execCommand('insertTab') + } else { + cm.execCommand('insertSoftTab') + } + cm.execCommand('goLineEnd') + } else if ( + !charBeforeCursor.match(/\t|\s|\r|\n/) && + cursor.ch > 1 + ) { + // text expansion on tab key if the char before is alphabet + const snippets = JSON.parse( + fs.readFileSync(consts.SNIPPET_FILE, 'utf8') + ) + if (expandSnippet(line, cursor, cm, snippets) === false) { + if (tabs) { + cm.execCommand('insertTab') + } else { + cm.execCommand('insertSoftTab') + } + } + } else { + if (tabs) { + cm.execCommand('insertTab') + } else { + cm.execCommand('insertSoftTab') + } + } + } + }, + 'Cmd-T': function (cm) { + // Do nothing + }, + Enter: 'boostNewLineAndIndentContinueMarkdownList', + 'Ctrl-C': cm => { + if (cm.getOption('keyMap').substr(0, 3) === 'vim') { + document.execCommand('copy') + } + return CodeMirror.Pass + } + }) + this.value = this.props.value this.editor = CodeMirror(this.refs.root, { rulers: buildCMRulers(rulers, enableRulers), @@ -151,58 +230,7 @@ export default class CodeEditor extends React.Component { explode: '[]{}``$$', override: true }, - extraKeys: { - Tab: function (cm) { - const cursor = cm.getCursor() - const line = cm.getLine(cursor.line) - const cursorPosition = cursor.ch - const charBeforeCursor = line.substr(cursorPosition - 1, 1) - if (cm.somethingSelected()) cm.indentSelection('add') - else { - const tabs = cm.getOption('indentWithTabs') - if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) { - cm.execCommand('goLineStart') - if (tabs) { - cm.execCommand('insertTab') - } else { - cm.execCommand('insertSoftTab') - } - cm.execCommand('goLineEnd') - } else if ( - !charBeforeCursor.match(/\t|\s|\r|\n/) && - cursor.ch > 1 - ) { - // text expansion on tab key if the char before is alphabet - const snippets = JSON.parse( - fs.readFileSync(consts.SNIPPET_FILE, 'utf8') - ) - if (expandSnippet(line, cursor, cm, snippets) === false) { - if (tabs) { - cm.execCommand('insertTab') - } else { - cm.execCommand('insertSoftTab') - } - } - } else { - if (tabs) { - cm.execCommand('insertTab') - } else { - cm.execCommand('insertSoftTab') - } - } - } - }, - 'Cmd-T': function (cm) { - // Do nothing - }, - Enter: 'boostNewLineAndIndentContinueMarkdownList', - 'Ctrl-C': cm => { - if (cm.getOption('keyMap').substr(0, 3) === 'vim') { - document.execCommand('copy') - } - return CodeMirror.Pass - } - } + extraKeys: this.defaultKeyMap }) this.setMode(this.props.mode) @@ -226,11 +254,66 @@ export default class CodeEditor extends React.Component { CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor) CodeMirror.Vim.map('ZZ', ':q', 'normal') - this.tableEditor = new TableEditor(new TextEditorInterface(this.editor)) + 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({ + smartCursor: true + }) + + this.editorKeyMap = CodeMirror.normalizeKeyMap({ + 'Tab': () => { this.tableEditor.nextCell(this.tableEditorOptions) }, + 'Shift-Tab': () => { this.tableEditor.previousCell(this.tableEditorOptions) }, + 'Enter': () => { this.tableEditor.nextRow(this.tableEditorOptions) }, + 'Ctrl-Enter': () => { this.tableEditor.escape(this.tableEditorOptions) }, + 'Cmd-Enter': () => { this.tableEditor.escape(this.tableEditorOptions) }, + 'Shift-Ctrl-Left': () => { this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) }, + 'Shift-Cmd-Left': () => { this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) }, + 'Shift-Ctrl-Right': () => { this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) }, + 'Shift-Cmd-Right': () => { this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) }, + 'Shift-Ctrl-Up': () => { this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) }, + 'Shift-Cmd-Up': () => { this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) }, + 'Shift-Ctrl-Down': () => { this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) }, + 'Shift-Cmd-Down': () => { this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) }, + 'Ctrl-Left': () => { this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) }, + 'Cmd-Left': () => { this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) }, + 'Ctrl-Right': () => { this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) }, + 'Cmd-Right': () => { this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) }, + 'Ctrl-Up': () => { this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) }, + 'Cmd-Up': () => { this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) }, + 'Ctrl-Down': () => { this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) }, + 'Cmd-Down': () => { this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) }, + 'Ctrl-K Ctrl-I': () => { this.tableEditor.insertRow(this.tableEditorOptions) }, + 'Cmd-K Cmd-I': () => { this.tableEditor.insertRow(this.tableEditorOptions) }, + 'Ctrl-L Ctrl-I': () => { this.tableEditor.deleteRow(this.tableEditorOptions) }, + 'Cmd-L Cmd-I': () => { this.tableEditor.deleteRow(this.tableEditorOptions) }, + 'Ctrl-K Ctrl-J': () => { this.tableEditor.insertColumn(this.tableEditorOptions) }, + 'Cmd-K Cmd-J': () => { this.tableEditor.insertColumn(this.tableEditorOptions) }, + 'Ctrl-L Ctrl-J': () => { this.tableEditor.deleteColumn(this.tableEditorOptions) }, + 'Cmd-L Cmd-J': () => { this.tableEditor.deleteColumn(this.tableEditorOptions) }, + 'Alt-Shift-Ctrl-Left': () => { this.tableEditor.moveColumn(-1, this.tableEditorOptions) }, + 'Alt-Shift-Cmd-Left': () => { this.tableEditor.moveColumn(-1, this.tableEditorOptions) }, + 'Alt-Shift-Ctrl-Right': () => { this.tableEditor.moveColumn(1, this.tableEditorOptions) }, + 'Alt-Shift-Cmd-Right': () => { this.tableEditor.moveColumn(1, this.tableEditorOptions) }, + 'Alt-Shift-Ctrl-Up': () => { this.tableEditor.moveRow(-1, this.tableEditorOptions) }, + 'Alt-Shift-Cmd-Up': () => { this.tableEditor.moveRow(-1, this.tableEditorOptions) }, + 'Alt-Shift-Ctrl-Down': () => { this.tableEditor.moveRow(1, this.tableEditorOptions) }, + 'Alt-Shift-Cmd-Down': () => { this.tableEditor.moveRow(1, this.tableEditorOptions) } + }) + + if (this.props.enableTableEditor) { + this.editor.on('cursorActivity', this.editorActivityHandler) + this.editor.on('changes', this.editorActivityHandler) + } + + this.setState({ + clientWidth: this.refs.root.clientWidth + }) } expandSnippet (line, cursor, cm, snippets) { @@ -369,6 +452,27 @@ export default class CodeEditor extends React.Component { this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd) } + if (prevProps.enableTableEditor !== this.props.enableTableEditor) { + if (this.props.enableTableEditor) { + this.editor.on('cursorActivity', this.editorActivityHandler) + this.editor.on('changes', this.editorActivityHandler) + } else { + this.editor.off('cursorActivity', this.editorActivityHandler) + this.editor.off('changes', this.editorActivityHandler) + } + + this.extraKeysMode = 'default' + this.editor.setOption('extraKeys', this.defaultKeyMap) + } + + if (this.state.clientWidth !== this.refs.root.clientWidth) { + this.setState({ + clientWidth: this.refs.root.clientWidth + }) + + needRefresh = true + } + if (needRefresh) { this.editor.refresh() } @@ -392,7 +496,13 @@ export default class CodeEditor extends React.Component { moveCursorTo (row, col) {} - scrollToLine (num) {} + scrollToLine (event, num) { + const cursor = { + line: num, + ch: 1 + } + this.editor.setCursor(cursor) + } focus () { this.editor.focus() @@ -455,7 +565,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, @@ -525,6 +639,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) => { @@ -533,7 +653,10 @@ export default class CodeEditor extends React.Component { body, 'text/html' ) - const linkWithTitle = `[${parsedBody.title}](${pastedTxt})` + const escapePipe = (str) => { + return str.replace('|', '\\|') + } + const linkWithTitle = `[${escapePipe(parsedBody.title)}](${pastedTxt})` resolve(linkWithTitle) } catch (e) { reject(e) diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index a690c6c8..d43a7693 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -64,6 +64,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) @@ -250,7 +254,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} @@ -265,6 +269,7 @@ class MarkdownEditor extends React.Component { storageKey={storageKey} noteKey={noteKey} fetchUrlTitle={config.editor.fetchUrlTitle} + enableTableEditor={config.editor.enableTableEditor} onChange={(e) => this.handleChange(e)} onBlur={(e) => this.handleBlur(e)} spellCheck @@ -300,6 +305,7 @@ class MarkdownEditor extends React.Component { noteKey={noteKey} customCSS={config.preview.customCSS} allowCustomCSS={config.preview.allowCustomCSS} + lineThroughCheckbox={config.preview.lineThroughCheckbox} /> ) diff --git a/browser/components/MarkdownEditor.styl b/browser/components/MarkdownEditor.styl index 13455e5d..c8fe2e49 100644 --- a/browser/components/MarkdownEditor.styl +++ b/browser/components/MarkdownEditor.styl @@ -16,7 +16,6 @@ .preview display block absolute top bottom left right - z-index 100 background-color white height 100% width 100% diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 5376a773..bef6a976 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -17,8 +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 context from 'browser/lib/context' +import i18n from 'browser/lib/i18n' +import fs from 'fs' -const { remote } = require('electron') +const { remote, shell } = require('electron') const attachmentManagement = require('../main/lib/dataApi/attachmentManagement') const { app } = remote @@ -27,6 +30,8 @@ const fileUrl = require('file-url') const dialog = remote.dialog +const uri2path = require('file-uri-to-path') + const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1] const appPath = fileUrl( process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve() @@ -75,7 +80,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 { @@ -83,6 +87,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); @@ -139,6 +148,8 @@ body p { display: none } } + +${allowCustomCSS ? customCSS : ''} ` } @@ -161,7 +172,6 @@ const scrollBarDarkStyle = ` } ` -const { shell } = require('electron') const OSX = global.process.platform === 'darwin' const defaultFontFamily = ['helvetica', 'arial', 'sans-serif'] @@ -219,8 +229,32 @@ export default class MarkdownPreview extends React.Component { } } - handleContextMenu (e) { - this.props.onContextMenu(e) + handleContextMenu (event) { + // If a contextMenu handler was passed to us, use it instead of the self-defined one -> return + if (_.isFunction(this.props.onContextMenu)) { + this.props.onContextMenu(event) + return + } + // No contextMenu was passed to us -> execute our own link-opener + if (event.target.tagName.toLowerCase() === 'a') { + const href = event.target.href + const isLocalFile = href.startsWith('file:') + if (isLocalFile) { + const absPath = uri2path(href) + try { + if (fs.lstatSync(absPath).isFile()) { + context.popup([ + { + label: i18n.__('Show in explorer'), + click: (e) => shell.showItemInFolder(absPath) + } + ]) + } + } catch (e) { + console.log('Error while evaluating if the file is locally available', e) + } + } + } } handleDoubleClick (e) { @@ -397,6 +431,7 @@ export default class MarkdownPreview extends React.Component { case 'dark': case 'solarized-dark': case 'monokai': + case 'dracula': return scrollBarDarkStyle default: return scrollBarStyle @@ -498,7 +533,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() @@ -827,6 +863,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 diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index 8721bc93..789399cf 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -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 @@ -145,7 +151,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} @@ -158,6 +164,7 @@ class MarkdownSplitEditor extends React.Component { rulers={config.editor.rulers} scrollPastEnd={config.editor.scrollPastEnd} fetchUrlTitle={config.editor.fetchUrlTitle} + enableTableEditor={config.editor.enableTableEditor} storageKey={storageKey} noteKey={noteKey} onChange={this.handleOnChange.bind(this)} @@ -192,6 +199,7 @@ class MarkdownSplitEditor extends React.Component { noteKey={noteKey} customCSS={config.preview.customCSS} allowCustomCSS={config.preview.allowCustomCSS} + lineThroughCheckbox={config.preview.lineThroughCheckbox} /> ) diff --git a/browser/components/NoteItem.js b/browser/components/NoteItem.js index 5073dc73..600b7e2d 100644 --- a/browser/components/NoteItem.js +++ b/browser/components/NoteItem.js @@ -74,24 +74,22 @@ const NoteItem = ({ ? note.title : {i18n.__('Empty note')}} - {['ALL', 'STORAGE'].includes(viewType) && -
-
{dateDisplay}
-
-
- {viewType === 'ALL' && storageName} - {viewType === 'STORAGE' && folderName} -
+
+
{dateDisplay}
+
+
+ {viewType === 'ALL' && storageName} + {viewType === 'STORAGE' && folderName}
-
} - +
+
{note.tags.length > 0 diff --git a/browser/components/NoteItem.styl b/browser/components/NoteItem.styl index 017ef6d0..a31f00a4 100644 --- a/browser/components/NoteItem.styl +++ b/browser/components/NoteItem.styl @@ -368,13 +368,13 @@ body[data-theme="monokai"] .item-title .item-title-icon .item-bottom-time - color $ui-monokai-text-color + color $ui-monokai-active-color .item-bottom-tagList-item background-color alpha(white, 10%) color $ui-monokai-text-color &:hover // background-color alpha($ui-monokai-button--active-backgroundColor, 60%) - color #c0392b + color #f92672 .item-bottom-tagList-item background-color alpha(#fff, 20%) @@ -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 \ No newline at end of file diff --git a/browser/components/NoteItemSimple.styl b/browser/components/NoteItemSimple.styl index 04f57fdc..70b74be7 100644 --- a/browser/components/NoteItemSimple.styl +++ b/browser/components/NoteItemSimple.styl @@ -240,7 +240,7 @@ body[data-theme="monokai"] .item-simple-title-icon .item-simple-bottom-time transition 0.15s - color $ui-solarized-dark-text-color + color $ui-monokai-text-color .item-simple-bottom-tagList-item transition 0.15s background-color alpha(#fff, 20%) @@ -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 \ No newline at end of file diff --git a/browser/components/RealtimeNotification.styl b/browser/components/RealtimeNotification.styl index 0365d8c9..36330c48 100644 --- a/browser/components/RealtimeNotification.styl +++ b/browser/components/RealtimeNotification.styl @@ -51,4 +51,15 @@ body[data-theme="monokai"] border none background-color $ui-monokai-button-backgroundColor &:hover - color #5CB85C \ No newline at end of file + 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 \ No newline at end of file diff --git a/browser/components/SideNavFilter.styl b/browser/components/SideNavFilter.styl index c9dbd861..1da8c7e4 100644 --- a/browser/components/SideNavFilter.styl +++ b/browser/components/SideNavFilter.styl @@ -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 \ No newline at end of file + 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 \ No newline at end of file diff --git a/browser/components/SnippetTab.styl b/browser/components/SnippetTab.styl index 02f88f8c..a31b8594 100644 --- a/browser/components/SnippetTab.styl +++ b/browser/components/SnippetTab.styl @@ -180,4 +180,48 @@ body[data-theme="monokai"] color $ui-monokai-text-color .deleteButton - color alpha($ui-monokai-text-color, 30%) \ No newline at end of file + 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%) \ No newline at end of file diff --git a/browser/components/StorageItem.styl b/browser/components/StorageItem.styl index 0a1b4525..df8cbbc6 100644 --- a/browser/components/StorageItem.styl +++ b/browser/components/StorageItem.styl @@ -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 \ No newline at end of file + 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 \ No newline at end of file diff --git a/browser/components/TagListItem.js b/browser/components/TagListItem.js index 6cd50c9c..19f11791 100644 --- a/browser/components/TagListItem.js +++ b/browser/components/TagListItem.js @@ -14,8 +14,8 @@ import CSSModules from 'browser/lib/CSSModules' * @param {bool} isRelated */ -const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isActive, isRelated, count}) => ( -
+const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count}) => ( +
handleContextMenu(e, name)}> {isRelated ?
) TodoListPercentage.propTypes = { - percentageOfTodo: PropTypes.number.isRequired + percentageOfTodo: PropTypes.number.isRequired, + onClearCheckboxClick: PropTypes.func.isRequired } export default CSSModules(TodoListPercentage, styles) diff --git a/browser/components/TodoListPercentage.styl b/browser/components/TodoListPercentage.styl index 6116cd58..5a0f3257 100644 --- a/browser/components/TodoListPercentage.styl +++ b/browser/components/TodoListPercentage.styl @@ -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,7 +54,10 @@ body[data-theme="dark"] .percentageText color $ui-dark-text-color - + + .todoClearText + color $ui-dark-text-color + body[data-theme="solarized-dark"] .percentageBar background-color #002b36 @@ -50,12 +68,28 @@ body[data-theme="solarized-dark"] .percentageText color #fdf6e3 + .todoClearText + color #fdf6e3 + body[data-theme="monokai"] .percentageBar - background-color #f92672 + background-color: $ui-monokai-borderColor .progressBar - background-color: #373831 + background-color $ui-monokai-active-color .percentageText - color #fdf6e3 \ No newline at end of file + 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 diff --git a/browser/components/markdown.styl b/browser/components/markdown.styl index fb30742d..e091331b 100644 --- a/browser/components/markdown.styl +++ b/browser/components/markdown.styl @@ -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 @@ -361,7 +364,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) @@ -476,5 +479,34 @@ body[data-theme="monokai"] border-color themeMonokaiTableBorder &:last-child border-right solid 1px themeMonokaiTableBorder + kbd + background-color themeDarkBackground + +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 \ No newline at end of file diff --git a/browser/components/render/MermaidRender.js b/browser/components/render/MermaidRender.js index 12dce327..e8784d9d 100644 --- a/browser/components/render/MermaidRender.js +++ b/browser/components/render/MermaidRender.js @@ -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) { @@ -21,7 +21,7 @@ function getId () { function render (element, content, theme) { try { - let isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai' + let isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai' || theme === 'dracula' mermaidAPI.initialize({ theme: isDarkTheme ? 'dark' : 'default', themeCSS: isDarkTheme ? darkThemeStyling : '' diff --git a/browser/lib/Languages.js b/browser/lib/Languages.js index ddb7e0ed..087210be 100644 --- a/browser/lib/Languages.js +++ b/browser/lib/Languages.js @@ -49,7 +49,7 @@ const languages = [ }, { name: 'Portuguese', - locale: 'pt' + locale: 'pt-BR' }, { name: 'Russian', diff --git a/browser/lib/TextEditorInterface.js b/browser/lib/TextEditorInterface.js index 53ae2337..55f2c29d 100644 --- a/browser/lib/TextEditorInterface.js +++ b/browser/lib/TextEditorInterface.js @@ -1,53 +1,113 @@ -import { Point } from '@susisu/mte-kernel' - -export default class TextEditorInterface { - constructor (editor) { - this.editor = editor - } - - getCursorPosition () { - const pos = this.editor.getCursor() - return new Point(pos.line, pos.ch) - } - - setCursorPosition (pos) { - this.editor.setCursor({line: pos.row, ch: pos.column}) - } - - setSelectionRange (range) { - this.editor.setSelection({ - anchor: {line: range.start.row, ch: range.start.column}, - head: {line: range.end.row, ch: range.end.column} - }) - } - - getLastRow () { - return this.editor.lastLine() - } - - acceptsTableEdit (row) { - return true - } - - getLine (row) { - return this.editor.getLine(row) - } - - insertLine (row, line) { - this.editor.replaceRange(line, {line: row, ch: 0}) - } - - deleteLine (row) { - this.editor.replaceRange('', {line: row, ch: 0}, {line: row, ch: this.editor.getLine(row).length}) - } - - replaceLines (startRow, endRow, lines) { - endRow-- // because endRow is a first line after a table. - const endRowCh = this.editor.getLine(endRow).length - this.editor.replaceRange(lines, {line: startRow, ch: 0}, {line: endRow, ch: endRowCh}) - } - - transact (func) { - func() - } -} +import { Point } from '@susisu/mte-kernel' + +export default class TextEditorInterface { + constructor (editor) { + this.editor = editor + this.doc = editor.getDoc() + this.transaction = false + } + + getCursorPosition () { + const { line, ch } = this.doc.getCursor() + return new Point(line, ch) + } + + setCursorPosition (pos) { + this.doc.setCursor({ + line: pos.row, + ch: pos.column + }) + } + + setSelectionRange (range) { + this.doc.setSelection( + { line: range.start.row, ch: range.start.column }, + { line: range.end.row, ch: range.end.column } + ) + } + + getLastRow () { + return this.doc.lineCount() - 1 + } + + acceptsTableEdit () { + return true + } + + getLine (row) { + return this.doc.getLine(row) + } + + insertLine (row, line) { + const lastRow = this.getLastRow() + if (row > lastRow) { + const lastLine = this.getLine(lastRow) + this.doc.replaceRange( + '\n' + line, + { line: lastRow, ch: lastLine.length }, + { line: lastRow, ch: lastLine.length } + ) + } else { + this.doc.replaceRange( + line + '\n', + { line: row, ch: 0 }, + { line: row, ch: 0 } + ) + } + } + + deleteLine (row) { + const lastRow = this.getLastRow() + if (row >= lastRow) { + if (lastRow > 0) { + const preLastLine = this.getLine(lastRow - 1) + const lastLine = this.getLine(lastRow) + this.doc.replaceRange( + '', + { line: lastRow - 1, ch: preLastLine.length }, + { line: lastRow, ch: lastLine.length } + ) + } else { + const lastLine = this.getLine(lastRow) + this.doc.replaceRange( + '', + { line: lastRow, ch: 0 }, + { line: lastRow, ch: lastLine.length } + ) + } + } else { + this.doc.replaceRange( + '', + { line: row, ch: 0 }, + { line: row + 1, ch: 0 } + ) + } + } + + replaceLines (startRow, endRow, lines) { + const lastRow = this.getLastRow() + if (endRow > lastRow) { + const lastLine = this.getLine(lastRow) + this.doc.replaceRange( + lines.join('\n'), + { line: startRow, ch: 0 }, + { line: lastRow, ch: lastLine.length } + ) + } else { + this.doc.replaceRange( + lines.join('\n') + '\n', + { line: startRow, ch: 0 }, + { line: endRow, ch: 0 } + ) + } + } + + transact (func) { + this.transaction = true + func() + this.transaction = false + if (this.onDidFinishTransaction) { + this.onDidFinishTransaction.call(undefined) + } + } +} diff --git a/browser/lib/findNoteTitle.js b/browser/lib/findNoteTitle.js index 81c9400f..912c3bdd 100644 --- a/browser/lib/findNoteTitle.js +++ b/browser/lib/findNoteTitle.js @@ -1,19 +1,37 @@ -export function findNoteTitle (value) { +export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleField = 'title') { const splitted = value.split('\n') let title = null let isInsideCodeBlock = false - splitted.some((line, index) => { - const trimmedLine = line.trim() - const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim() - if (trimmedLine.match('```')) { - isInsideCodeBlock = !isInsideCodeBlock + 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) + + break + } } - 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 = '' diff --git a/browser/lib/markdown-it-frontmatter.js b/browser/lib/markdown-it-frontmatter.js new file mode 100644 index 00000000..66d8ce89 --- /dev/null +++ b/browser/lib/markdown-it-frontmatter.js @@ -0,0 +1,24 @@ +'use strict' + +module.exports = function frontMatterPlugin (md) { + function frontmatter (state, startLine, endLine, silent) { + if (startLine !== 0 || state.src.substr(startLine, state.eMarks[0]) !== '---') { + return false + } + + let line = 0 + while (++line < state.lineMax) { + if (state.src.substring(state.bMarks[line], state.eMarks[line]) === '---') { + state.line = line + 1 + + return true + } + } + + return false + } + + md.block.ruler.before('table', 'frontmatter', frontmatter, { + alt: [ 'paragraph', 'reference', 'blockquote', 'list' ] + }) +} diff --git a/browser/lib/markdown-toc-generator.js b/browser/lib/markdown-toc-generator.js new file mode 100644 index 00000000..716be83a --- /dev/null +++ b/browser/lib/markdown-toc-generator.js @@ -0,0 +1,100 @@ +/** + * @fileoverview Markdown table of contents generator + */ + +import toc from 'markdown-toc' +import diacritics from 'diacritics-map' +import stripColor from 'strip-color' + +const EOL = require('os').EOL + +/** + * @caseSensitiveSlugify Custom slugify function + * Same implementation that the original used by markdown-toc (node_modules/markdown-toc/lib/utils.js), + * but keeps original case to properly handle https://github.com/BoostIO/Boostnote/issues/2067 + */ +function caseSensitiveSlugify (str) { + function replaceDiacritics (str) { + return str.replace(/[À-ž]/g, function (ch) { + return diacritics[ch] || ch + }) + } + + function getTitle (str) { + if (/^\[[^\]]+\]\(/.test(str)) { + var m = /^\[([^\]]+)\]/.exec(str) + if (m) return m[1] + } + return str + } + + str = getTitle(str) + str = stripColor(str) + // str = str.toLowerCase() //let's be case sensitive + + // `.split()` is often (but not always) faster than `.replace()` + str = str.split(' ').join('-') + str = str.split(/\t/).join('--') + str = str.split(/<\/?[^>]+>/).join('') + str = str.split(/[|$&`~=\\\/@+*!?({[\]})<>=.,;:'"^]/).join('') + str = str.split(/[。?!,、;:“”【】()〔〕[]﹃﹄“ ”‘’﹁﹂—…-~《》〈〉「」]/).join('') + str = replaceDiacritics(str) + return str +} + +const TOC_MARKER_START = '' +const TOC_MARKER_END = '' + +/** + * Takes care of proper updating given editor with TOC. + * If TOC doesn't exit in the editor, it's inserted at current caret position. + * Otherwise,TOC is updated in place. + * @param editor CodeMirror editor to be updated with TOC + */ +export function generateInEditor (editor) { + const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`) + + function tocExistsInEditor () { + return tocRegex.test(editor.getValue()) + } + + function updateExistingToc () { + const toc = generate(editor.getValue()) + const search = editor.getSearchCursor(tocRegex) + while (search.findNext()) { + search.replace(toc) + } + } + + function addTocAtCursorPosition () { + const toc = generate(editor.getRange(editor.getCursor(), {line: Infinity})) + editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor()) + } + + if (tocExistsInEditor()) { + updateExistingToc() + } else { + addTocAtCursorPosition() + } +} + +/** + * Generates MD TOC based on MD document passed as string. + * @param markdownText MD document + * @returns generatedTOC String containing generated TOC + */ +export function generate (markdownText) { + const generatedToc = toc(markdownText, {slugify: caseSensitiveSlugify}) + return TOC_MARKER_START + EOL + EOL + generatedToc.content + EOL + EOL + TOC_MARKER_END +} + +function wrapTocWithEol (toc, editor) { + const leftWrap = editor.getCursor().ch === 0 ? '' : EOL + const rightWrap = editor.getLine(editor.getCursor().line).length === editor.getCursor().ch ? '' : EOL + return leftWrap + toc + rightWrap +} + +export default { + generate, + generateInEditor +} diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 49fd2f86..49260740 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -7,6 +7,7 @@ import _ from 'lodash' import ConfigManager from 'browser/main/lib/ConfigManager' import katex from 'katex' import { lastFindInArray } from './utils' +import ee from 'browser/main/lib/eventEmitter' function createGutter (str, firstLineNumber) { if (Number.isNaN(firstLineNumber)) firstLineNumber = 1 @@ -148,7 +149,9 @@ class Markdown { } }) this.md.use(require('markdown-it-kbd')) - this.md.use(require('markdown-it-admonition')) + + this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error']}) + this.md.use(require('./markdown-it-frontmatter')) const deflate = require('markdown-it-plantuml/lib/deflate') this.md.use(require('markdown-it-plantuml'), '', { @@ -221,7 +224,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 = `` } diff --git a/browser/lib/newNote.js b/browser/lib/newNote.js new file mode 100644 index 00000000..bed69735 --- /dev/null +++ b/browser/lib/newNote.js @@ -0,0 +1,62 @@ +import { hashHistory } from 'react-router' +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) { + AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN') + AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE') + return dataApi + .createNote(storage, { + type: 'MARKDOWN_NOTE', + folder: folder, + title: '', + content: '' + }) + .then(note => { + const noteHash = note.key + dispatch({ + type: 'UPDATE_NOTE', + note: note + }) + + hashHistory.push({ + pathname: location.pathname, + query: { key: noteHash } + }) + ee.emit('list:jump', noteHash) + ee.emit('detail:focus') + }) +} + +export function createSnippetNote (storage, folder, dispatch, location, config) { + AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET') + AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE') + return dataApi + .createNote(storage, { + type: 'SNIPPET_NOTE', + folder: folder, + title: '', + description: '', + snippets: [ + { + name: '', + mode: config.editor.snippetDefaultLanguage || 'text', + content: '' + } + ] + }) + .then(note => { + const noteHash = note.key + dispatch({ + type: 'UPDATE_NOTE', + note: note + }) + hashHistory.push({ + pathname: location.pathname, + query: { key: noteHash } + }) + ee.emit('list:jump', noteHash) + ee.emit('detail:focus') + }) +} diff --git a/browser/main/Detail/Detail.styl b/browser/main/Detail/Detail.styl index 49a634f3..1b7bd606 100644 --- a/browser/main/Detail/Detail.styl +++ b/browser/main/Detail/Detail.styl @@ -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 \ No newline at end of file diff --git a/browser/main/Detail/FolderSelect.styl b/browser/main/Detail/FolderSelect.styl index cfdc2734..fe045e3a 100644 --- a/browser/main/Detail/FolderSelect.styl +++ b/browser/main/Detail/FolderSelect.styl @@ -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 diff --git a/browser/main/Detail/InfoPanel.styl b/browser/main/Detail/InfoPanel.styl index 2a73ca7e..1f774174 100644 --- a/browser/main/Detail/InfoPanel.styl +++ b/browser/main/Detail/InfoPanel.styl @@ -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 \ No newline at end of file diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 82073162..c35127ba 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -29,6 +29,7 @@ import { formatDate } from 'browser/lib/date-formatter' import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus' import striptags from 'striptags' import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' +import markdownToc from 'browser/lib/markdown-toc-generator' class MarkdownNoteDetail extends React.Component { constructor (props) { @@ -47,6 +48,7 @@ class MarkdownNoteDetail extends React.Component { this.dispatchTimer = null this.toggleLockButton = this.handleToggleLockButton.bind(this) + this.generateToc = () => this.handleGenerateToc() } focus () { @@ -59,10 +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) @@ -75,6 +81,7 @@ class MarkdownNoteDetail extends React.Component { componentWillUnmount () { ee.off('topbar:togglelockbutton', this.toggleLockButton) + ee.off('code:generate-toc', this.generateToc) if (this.saveQueue != null) this.saveNow() } @@ -87,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) } @@ -262,6 +269,11 @@ class MarkdownNoteDetail extends React.Component { } } + handleGenerateToc () { + const editor = this.refs.content.refs.code.editor + markdownToc.generateInEditor(editor) + } + handleFocus (e) { this.focus() } @@ -284,9 +296,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 - + this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
this.handleSwitchMode(e)} editorType={editorType} /> diff --git a/browser/main/Detail/MarkdownNoteDetail.styl b/browser/main/Detail/MarkdownNoteDetail.styl index b27dc80e..cdfeaf3a 100644 --- a/browser/main/Detail/MarkdownNoteDetail.styl +++ b/browser/main/Detail/MarkdownNoteDetail.styl @@ -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 \ No newline at end of file diff --git a/browser/main/Detail/NoteDetailInfo.styl b/browser/main/Detail/NoteDetailInfo.styl index 8d454203..1ca46516 100644 --- a/browser/main/Detail/NoteDetailInfo.styl +++ b/browser/main/Detail/NoteDetailInfo.styl @@ -13,6 +13,7 @@ $info-margin-under-border = 30px display flex align-items center padding 0 20px + z-index 99 .info-left padding 0 10px @@ -97,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 \ No newline at end of file + background-color $ui-monokai-noteDetail-backgroundColor + +body[data-theme="dracula"] + .info + border-color $ui-dracula-borderColor + background-color $ui-dracula-noteDetail-backgroundColor \ No newline at end of file diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 6220b5a4..65d5dfd3 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -29,6 +29,7 @@ import InfoPanelTrashed from './InfoPanelTrashed' import { formatDate } from 'browser/lib/date-formatter' import i18n from 'browser/lib/i18n' import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' +import markdownToc from 'browser/lib/markdown-toc-generator' const electron = require('electron') const { remote } = electron @@ -52,6 +53,7 @@ class SnippetNoteDetail extends React.Component { } this.scrollToNextTabThreshold = 0.7 + this.generateToc = () => this.handleGenerateToc() } componentDidMount () { @@ -65,6 +67,7 @@ class SnippetNoteDetail extends React.Component { enableLeftArrow: allTabs.offsetLeft !== 0 }) } + ee.on('code:generate-toc', this.generateToc) } componentWillReceiveProps (nextProps) { @@ -91,6 +94,16 @@ class SnippetNoteDetail extends React.Component { componentWillUnmount () { if (this.saveQueue != null) this.saveNow() + ee.off('code:generate-toc', this.generateToc) + } + + handleGenerateToc () { + const { note, snippetIndex } = this.state + const currentMode = note.snippets[snippetIndex].mode + if (currentMode.includes('Markdown')) { + const currentEditor = this.refs[`code-${snippetIndex}`].refs.code.editor + markdownToc.generateInEditor(currentEditor) + } } handleChange (e) { @@ -99,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 @@ -441,7 +454,7 @@ class SnippetNoteDetail extends React.Component { const isSuper = global.process.platform === 'darwin' ? e.metaKey : e.ctrlKey - if (isSuper && !e.shiftKey) { + if (isSuper && !e.shiftKey && !e.altKey) { e.preventDefault() this.addSnippet() } @@ -692,6 +705,7 @@ class SnippetNoteDetail extends React.Component { keyMap={config.editor.keyMap} scrollPastEnd={config.editor.scrollPastEnd} fetchUrlTitle={config.editor.fetchUrlTitle} + enableTableEditor={config.editor.enableTableEditor} onChange={(e) => this.handleCodeChange(index)(e)} ref={'code-' + index} /> @@ -745,6 +759,7 @@ class SnippetNoteDetail extends React.Component { this.handleChange(e)} />
diff --git a/browser/main/Detail/SnippetNoteDetail.styl b/browser/main/Detail/SnippetNoteDetail.styl index f8ca48cc..e3bb31c6 100644 --- a/browser/main/Detail/SnippetNoteDetail.styl +++ b/browser/main/Detail/SnippetNoteDetail.styl @@ -169,4 +169,21 @@ body[data-theme="monokai"] .tabList background-color $ui-monokai-noteDetail-backgroundColor - color $ui-monokai-text-color \ No newline at end of file + 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 \ No newline at end of file diff --git a/browser/main/Detail/TagSelect.js b/browser/main/Detail/TagSelect.js index e251dd42..eb160e4c 100644 --- a/browser/main/Detail/TagSelect.js +++ b/browser/main/Detail/TagSelect.js @@ -6,71 +6,33 @@ import _ from 'lodash' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import i18n from 'browser/lib/i18n' import ee from 'browser/main/lib/eventEmitter' +import Autosuggest from 'react-autosuggest' class TagSelect extends React.Component { constructor (props) { super(props) this.state = { - newTag: '' + newTag: '', + suggestions: [] } - this.addtagHandler = this.handleAddTag.bind(this) + + this.handleAddTag = this.handleAddTag.bind(this) + this.onInputBlur = this.onInputBlur.bind(this) + this.onInputChange = this.onInputChange.bind(this) + this.onInputKeyDown = this.onInputKeyDown.bind(this) + this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this) + this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this) + this.onSuggestionSelected = this.onSuggestionSelected.bind(this) } - componentDidMount () { - this.value = this.props.value - ee.on('editor:add-tag', this.addtagHandler) - } - - componentDidUpdate () { - this.value = this.props.value - } - - componentWillUnmount () { - ee.off('editor:add-tag', this.addtagHandler) - } - - handleAddTag () { - this.refs.newTag.focus() - } - - handleNewTagInputKeyDown (e) { - switch (e.keyCode) { - case 9: - e.preventDefault() - this.submitTag() - break - case 13: - this.submitTag() - break - case 8: - if (this.refs.newTag.value.length === 0) { - this.removeLastTag() - } - } - } - - handleNewTagBlur (e) { - this.submitTag() - } - - removeLastTag () { - this.removeTagByCallback((value) => { - value.pop() - }) - } - - reset () { - this.setState({ - newTag: '' - }) - } - - submitTag () { + addNewTag (newTag) { AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG') - let { value } = this.props - let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_') - newTag = newTag.charAt(0) === '#' ? newTag.substring(1) : newTag + + newTag = newTag.trim().replace(/ +/g, '_') + if (newTag.charAt(0) === '#') { + newTag.substring(1) + } if (newTag.length <= 0) { this.setState({ @@ -79,6 +41,7 @@ class TagSelect extends React.Component { return } + let { value } = this.props value = _.isArray(value) ? value.slice() : [] @@ -93,10 +56,41 @@ class TagSelect extends React.Component { }) } - handleNewTagInputChange (e) { - this.setState({ - newTag: this.refs.newTag.value - }) + buildSuggestions () { + this.suggestions = _.sortBy(this.props.data.tagNoteMap.map( + (tag, name) => ({ + name, + nameLC: name.toLowerCase(), + size: tag.size + }) + ).filter( + tag => tag.size > 0 + ), ['name']) + } + + componentDidMount () { + this.value = this.props.value + + this.buildSuggestions() + + ee.on('editor:add-tag', this.handleAddTag) + } + + componentDidUpdate () { + this.value = this.props.value + } + + componentWillUnmount () { + ee.off('editor:add-tag', this.handleAddTag) + } + + handleAddTag () { + this.refs.newTag.input.focus() + } + + handleTagLabelClick (tag) { + const { router } = this.context + router.push(`/tags/${tag}`) } handleTagRemoveButtonClick (tag) { @@ -105,6 +99,60 @@ class TagSelect extends React.Component { }, tag) } + onInputBlur (e) { + this.submitNewTag() + } + + onInputChange (e, { newValue, method }) { + this.setState({ + newTag: newValue + }) + } + + onInputKeyDown (e) { + switch (e.keyCode) { + case 9: + e.preventDefault() + this.submitNewTag() + break + case 13: + this.submitNewTag() + break + case 8: + if (this.state.newTag.length === 0) { + this.removeLastTag() + } + } + } + + onSuggestionsClearRequested () { + this.setState({ + suggestions: [] + }) + } + + onSuggestionsFetchRequested ({ value }) { + const valueLC = value.toLowerCase() + const suggestions = _.filter( + this.suggestions, + tag => !_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1 + ) + + this.setState({ + suggestions + }) + } + + onSuggestionSelected (event, { suggestion, suggestionValue }) { + this.addNewTag(suggestionValue) + } + + removeLastTag () { + this.removeTagByCallback((value) => { + value.pop() + }) + } + removeTagByCallback (callback, tag = null) { let { value } = this.props @@ -118,6 +166,18 @@ class TagSelect extends React.Component { this.props.onChange() } + reset () { + this.buildSuggestions() + + this.setState({ + newTag: '' + }) + } + + submitNewTag () { + this.addNewTag(this.refs.newTag.input.value) + } + render () { const { value, className } = this.props @@ -127,7 +187,7 @@ class TagSelect extends React.Component { - #{tag} + this.handleTagLabelClick(tag)}>#{tag}
) diff --git a/browser/main/modals/PreferencesModal/Crowdfunding.styl b/browser/main/modals/PreferencesModal/Crowdfunding.styl index 326867d3..6d72290b 100644 --- a/browser/main/modals/PreferencesModal/Crowdfunding.styl +++ b/browser/main/modals/PreferencesModal/Crowdfunding.styl @@ -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 \ No newline at end of file diff --git a/browser/main/modals/PreferencesModal/FolderItem.styl b/browser/main/modals/PreferencesModal/FolderItem.styl index 8bcf2b02..2ded3ada 100644 --- a/browser/main/modals/PreferencesModal/FolderItem.styl +++ b/browser/main/modals/PreferencesModal/FolderItem.styl @@ -2,6 +2,7 @@ height 35px box-sizing border-box padding 2.5px 15px + display flex &:hover background-color darken(white, 3%) @@ -18,7 +19,10 @@ border-left solid 2px transparent padding 0 10px line-height 30px - float left + flex 1 + white-space nowrap + text-overflow ellipsis + overflow hidden .folderItem-left-danger color $danger-color font-weight bold @@ -52,7 +56,8 @@ outline none .folderItem-right - float right + -webkit-box-flex: 1 + white-space nowrap .folderItem-right-button vertical-align middle @@ -149,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() \ No newline at end of file diff --git a/browser/main/modals/PreferencesModal/HotkeyTab.js b/browser/main/modals/PreferencesModal/HotkeyTab.js index 671e1516..9f964957 100644 --- a/browser/main/modals/PreferencesModal/HotkeyTab.js +++ b/browser/main/modals/PreferencesModal/HotkeyTab.js @@ -30,7 +30,7 @@ class HotkeyTab extends React.Component { this.handleSettingError = (err) => { this.setState({keymapAlert: { type: 'error', - message: err.message != null ? err.message : i18n.__('Error occurs!') + message: err.message != null ? err.message : i18n.__('An error occurred!') }}) } this.oldHotkey = this.state.config.hotkey @@ -68,7 +68,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 @@ -79,7 +80,7 @@ class HotkeyTab extends React.Component { this.props.haveToSave({ tab: 'Hotkey', type: 'warning', - message: i18n.__('You have to save!') + message: i18n.__('Unsaved Changes!') }) } } @@ -117,7 +118,7 @@ class HotkeyTab extends React.Component {
-
{i18n.__('Toggle editor mode')}
+
{i18n.__('Toggle Editor Mode')}
this.handleHotkeyChange(e)} @@ -127,6 +128,17 @@ class HotkeyTab extends React.Component { />
+
+
{i18n.__('Delete Note')}
+
+ this.handleHotkeyChange(e)} + ref='deleteNote' + value={config.hotkey.deleteNote} + type='text' + /> +
+
+
+
{i18n.__('Snippet name')}
diff --git a/browser/main/modals/PreferencesModal/SnippetTab.styl b/browser/main/modals/PreferencesModal/SnippetTab.styl index 02307b64..dd22b72e 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.styl +++ b/browser/main/modals/PreferencesModal/SnippetTab.styl @@ -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() \ No newline at end of file diff --git a/browser/main/modals/PreferencesModal/StorageItem.styl b/browser/main/modals/PreferencesModal/StorageItem.styl index 29dfbd0b..adcc483e 100644 --- a/browser/main/modals/PreferencesModal/StorageItem.styl +++ b/browser/main/modals/PreferencesModal/StorageItem.styl @@ -9,13 +9,17 @@ box-sizing border-box border-bottom $default-border margin-bottom 5px + display flex .header-label - float left cursor pointer &:hover .header-label-editButton opacity 1 + flex 1 + white-space nowrap + text-overflow ellipsis + overflow hidden .header-label-path color $ui-inactive-text-color @@ -38,8 +42,8 @@ outline none .header-control - float right - + -webkit-box-flex: 1 + white-space nowrap .header-control-button width 30px height 25px diff --git a/browser/main/modals/PreferencesModal/StoragesTab.js b/browser/main/modals/PreferencesModal/StoragesTab.js index ad7472d2..046b24e6 100644 --- a/browser/main/modals/PreferencesModal/StoragesTab.js +++ b/browser/main/modals/PreferencesModal/StoragesTab.js @@ -70,7 +70,7 @@ class StoragesTab extends React.Component { }) return (
-
{i18n.__('Storages')}
+
{i18n.__('Storage Locations')}
{storageList.length > 0 ? storageList :
{i18n.__('No storage found.')}
diff --git a/browser/main/modals/PreferencesModal/StoragesTab.styl b/browser/main/modals/PreferencesModal/StoragesTab.styl index 9804d7e7..9a1a0ef8 100644 --- a/browser/main/modals/PreferencesModal/StoragesTab.styl +++ b/browser/main/modals/PreferencesModal/StoragesTab.styl @@ -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 \ No newline at end of file diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index 74047d44..6a9abe65 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -40,7 +40,7 @@ class UiTab extends React.Component { this.handleSettingError = (err) => { this.setState({UiAlert: { type: 'error', - message: err.message != null ? err.message : i18n.__('Error occurs!') + message: err.message != null ? err.message : i18n.__('An error occurred!') }}) } ipc.addListener('APP_SETTING_DONE', this.handleSettingDone) @@ -67,6 +67,7 @@ class UiTab extends React.Component { ui: { theme: this.refs.uiTheme.value, language: this.refs.uiLanguage.value, + defaultNote: this.refs.defaultNote.value, showCopyNotification: this.refs.showCopyNotification.checked, confirmDeletion: this.refs.confirmDeletion.checked, showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked, @@ -87,7 +88,10 @@ class UiTab extends React.Component { keyMap: this.refs.editorKeyMap.value, snippetDefaultLanguage: this.refs.editorSnippetDefaultLanguage.value, scrollPastEnd: this.refs.scrollPastEnd.checked, - fetchUrlTitle: this.refs.editorFetchUrlTitle.checked + fetchUrlTitle: this.refs.editorFetchUrlTitle.checked, + enableTableEditor: this.refs.enableTableEditor.checked, + enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked, + frontMatterTitleField: this.refs.frontMatterTitleField.value }, preview: { fontSize: this.refs.previewFontSize.value, @@ -100,11 +104,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() } } @@ -123,7 +129,7 @@ class UiTab extends React.Component { this.props.haveToSave({ tab: 'UI', type: 'warning', - message: i18n.__('You have to save!') + message: i18n.__('Unsaved Changes!') }) } }) @@ -173,7 +179,9 @@ class UiTab extends React.Component {
{i18n.__('Interface')}
- {i18n.__('Interface Theme')} +
+ {i18n.__('Interface Theme')} +
- {i18n.__('Language')} +
+ {i18n.__('Language')} +
this.handleUIChange(e)} + ref='defaultNote' + > + + + + +
+
+
+
+
+ {i18n.__('Front matter title field')} +
+
+ this.handleUIChange(e)} + type='text' + /> +
+
+ +
+ +
+
+
+ +
+
{i18n.__('Preview')}
@@ -465,7 +528,7 @@ class UiTab extends React.Component {
-
{i18n.__('Code block Theme')}
+
{i18n.__('Code Block Theme')}
this.handleUIChange(e)} + checked={this.state.config.preview.lineThroughCheckbox} + ref='lineThroughCheckbox' + type='checkbox' + />  + {i18n.__('Allow line through checkbox')} + +
+
+ +