1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 17:56:25 +00:00

Merge branch 'master' into filter-tags-and-folders

# Conflicts:
#	browser/main/SideNav/index.js
This commit is contained in:
amedora
2020-02-05 11:13:28 +09:00
197 changed files with 9275 additions and 5578 deletions

View File

@@ -6,11 +6,7 @@ import hljs from 'highlight.js'
import 'codemirror-mode-elixir'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import convertModeName from 'browser/lib/convertModeName'
import {
options,
TableEditor,
Alignment
} 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'
@@ -20,11 +16,15 @@ import styles from '../components/CodeEditor.styl'
const { ipcRenderer, remote, clipboard } = require('electron')
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
const spellcheck = require('browser/lib/spellcheck')
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder').buildEditorContextMenu
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
.buildEditorContextMenu
import { createTurndownService } from '../lib/turndown'
import {languageMaps} from '../lib/CMLanguageList'
import { languageMaps } from '../lib/CMLanguageList'
import snippetManager from '../lib/SnippetManager'
import {generateInEditor, tocExistsInEditor} from 'browser/lib/markdown-toc-generator'
import {
generateInEditor,
tocExistsInEditor
} from 'browser/lib/markdown-toc-generator'
import markdownlint from 'markdownlint'
import Jsonlint from 'jsonlint-mod'
import { DEFAULT_CONFIG } from '../main/lib/ConfigManager'
@@ -33,28 +33,38 @@ import prettier from 'prettier'
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
const buildCMRulers = (rulers, enableRulers) =>
(enableRulers ? rulers.map(ruler => ({
column: ruler
})) : [])
enableRulers
? rulers.map(ruler => ({
column: ruler
}))
: []
function translateHotkey (hotkey) {
return hotkey.replace(/\s*\+\s*/g, '-').replace(/Command/g, 'Cmd').replace(/Control/g, 'Ctrl')
function translateHotkey(hotkey) {
return hotkey
.replace(/\s*\+\s*/g, '-')
.replace(/Command/g, 'Cmd')
.replace(/Control/g, 'Ctrl')
}
export default class CodeEditor extends React.Component {
constructor (props) {
constructor(props) {
super(props)
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {
leading: false,
trailing: true
})
this.changeHandler = (editor, changeObject) => this.handleChange(editor, changeObject)
this.highlightHandler = (editor, changeObject) => this.handleHighlight(editor, changeObject)
this.changeHandler = (editor, changeObject) =>
this.handleChange(editor, changeObject)
this.highlightHandler = (editor, changeObject) =>
this.handleHighlight(editor, changeObject)
this.focusHandler = () => {
ipcRenderer.send('editor:focused', true)
}
const debouncedDeletionOfAttachments = _.debounce(attachmentManagement.deleteAttachmentsNotPresentInNote, 30000)
const debouncedDeletionOfAttachments = _.debounce(
attachmentManagement.deleteAttachmentsNotPresentInNote,
30000
)
this.blurHandler = (editor, e) => {
ipcRenderer.send('editor:focused', false)
if (e == null) return null
@@ -66,12 +76,13 @@ export default class CodeEditor extends React.Component {
el = el.parentNode
}
this.props.onBlur != null && this.props.onBlur(e)
const {
storageKey,
noteKey
} = this.props
const { storageKey, noteKey } = this.props
if (this.props.deleteUnusedAttachments === true) {
debouncedDeletionOfAttachments(this.editor.getValue(), storageKey, noteKey)
debouncedDeletionOfAttachments(
this.editor.getValue(),
storageKey,
noteKey
)
}
}
this.pasteHandler = (editor, e) => {
@@ -91,7 +102,7 @@ export default class CodeEditor extends React.Component {
this.formatTable = () => this.handleFormatTable()
if (props.switchPreview !== 'RIGHTCLICK') {
this.contextMenuHandler = function (editor, event) {
this.contextMenuHandler = function(editor, event) {
const menu = buildEditorContextMenu(editor, event)
if (menu != null) {
setTimeout(() => menu.popup(remote.getCurrentWindow()), 30)
@@ -104,24 +115,24 @@ export default class CodeEditor extends React.Component {
this.turndownService = createTurndownService()
}
handleSearch (msg) {
handleSearch(msg) {
const cm = this.editor
const component = this
if (component.searchState) cm.removeOverlay(component.searchState)
if (msg.length < 1) return
cm.operation(function () {
cm.operation(function() {
component.searchState = makeOverlay(msg, 'searching')
cm.addOverlay(component.searchState)
function makeOverlay (query, style) {
function makeOverlay(query, style) {
query = new RegExp(
query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'),
'gi'
)
return {
token: function (stream) {
token: function(stream) {
query.lastIndex = stream.pos
var match = query.exec(stream.string)
if (match && match.index === stream.pos) {
@@ -138,25 +149,27 @@ export default class CodeEditor extends React.Component {
})
}
handleFormatTable () {
this.tableEditor.formatAll(options({
textWidthOptions: {}
}))
handleFormatTable() {
this.tableEditor.formatAll(
options({
textWidthOptions: {}
})
)
}
handleEditorActivity () {
handleEditorActivity() {
if (!this.textEditorInterface.transaction) {
this.updateTableEditorState()
}
}
updateDefaultKeyMap () {
updateDefaultKeyMap() {
const { hotkey } = this.props
const self = this
const expandSnippet = snippetManager.expandSnippet
this.defaultKeyMap = CodeMirror.normalizeKeyMap({
Tab: function (cm) {
Tab: function(cm) {
const cursor = cm.getCursor()
const line = cm.getLine(cursor.line)
const cursorPosition = cursor.ch
@@ -198,17 +211,17 @@ export default class CodeEditor extends React.Component {
}
}
},
'Cmd-Left': function (cm) {
'Cmd-Left': function(cm) {
cm.execCommand('goLineLeft')
},
'Cmd-T': function (cm) {
'Cmd-T': function(cm) {
// Do nothing
},
[translateHotkey(hotkey.insertDate)]: function (cm) {
[translateHotkey(hotkey.insertDate)]: function(cm) {
const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleDateString())
},
[translateHotkey(hotkey.insertDateTime)]: function (cm) {
[translateHotkey(hotkey.insertDateTime)]: function(cm) {
const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleString())
},
@@ -231,7 +244,10 @@ export default class CodeEditor extends React.Component {
currentConfig.cursorOffset = cm.doc.indexFromPos(cursorPos)
// Prettify contents of editor
const formattedTextDetails = prettier.formatWithCursor(cm.doc.getValue(), currentConfig)
const formattedTextDetails = prettier.formatWithCursor(
cm.doc.getValue(),
currentConfig
)
const formattedText = formattedTextDetails.formatted
const formattedCursorPos = formattedTextDetails.cursorOffset
@@ -246,7 +262,8 @@ export default class CodeEditor extends React.Component {
const appendLineBreak = /\n$/.test(selection)
const sorted = _.split(selection.trim(), '\n').sort()
const sortedString = _.join(sorted, '\n') + (appendLineBreak ? '\n' : '')
const sortedString =
_.join(sorted, '\n') + (appendLineBreak ? '\n' : '')
cm.doc.replaceSelection(sortedString)
},
@@ -256,7 +273,7 @@ export default class CodeEditor extends React.Component {
})
}
updateTableEditorState () {
updateTableEditorState() {
const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions)
if (active) {
if (this.extraKeysMode !== 'editor') {
@@ -272,7 +289,7 @@ export default class CodeEditor extends React.Component {
}
}
componentDidMount () {
componentDidMount() {
const { rulers, enableRulers, enableMarkdownLint, RTL } = this.props
eventEmitter.on('line:jump', this.scrollToLineHandeler)
@@ -298,7 +315,11 @@ export default class CodeEditor extends React.Component {
rtlMoveVisually: RTL,
foldGutter: true,
lint: enableMarkdownLint ? this.getCodeEditorLintConfig() : false,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
gutters: [
'CodeMirror-linenumbers',
'CodeMirror-foldgutter',
'CodeMirror-lint-markers'
],
autoCloseBrackets: {
pairs: this.props.matchingPairs,
triples: this.props.matchingTriples,
@@ -309,7 +330,9 @@ export default class CodeEditor extends React.Component {
prettierConfig: this.props.prettierConfig
})
document.querySelector('.CodeMirror-lint-markers').style.display = enableMarkdownLint ? 'inline-block' : 'none'
document.querySelector(
'.CodeMirror-lint-markers'
).style.display = enableMarkdownLint ? 'inline-block' : 'none'
if (!this.props.mode && this.props.value && this.props.autoDetect) {
this.autoDetectLanguage(this.props.value)
@@ -342,7 +365,7 @@ 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'})
this.editor.addPanel(this.createSpellCheckPanel(), { position: 'bottom' })
}
eventEmitter.on('code:format-table', this.formatTable)
@@ -352,13 +375,13 @@ export default class CodeEditor extends React.Component {
})
this.editorKeyMap = CodeMirror.normalizeKeyMap({
'Tab': () => {
Tab: () => {
this.tableEditor.nextCell(this.tableEditorOptions)
},
'Shift-Tab': () => {
this.tableEditor.previousCell(this.tableEditorOptions)
},
'Enter': () => {
Enter: () => {
this.tableEditor.nextRow(this.tableEditorOptions)
},
'Ctrl-Enter': () => {
@@ -477,7 +500,7 @@ export default class CodeEditor extends React.Component {
this.initialHighlighting()
}
getWordBeforeCursor (line, lineNumber, cursorPosition) {
getWordBeforeCursor(line, lineNumber, cursorPosition) {
let wordBeforeCursor = ''
const originCursorPosition = cursorPosition
const emptyChars = /\t|\s|\r|\n|\$/
@@ -514,11 +537,11 @@ export default class CodeEditor extends React.Component {
}
}
quitEditor () {
quitEditor() {
document.querySelector('textarea').blur()
}
componentWillUnmount () {
componentWillUnmount() {
this.editor.off('focus', this.focusHandler)
this.editor.off('blur', this.blurHandler)
this.editor.off('change', this.changeHandler)
@@ -533,7 +556,7 @@ export default class CodeEditor extends React.Component {
eventEmitter.off('code:format-table', this.formatTable)
}
componentDidUpdate (prevProps, prevState) {
componentDidUpdate(prevProps, prevState) {
let needRefresh = false
const {
rulers,
@@ -561,13 +584,18 @@ export default class CodeEditor extends React.Component {
this.editor.setOption('direction', this.props.RTL ? 'rtl' : 'ltr')
this.editor.setOption('rtlMoveVisually', this.props.RTL)
}
if (prevProps.enableMarkdownLint !== enableMarkdownLint || prevProps.customMarkdownLintConfig !== customMarkdownLintConfig) {
if (
prevProps.enableMarkdownLint !== enableMarkdownLint ||
prevProps.customMarkdownLintConfig !== customMarkdownLintConfig
) {
if (!enableMarkdownLint) {
this.editor.setOption('lint', {default: false})
document.querySelector('.CodeMirror-lint-markers').style.display = 'none'
this.editor.setOption('lint', { default: false })
document.querySelector('.CodeMirror-lint-markers').style.display =
'none'
} else {
this.editor.setOption('lint', this.getCodeEditorLintConfig())
document.querySelector('.CodeMirror-lint-markers').style.display = 'inline-block'
document.querySelector('.CodeMirror-lint-markers').style.display =
'inline-block'
}
needRefresh = true
}
@@ -599,9 +627,11 @@ export default class CodeEditor extends React.Component {
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
}
if (prevProps.matchingPairs !== this.props.matchingPairs ||
if (
prevProps.matchingPairs !== this.props.matchingPairs ||
prevProps.matchingTriples !== this.props.matchingTriples ||
prevProps.explodingPairs !== this.props.explodingPairs) {
prevProps.explodingPairs !== this.props.explodingPairs
) {
const bracketObject = {
pairs: this.props.matchingPairs,
triples: this.props.matchingTriples,
@@ -646,11 +676,18 @@ export default class CodeEditor extends React.Component {
const elem = document.getElementById('editor-bottom-panel')
elem.parentNode.removeChild(elem)
} else {
this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'})
this.editor.addPanel(this.createSpellCheckPanel(), {
position: 'bottom'
})
}
}
if (prevProps.deleteUnusedAttachments !== this.props.deleteUnusedAttachments) {
this.editor.setOption('deleteUnusedAttachments', this.props.deleteUnusedAttachments)
if (
prevProps.deleteUnusedAttachments !== this.props.deleteUnusedAttachments
) {
this.editor.setOption(
'deleteUnusedAttachments',
this.props.deleteUnusedAttachments
)
}
if (needRefresh) {
@@ -658,17 +695,19 @@ export default class CodeEditor extends React.Component {
}
}
getCodeEditorLintConfig () {
getCodeEditorLintConfig() {
const { mode } = this.props
const checkMarkdownNoteIsOpen = mode === 'Boost Flavored Markdown'
return checkMarkdownNoteIsOpen ? {
getAnnotations: this.validatorOfMarkdown,
async: true
} : false
return checkMarkdownNoteIsOpen
? {
getAnnotations: this.validatorOfMarkdown,
async: true
}
: false
}
validatorOfMarkdown (text, updateLinting) {
validatorOfMarkdown(text, updateLinting) {
const { customMarkdownLintConfig } = this.props
let lintConfigJson
try {
@@ -693,7 +732,7 @@ export default class CodeEditor extends React.Component {
let ruleNames = ''
item.ruleNames.map((ruleName, index) => {
ruleNames += ruleName
ruleNames += (index === item.ruleNames.length - 1) ? ': ' : '/'
ruleNames += index === item.ruleNames.length - 1 ? ': ' : '/'
})
const lineNumber = item.lineNumber - 1
foundIssues.push({
@@ -708,7 +747,7 @@ export default class CodeEditor extends React.Component {
})
}
setMode (mode) {
setMode(mode) {
let syntax = CodeMirror.findModeByName(convertModeName(mode || 'text'))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
@@ -716,7 +755,7 @@ export default class CodeEditor extends React.Component {
CodeMirror.autoLoadMode(this.editor, syntax.mode)
}
handleChange (editor, changeObject) {
handleChange(editor, changeObject) {
spellcheck.handleChange(editor, changeObject)
// The current note contains an toc. We'll check for changes on headlines.
@@ -726,7 +765,11 @@ export default class CodeEditor extends React.Component {
// Check if one of the changed lines contains a headline
for (let line = 0; line < changeObject.text.length; line++) {
if (this.linePossibleContainsHeadline(editor.getLine(changeObject.from.line + line))) {
if (
this.linePossibleContainsHeadline(
editor.getLine(changeObject.from.line + line)
)
) {
requireTocUpdate = true
break
}
@@ -755,13 +798,13 @@ export default class CodeEditor extends React.Component {
}
}
linePossibleContainsHeadline (currentLine) {
linePossibleContainsHeadline(currentLine) {
// We can't check if the line start with # because when some write text before
// the # we also need to update the toc
return currentLine.includes('# ')
}
incrementLines (start, linesAdded, linesRemoved, editor) {
incrementLines(start, linesAdded, linesRemoved, editor) {
const highlightedLines = editor.options.linesHighlighted
const totalHighlightedLines = highlightedLines.length
@@ -782,7 +825,7 @@ export default class CodeEditor extends React.Component {
highlightedLines.splice(highlightedLines.indexOf(lineNumber), 1)
// Lines that need to be relocated
if (lineNumber >= (start + linesRemoved)) {
if (lineNumber >= start + linesRemoved) {
newLines.push(lineNumber + offset)
}
}
@@ -796,22 +839,30 @@ export default class CodeEditor extends React.Component {
}
}
handleHighlight (editor, changeObject) {
handleHighlight(editor, changeObject) {
const lines = editor.options.linesHighlighted
if (!lines.includes(changeObject)) {
lines.push(changeObject)
editor.addLineClass(changeObject, 'text', 'CodeMirror-activeline-background')
editor.addLineClass(
changeObject,
'text',
'CodeMirror-activeline-background'
)
} else {
lines.splice(lines.indexOf(changeObject), 1)
editor.removeLineClass(changeObject, 'text', 'CodeMirror-activeline-background')
editor.removeLineClass(
changeObject,
'text',
'CodeMirror-activeline-background'
)
}
if (this.props.onChange) {
this.props.onChange(editor)
}
}
updateHighlight (editor, changeObject) {
updateHighlight(editor, changeObject) {
const linesAdded = changeObject.text.length - 1
const linesRemoved = changeObject.removed.length - 1
@@ -842,28 +893,28 @@ export default class CodeEditor extends React.Component {
this.incrementLines(start, linesAdded, linesRemoved, editor)
}
moveCursorTo (row, col) {}
moveCursorTo(row, col) {}
scrollToLine (event, num) {
scrollToLine(event, num) {
const cursor = {
line: num,
ch: 1
}
this.editor.setCursor(cursor)
const top = this.editor.charCoords({line: num, ch: 0}, 'local').top
const top = this.editor.charCoords({ line: num, ch: 0 }, 'local').top
const middleHeight = this.editor.getScrollerElement().offsetHeight / 2
this.editor.scrollTo(null, top - middleHeight - 5)
}
focus () {
focus() {
this.editor.focus()
}
blur () {
blur() {
this.editor.blur()
}
reload () {
reload() {
// Change event shouldn't be fired when switch note
this.editor.off('change', this.changeHandler)
this.value = this.props.value
@@ -874,7 +925,7 @@ export default class CodeEditor extends React.Component {
this.editor.refresh()
}
setValue (value) {
setValue(value) {
const cursor = this.editor.getCursor()
this.editor.setValue(value)
this.editor.setCursor(cursor)
@@ -885,18 +936,19 @@ export default class CodeEditor extends React.Component {
* @param {Number} lineNumber
* @param {String} content
*/
setLineContent (lineNumber, content) {
setLineContent(lineNumber, content) {
const prevContent = this.editor.getLine(lineNumber)
const prevContentLength = prevContent ? prevContent.length : 0
this.editor.replaceRange(content, { line: lineNumber, ch: 0 }, { line: lineNumber, ch: prevContentLength })
this.editor.replaceRange(
content,
{ line: lineNumber, ch: 0 },
{ line: lineNumber, ch: prevContentLength }
)
}
handleDropImage (dropEvent) {
handleDropImage(dropEvent) {
dropEvent.preventDefault()
const {
storageKey,
noteKey
} = this.props
const { storageKey, noteKey } = this.props
attachmentManagement.handleAttachmentDrop(
this,
storageKey,
@@ -905,37 +957,44 @@ export default class CodeEditor extends React.Component {
)
}
insertAttachmentMd (imageMd) {
insertAttachmentMd(imageMd) {
this.editor.replaceSelection(imageMd)
}
autoDetectLanguage (content) {
autoDetectLanguage(content) {
const res = hljs.highlightAuto(content, Object.keys(languageMaps))
this.setMode(languageMaps[res.language])
}
handlePaste (editor, forceSmartPaste) {
handlePaste(editor, forceSmartPaste) {
const { storageKey, noteKey, fetchUrlTitle, enableSmartPaste } = this.props
const isURL = str => /(?:^\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
const isURL = str =>
/(?:^\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
const isInLinkTag = editor => {
const startCursor = editor.getCursor('start')
const prevChar = editor.getRange({
line: startCursor.line,
ch: startCursor.ch - 2
}, {
line: startCursor.line,
ch: startCursor.ch
})
const prevChar = editor.getRange(
{
line: startCursor.line,
ch: startCursor.ch - 2
},
{
line: startCursor.line,
ch: startCursor.ch
}
)
const endCursor = editor.getCursor('end')
const nextChar = editor.getRange({
line: endCursor.line,
ch: endCursor.ch
}, {
line: endCursor.line,
ch: endCursor.ch + 1
})
const nextChar = editor.getRange(
{
line: endCursor.line,
ch: endCursor.ch
},
{
line: endCursor.line,
ch: endCursor.ch + 1
}
)
return prevChar === '](' && nextChar === ')'
}
@@ -947,7 +1006,7 @@ export default class CodeEditor extends React.Component {
return true
}
let line = line = cursor.line - 1
let line = (line = cursor.line - 1)
while (line >= 0) {
token = editor.getTokenAt({
ch: 3,
@@ -979,7 +1038,11 @@ export default class CodeEditor extends React.Component {
if (isInFencedCodeBlock(editor)) {
this.handlePasteText(editor, pastedTxt)
} else if (fetchUrlTitle && isMarkdownTitleURL(pastedTxt) && !isInLinkTag(editor)) {
} else if (
fetchUrlTitle &&
isMarkdownTitleURL(pastedTxt) &&
!isInLinkTag(editor)
) {
this.handlePasteUrl(editor, pastedTxt)
} else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(editor, pastedTxt)
@@ -1015,13 +1078,13 @@ export default class CodeEditor extends React.Component {
}
}
handleScroll (e) {
handleScroll(e) {
if (this.props.onScroll) {
this.props.onScroll(e)
}
}
handlePasteUrl (editor, pastedTxt) {
handlePasteUrl(editor, pastedTxt) {
let taggedUrl = `<${pastedTxt}>`
let urlToFetch = pastedTxt
let titleMark = ''
@@ -1071,16 +1134,16 @@ export default class CodeEditor extends React.Component {
})
}
handlePasteHtml (editor, pastedHtml) {
handlePasteHtml(editor, pastedHtml) {
const markdown = this.turndownService.turndown(pastedHtml)
editor.replaceSelection(markdown)
}
handlePasteText (editor, pastedTxt) {
handlePasteText(editor, pastedTxt) {
editor.replaceSelection(pastedTxt)
}
mapNormalResponse (response, pastedTxt) {
mapNormalResponse(response, pastedTxt) {
return this.decodeResponse(response).then(body => {
return new Promise((resolve, reject) => {
try {
@@ -1088,10 +1151,12 @@ export default class CodeEditor extends React.Component {
body,
'text/html'
)
const escapePipe = (str) => {
const escapePipe = str => {
return str.replace('|', '\\|')
}
const linkWithTitle = `[${escapePipe(parsedBody.title)}](${pastedTxt})`
const linkWithTitle = `[${escapePipe(
parsedBody.title
)}](${pastedTxt})`
resolve(linkWithTitle)
} catch (e) {
reject(e)
@@ -1100,7 +1165,7 @@ export default class CodeEditor extends React.Component {
})
}
initialHighlighting () {
initialHighlighting() {
if (this.editor.options.linesHighlighted == null) {
return
}
@@ -1114,16 +1179,20 @@ export default class CodeEditor extends React.Component {
// make sure that we skip the invalid lines althrough this case should not be happened.
continue
}
this.editor.addLineClass(lineNumber, 'text', 'CodeMirror-activeline-background')
this.editor.addLineClass(
lineNumber,
'text',
'CodeMirror-activeline-background'
)
}
}
restartHighlighting () {
restartHighlighting() {
this.editor.options.linesHighlighted = this.props.linesHighlighted
this.initialHighlighting()
}
mapImageResponse (response, pastedTxt) {
mapImageResponse(response, pastedTxt) {
return new Promise((resolve, reject) => {
try {
const url = response.url
@@ -1136,7 +1205,7 @@ export default class CodeEditor extends React.Component {
})
}
decodeResponse (response) {
decodeResponse(response) {
const headers = response.headers
const _charset = headers.has('content-type')
? this.extractContentTypeCharset(headers.get('content-type'))
@@ -1144,10 +1213,10 @@ export default class CodeEditor extends React.Component {
return response.arrayBuffer().then(buff => {
return new Promise((resolve, reject) => {
try {
const charset = _charset !== undefined &&
iconv.encodingExists(_charset)
? _charset
: 'utf-8'
const charset =
_charset !== undefined && iconv.encodingExists(_charset)
? _charset
: 'utf-8'
resolve(iconv.decode(Buffer.from(buff), charset).toString())
} catch (e) {
reject(e)
@@ -1156,50 +1225,49 @@ export default class CodeEditor extends React.Component {
})
}
extractContentTypeCharset (contentType) {
extractContentTypeCharset(contentType) {
return contentType
.split(';')
.filter(str => {
return str.trim().toLowerCase().startsWith('charset')
return str
.trim()
.toLowerCase()
.startsWith('charset')
})
.map(str => {
return str.replace(/['"]/g, '').split('=')[1]
})[0]
}
render () {
const {
className,
fontSize
} = this.props
render() {
const { className, fontSize } = this.props
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
const width = this.props.width
return (<
div className={
className == null ? 'CodeEditor' : `CodeEditor ${className}`
}
ref='root'
tabIndex='-1'
style={{
fontFamily,
fontSize: fontSize,
width: width
}}
onDrop={
e => this.handleDropImage(e)
}
return (
<div
className={className == null ? 'CodeEditor' : `CodeEditor ${className}`}
ref='root'
tabIndex='-1'
style={{
fontFamily,
fontSize: fontSize,
width: width
}}
onDrop={e => this.handleDropImage(e)}
/>
)
}
createSpellCheckPanel () {
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))
dropdown.addEventListener('change', e =>
spellcheck.setLanguage(this.editor, dropdown.value)
)
const options = spellcheck.getAvailableDictionaries()
for (const op of options) {
const option = document.createElement('option')

View File

@@ -7,7 +7,7 @@ import styles from './ColorPicker.styl'
const componentHeight = 330
class ColorPicker extends React.Component {
constructor (props) {
constructor(props) {
super(props)
this.state = {
@@ -18,21 +18,21 @@ class ColorPicker extends React.Component {
this.handleConfirm = this.handleConfirm.bind(this)
}
componentWillReceiveProps (nextProps) {
componentWillReceiveProps(nextProps) {
this.onColorChange(nextProps.color)
}
onColorChange (color) {
onColorChange(color) {
this.setState({
color
})
}
handleConfirm () {
handleConfirm() {
this.props.onConfirm(this.state.color)
}
render () {
render() {
const { onReset, onCancel, targetRect } = this.props
const { color } = this.state
@@ -44,13 +44,22 @@ class ColorPicker extends React.Component {
}
return (
<div styleName='colorPicker' style={{top: `${alignY}px`, left: `${alignX}px`}}>
<div
styleName='colorPicker'
style={{ top: `${alignY}px`, left: `${alignX}px` }}
>
<div styleName='cover' onClick={onCancel} />
<SketchPicker color={color} onChange={this.onColorChange} />
<div styleName='footer'>
<button styleName='btn-reset' onClick={onReset}>Reset</button>
<button styleName='btn-cancel' onClick={onCancel}>Cancel</button>
<button styleName='btn-confirm' onClick={this.handleConfirm}>Confirm</button>
<button styleName='btn-reset' onClick={onReset}>
Reset
</button>
<button styleName='btn-cancel' onClick={onCancel}>
Cancel
</button>
<button styleName='btn-confirm' onClick={this.handleConfirm}>
Confirm
</button>
</div>
</div>
)

View File

@@ -10,7 +10,7 @@ import ConfigManager from 'browser/main/lib/ConfigManager'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
class MarkdownEditor extends React.Component {
constructor (props) {
constructor(props) {
super(props)
// char codes for ctrl + w
@@ -20,7 +20,10 @@ class MarkdownEditor extends React.Component {
this.supportMdSelectionBold = [16, 17, 186]
this.state = {
status: props.config.editor.switchPreview === 'RIGHTCLICK' ? props.config.editor.delfaultStatus : 'CODE',
status:
props.config.editor.switchPreview === 'RIGHTCLICK'
? props.config.editor.delfaultStatus
: 'CODE',
renderValue: props.value,
keyPressed: new Set(),
isLocked: props.isLocked
@@ -29,133 +32,153 @@ class MarkdownEditor extends React.Component {
this.lockEditorCode = () => this.handleLockEditor()
}
componentDidMount () {
componentDidMount() {
this.value = this.refs.code.value
eventEmitter.on('editor:lock', this.lockEditorCode)
eventEmitter.on('editor:focus', this.focusEditor.bind(this))
}
componentDidUpdate () {
componentDidUpdate() {
this.value = this.refs.code.value
}
componentWillReceiveProps (props) {
componentWillReceiveProps(props) {
if (props.value !== this.props.value) {
this.queueRendering(props.value)
}
}
componentWillUnmount () {
componentWillUnmount() {
this.cancelQueue()
eventEmitter.off('editor:lock', this.lockEditorCode)
eventEmitter.off('editor:focus', this.focusEditor.bind(this))
}
focusEditor () {
this.setState({
status: 'CODE'
}, () => {
this.refs.code.focus()
})
focusEditor() {
this.setState(
{
status: 'CODE'
},
() => {
this.refs.code.focus()
}
)
}
queueRendering (value) {
queueRendering(value) {
clearTimeout(this.renderTimer)
this.renderTimer = setTimeout(() => {
this.renderPreview(value)
}, 500)
}
cancelQueue () {
cancelQueue() {
clearTimeout(this.renderTimer)
}
renderPreview (value) {
renderPreview(value) {
this.setState({
renderValue: value
})
}
setValue (value) {
setValue(value) {
this.refs.code.setValue(value)
}
handleChange (e) {
handleChange(e) {
this.value = this.refs.code.value
this.props.onChange(e)
}
handleContextMenu (e) {
handleContextMenu(e) {
if (this.state.isLocked) return
const { config } = this.props
if (config.editor.switchPreview === 'RIGHTCLICK') {
const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW'
this.setState({
status: newStatus
}, () => {
if (newStatus === 'CODE') {
this.refs.code.focus()
} else {
this.refs.preview.focus()
}
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
this.setState(
{
status: newStatus
},
() => {
if (newStatus === 'CODE') {
this.refs.code.focus()
} else {
this.refs.preview.focus()
}
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
const newConfig = Object.assign({}, config)
newConfig.editor.delfaultStatus = newStatus
ConfigManager.set(newConfig)
})
const newConfig = Object.assign({}, config)
newConfig.editor.delfaultStatus = newStatus
ConfigManager.set(newConfig)
}
)
}
}
handleBlur (e) {
handleBlur(e) {
if (this.state.isLocked) return
this.setState({ keyPressed: new Set() })
const { config } = this.props
if (config.editor.switchPreview === 'BLUR' ||
(config.editor.switchPreview === 'DBL_CLICK' && this.state.status === 'CODE')
if (
config.editor.switchPreview === 'BLUR' ||
(config.editor.switchPreview === 'DBL_CLICK' &&
this.state.status === 'CODE')
) {
const cursorPosition = this.refs.code.editor.getCursor()
this.setState({
status: 'PREVIEW'
}, () => {
this.refs.preview.focus()
this.refs.preview.scrollToRow(cursorPosition.line)
})
this.setState(
{
status: 'PREVIEW'
},
() => {
this.refs.preview.focus()
this.refs.preview.scrollToRow(cursorPosition.line)
}
)
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
}
}
handleDoubleClick (e) {
handleDoubleClick(e) {
if (this.state.isLocked) return
this.setState({keyPressed: new Set()})
this.setState({ keyPressed: new Set() })
const { config } = this.props
if (config.editor.switchPreview === 'DBL_CLICK') {
this.setState({
status: 'CODE'
}, () => {
this.refs.code.focus()
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
})
this.setState(
{
status: 'CODE'
},
() => {
this.refs.code.focus()
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
}
)
}
}
handlePreviewMouseDown (e) {
handlePreviewMouseDown(e) {
this.previewMouseDownedAt = new Date()
}
handlePreviewMouseUp (e) {
handlePreviewMouseUp(e) {
const { config } = this.props
if (config.editor.switchPreview === 'BLUR' && new Date() - this.previewMouseDownedAt < 200) {
this.setState({
status: 'CODE'
}, () => {
this.refs.code.focus()
})
if (
config.editor.switchPreview === 'BLUR' &&
new Date() - this.previewMouseDownedAt < 200
) {
this.setState(
{
status: 'CODE'
},
() => {
this.refs.code.focus()
}
)
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
}
}
handleCheckboxClick (e) {
handleCheckboxClick(e) {
e.preventDefault()
e.stopPropagation()
const idMatch = /checkbox-([0-9]+)/
@@ -164,9 +187,9 @@ class MarkdownEditor extends React.Component {
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
.split('\n')
const lineIndex =
parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
const lines = this.refs.code.value.split('\n')
const targetLine = lines[lineIndex]
let newLine = targetLine
@@ -181,45 +204,56 @@ class MarkdownEditor extends React.Component {
}
}
focus () {
focus() {
if (this.state.status === 'PREVIEW') {
this.setState({
status: 'CODE'
}, () => {
this.refs.code.focus()
})
this.setState(
{
status: 'CODE'
},
() => {
this.refs.code.focus()
}
)
} else {
this.refs.code.focus()
}
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
}
reload () {
reload() {
this.refs.code.reload()
this.cancelQueue()
this.renderPreview(this.props.value)
}
handleKeyDown (e) {
handleKeyDown(e) {
const { config } = this.props
if (this.state.status !== 'CODE') return false
const keyPressed = this.state.keyPressed
keyPressed.add(e.keyCode)
this.setState({ keyPressed })
const isNoteHandlerKey = (el) => { return keyPressed.has(el) }
const isNoteHandlerKey = el => {
return keyPressed.has(el)
}
// These conditions are for ctrl-e and ctrl-w
if (keyPressed.size === this.escapeFromEditor.length &&
!this.state.isLocked && this.state.status === 'CODE' &&
this.escapeFromEditor.every(isNoteHandlerKey)) {
if (
keyPressed.size === this.escapeFromEditor.length &&
!this.state.isLocked &&
this.state.status === 'CODE' &&
this.escapeFromEditor.every(isNoteHandlerKey)
) {
this.handleContextMenu()
if (config.editor.switchPreview === 'BLUR') document.activeElement.blur()
}
if (keyPressed.size === this.supportMdSelectionBold.length && this.supportMdSelectionBold.every(isNoteHandlerKey)) {
if (
keyPressed.size === this.supportMdSelectionBold.length &&
this.supportMdSelectionBold.every(isNoteHandlerKey)
) {
this.addMdAroundWord('**')
}
}
addMdAroundWord (mdElement) {
addMdAroundWord(mdElement) {
if (this.refs.code.editor.getSelection()) {
return this.addMdAroundSelection(mdElement)
}
@@ -227,47 +261,63 @@ class MarkdownEditor extends React.Component {
const word = this.refs.code.editor.findWordAt(currentCaret)
const cmDoc = this.refs.code.editor.getDoc()
cmDoc.replaceRange(mdElement, word.anchor)
cmDoc.replaceRange(mdElement, { line: word.head.line, ch: word.head.ch + mdElement.length })
}
addMdAroundSelection (mdElement) {
this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`)
}
handleDropImage (dropEvent) {
dropEvent.preventDefault()
const { storageKey, noteKey } = this.props
this.setState({
status: 'CODE'
}, () => {
this.refs.code.focus()
this.refs.code.editor.execCommand('goDocEnd')
this.refs.code.editor.execCommand('goLineEnd')
this.refs.code.editor.execCommand('newlineAndIndent')
attachmentManagement.handleAttachmentDrop(
this.refs.code,
storageKey,
noteKey,
dropEvent
)
cmDoc.replaceRange(mdElement, {
line: word.head.line,
ch: word.head.ch + mdElement.length
})
}
handleKeyUp (e) {
addMdAroundSelection(mdElement) {
this.refs.code.editor.replaceSelection(
`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`
)
}
handleDropImage(dropEvent) {
dropEvent.preventDefault()
const { storageKey, noteKey } = this.props
this.setState(
{
status: 'CODE'
},
() => {
this.refs.code.focus()
this.refs.code.editor.execCommand('goDocEnd')
this.refs.code.editor.execCommand('goLineEnd')
this.refs.code.editor.execCommand('newlineAndIndent')
attachmentManagement.handleAttachmentDrop(
this.refs.code,
storageKey,
noteKey,
dropEvent
)
}
)
}
handleKeyUp(e) {
const keyPressed = this.state.keyPressed
keyPressed.delete(e.keyCode)
this.setState({ keyPressed })
}
handleLockEditor () {
handleLockEditor() {
this.setState({ isLocked: !this.state.isLocked })
}
render () {
const {className, value, config, storageKey, noteKey, linesHighlighted, RTL} = this.props
render() {
const {
className,
value,
config,
storageKey,
noteKey,
linesHighlighted,
RTL
} = this.props
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
@@ -275,23 +325,24 @@ class MarkdownEditor extends React.Component {
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
const previewStyle = {}
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
if (this.props.ignorePreviewPointerEvents)
previewStyle.pointerEvents = 'none'
const storage = findStorage(storageKey)
return (
<div className={className == null
? 'MarkdownEditor'
: `MarkdownEditor ${className}`
<div
className={
className == null ? 'MarkdownEditor' : `MarkdownEditor ${className}`
}
onContextMenu={(e) => this.handleContextMenu(e)}
onContextMenu={e => this.handleContextMenu(e)}
tabIndex='-1'
onKeyDown={(e) => this.handleKeyDown(e)}
onKeyUp={(e) => this.handleKeyUp(e)}
onKeyDown={e => this.handleKeyDown(e)}
onKeyUp={e => this.handleKeyUp(e)}
>
<CodeEditor styleName={this.state.status === 'CODE'
? 'codeEditor'
: 'codeEditor--hide'
<CodeEditor
styleName={
this.state.status === 'CODE' ? 'codeEditor' : 'codeEditor--hide'
}
ref='code'
mode='Boost Flavored Markdown'
@@ -315,8 +366,8 @@ class MarkdownEditor extends React.Component {
fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
linesHighlighted={linesHighlighted}
onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)}
onChange={e => this.handleChange(e)}
onBlur={e => this.handleBlur(e)}
spellCheck={config.editor.spellcheck}
enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey}
@@ -327,9 +378,9 @@ class MarkdownEditor extends React.Component {
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
RTL={RTL}
/>
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
? 'preview'
: 'preview--hide'
<MarkdownPreview
styleName={
this.state.status === 'PREVIEW' ? 'preview' : 'preview--hide'
}
style={previewStyle}
theme={config.ui.theme}
@@ -347,20 +398,20 @@ class MarkdownEditor extends React.Component {
sanitize={config.preview.sanitize}
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
ref='preview'
onContextMenu={(e) => this.handleContextMenu(e)}
onDoubleClick={(e) => this.handleDoubleClick(e)}
onContextMenu={e => this.handleContextMenu(e)}
onDoubleClick={e => this.handleDoubleClick(e)}
tabIndex='0'
value={this.state.renderValue}
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
onMouseUp={e => this.handlePreviewMouseUp(e)}
onMouseDown={e => this.handlePreviewMouseDown(e)}
onCheckboxClick={e => this.handleCheckboxClick(e)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
noteKey={noteKey}
customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS}
lineThroughCheckbox={config.preview.lineThroughCheckbox}
onDrop={(e) => this.handleDropImage(e)}
onDrop={e => this.handleDropImage(e)}
RTL={RTL}
/>
</div>

View File

@@ -26,7 +26,8 @@ import i18n from 'browser/lib/i18n'
const { remote, shell } = require('electron')
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
const buildMarkdownPreviewContextMenu = require('browser/lib/contextMenuBuilder').buildMarkdownPreviewContextMenu
const buildMarkdownPreviewContextMenu = require('browser/lib/contextMenuBuilder')
.buildMarkdownPreviewContextMenu
const { app } = remote
const path = require('path')
@@ -56,7 +57,7 @@ const CSS_FILES = [
* @param {String} [opts.customCSS] Will be added to bottom, only if `opts.allowCustomCSS` is truthy
* @returns {String}
*/
function buildStyle (opts) {
function buildStyle(opts) {
const {
fontFamily,
fontSize,
@@ -104,11 +105,14 @@ body {
font-family: '${fontFamily.join("','")}';
font-size: ${fontSize}px;
${scrollPastEnd ? `
${
scrollPastEnd
? `
padding-bottom: 90vh;
box-sizing: border-box;
`
: ''}
: ''
}
${RTL ? 'direction: rtl;' : ''}
${RTL ? 'text-align: right;' : ''}
}
@@ -225,7 +229,7 @@ const defaultCodeBlockFontFamily = [
// return the line number of the line that used to generate the specified element
// return -1 if the line is not found
function getSourceLineNumberByElement (element) {
function getSourceLineNumberByElement(element) {
let isHasLineNumber = element.dataset.line !== undefined
let parent = element
while (!isHasLineNumber && parent.parentElement !== null) {
@@ -236,7 +240,7 @@ function getSourceLineNumberByElement (element) {
}
export default class MarkdownPreview extends React.Component {
constructor (props) {
constructor(props) {
super(props)
this.contextMenuHandler = e => this.handleContextMenu(e)
@@ -260,7 +264,7 @@ export default class MarkdownPreview extends React.Component {
this.initMarkdown()
}
initMarkdown () {
initMarkdown() {
const { smartQuotes, sanitize, breaks } = this.props
this.markdown = new Markdown({
typographer: smartQuotes,
@@ -269,17 +273,17 @@ export default class MarkdownPreview extends React.Component {
})
}
handleCheckboxClick (e) {
handleCheckboxClick(e) {
this.props.onCheckboxClick(e)
}
handleScroll (e) {
handleScroll(e) {
if (this.props.onScroll) {
this.props.onScroll(e)
}
}
handleContextMenu (event) {
handleContextMenu(event) {
const menu = buildMarkdownPreviewContextMenu(this, event)
const switchPreview = ConfigManager.get().editor.switchPreview
if (menu != null && switchPreview !== 'RIGHTCLICK') {
@@ -289,17 +293,21 @@ export default class MarkdownPreview extends React.Component {
}
}
handleDoubleClick (e) {
handleDoubleClick(e) {
if (this.props.onDoubleClick != null) this.props.onDoubleClick(e)
}
handleMouseDown (e) {
handleMouseDown(e) {
const config = ConfigManager.get()
const clickElement = e.target
const targetTag = clickElement.tagName // The direct parent HTML of where was clicked ie "BODY" or "DIV"
const lineNumber = getSourceLineNumberByElement(clickElement) // Line location of element clicked.
if (config.editor.switchPreview === 'RIGHTCLICK' && e.buttons === 2 && config.editor.type === 'SPLIT') {
if (
config.editor.switchPreview === 'RIGHTCLICK' &&
e.buttons === 2 &&
config.editor.type === 'SPLIT'
) {
eventEmitter.emit('topbar:togglemodebutton', 'CODE')
}
if (e.ctrlKey) {
@@ -315,10 +323,11 @@ export default class MarkdownPreview extends React.Component {
}
}
if (this.props.onMouseDown != null && targetTag === 'BODY') this.props.onMouseDown(e)
if (this.props.onMouseDown != null && targetTag === 'BODY')
this.props.onMouseDown(e)
}
handleMouseUp (e) {
handleMouseUp(e) {
if (!this.props.onMouseUp) return
if (e.target != null && e.target.tagName === 'A') {
return null
@@ -326,15 +335,15 @@ export default class MarkdownPreview extends React.Component {
if (this.props.onMouseUp != null) this.props.onMouseUp(e)
}
handleSaveAsText () {
handleSaveAsText() {
this.exportAsDocument('txt')
}
handleSaveAsMd () {
handleSaveAsMd() {
this.exportAsDocument('md')
}
htmlContentFormatter (noteContent, exportTasks, targetDir) {
htmlContentFormatter(noteContent, exportTasks, targetDir) {
const {
fontFamily,
fontSize,
@@ -360,10 +369,7 @@ export default class MarkdownPreview extends React.Component {
RTL
})
let body = this.refs.root.contentWindow.document.body.innerHTML
body = attachmentManagement.fixLocalURLS(
body,
this.props.storagePath
)
body = attachmentManagement.fixLocalURLS(body, this.props.storagePath)
const files = [this.getCodeThemeLink(codeBlockTheme), ...CSS_FILES]
files.forEach(file => {
if (global.process.platform === 'win32') {
@@ -394,14 +400,24 @@ export default class MarkdownPreview extends React.Component {
</html>`
}
handleSaveAsHtml () {
this.exportAsDocument('html', (noteContent, exportTasks, targetDir) => Promise.resolve(this.htmlContentFormatter(noteContent, exportTasks, targetDir)))
handleSaveAsHtml() {
this.exportAsDocument('html', (noteContent, exportTasks, targetDir) =>
Promise.resolve(
this.htmlContentFormatter(noteContent, exportTasks, targetDir)
)
)
}
handleSaveAsPdf () {
handleSaveAsPdf() {
this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => {
const printout = new remote.BrowserWindow({show: false, webPreferences: {webSecurity: false, javascript: false}})
printout.loadURL('data:text/html;charset=UTF-8,' + this.htmlContentFormatter(noteContent, exportTasks, targetDir))
const printout = new remote.BrowserWindow({
show: false,
webPreferences: { webSecurity: false, javascript: false }
})
printout.loadURL(
'data:text/html;charset=UTF-8,' +
this.htmlContentFormatter(noteContent, exportTasks, targetDir)
)
return new Promise((resolve, reject) => {
printout.webContents.on('did-finish-load', () => {
printout.webContents.printToPDF({}, (err, data) => {
@@ -414,11 +430,11 @@ export default class MarkdownPreview extends React.Component {
})
}
handlePrint () {
handlePrint() {
this.refs.root.contentWindow.print()
}
exportAsDocument (fileType, contentFormatter) {
exportAsDocument(fileType, contentFormatter) {
const options = {
filters: [{ name: 'Documents', extensions: [fileType] }],
properties: ['openFile', 'createDirectory']
@@ -449,7 +465,7 @@ export default class MarkdownPreview extends React.Component {
})
}
fixDecodedURI (node) {
fixDecodedURI(node) {
if (
node &&
node.children.length === 1 &&
@@ -466,17 +482,18 @@ export default class MarkdownPreview extends React.Component {
* @param {string[]} splitWithCodeTag Array of HTML strings separated by three ```
* @returns {string} HTML in which special characters between three ``` have been converted
*/
escapeHtmlCharactersInCodeTag (splitWithCodeTag) {
escapeHtmlCharactersInCodeTag(splitWithCodeTag) {
for (let index = 0; index < splitWithCodeTag.length; index++) {
const codeTagRequired = (splitWithCodeTag[index] !== '\`\`\`' && index < splitWithCodeTag.length - 1)
const codeTagRequired =
splitWithCodeTag[index] !== '```' && index < splitWithCodeTag.length - 1
if (codeTagRequired) {
splitWithCodeTag.splice((index + 1), 0, '\`\`\`')
splitWithCodeTag.splice(index + 1, 0, '```')
}
}
let inCodeTag = false
let result = ''
for (let content of splitWithCodeTag) {
if (content === '\`\`\`') {
if (content === '```') {
inCodeTag = !inCodeTag
} else if (inCodeTag) {
content = escapeHtmlCharacters(content)
@@ -486,13 +503,15 @@ export default class MarkdownPreview extends React.Component {
return result
}
getScrollBarStyle () {
getScrollBarStyle() {
const { theme } = this.props
return uiThemes.some(item => item.name === theme && item.isDark) ? scrollBarDarkStyle : scrollBarStyle
return uiThemes.some(item => item.name === theme && item.isDark)
? scrollBarDarkStyle
: scrollBarStyle
}
componentDidMount () {
componentDidMount() {
const { onDrop } = this.props
this.refs.root.setAttribute('sandbox', 'allow-scripts')
@@ -542,10 +561,7 @@ export default class MarkdownPreview extends React.Component {
'scroll',
this.scrollHandler
)
this.refs.root.contentWindow.addEventListener(
'resize',
this.resizeHandler
)
this.refs.root.contentWindow.addEventListener('resize', this.resizeHandler)
eventEmitter.on('export:save-text', this.saveAsTextHandler)
eventEmitter.on('export:save-md', this.saveAsMdHandler)
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
@@ -553,7 +569,7 @@ export default class MarkdownPreview extends React.Component {
eventEmitter.on('print', this.printHandler)
}
componentWillUnmount () {
componentWillUnmount() {
const { onDrop } = this.props
this.refs.root.contentWindow.document.body.removeEventListener(
@@ -595,7 +611,7 @@ export default class MarkdownPreview extends React.Component {
eventEmitter.off('print', this.printHandler)
}
componentDidUpdate (prevProps) {
componentDidUpdate(prevProps) {
// actual rewriteIframe function should be called only once
let needsRewriteIframe = false
if (prevProps.value !== this.props.value) needsRewriteIframe = true
@@ -637,7 +653,7 @@ export default class MarkdownPreview extends React.Component {
}
}
getStyleParams () {
getStyleParams() {
const {
fontSize,
lineNumber,
@@ -649,19 +665,20 @@ export default class MarkdownPreview extends React.Component {
RTL
} = this.props
let { fontFamily, codeBlockFontFamily } = this.props
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
? fontFamily
.split(',')
.map(fontName => fontName.trim())
.concat(defaultFontFamily)
: defaultFontFamily
codeBlockFontFamily = _.isString(codeBlockFontFamily) &&
codeBlockFontFamily.trim().length > 0
? codeBlockFontFamily
.split(',')
.map(fontName => fontName.trim())
.concat(defaultCodeBlockFontFamily)
: defaultCodeBlockFontFamily
fontFamily =
_.isString(fontFamily) && fontFamily.trim().length > 0
? fontFamily
.split(',')
.map(fontName => fontName.trim())
.concat(defaultFontFamily)
: defaultFontFamily
codeBlockFontFamily =
_.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0
? codeBlockFontFamily
.split(',')
.map(fontName => fontName.trim())
.concat(defaultCodeBlockFontFamily)
: defaultCodeBlockFontFamily
return {
fontFamily,
@@ -677,7 +694,7 @@ export default class MarkdownPreview extends React.Component {
}
}
applyStyle () {
applyStyle() {
const {
fontFamily,
fontSize,
@@ -707,7 +724,7 @@ export default class MarkdownPreview extends React.Component {
})
}
getCodeThemeLink (name) {
getCodeThemeLink(name) {
const theme = consts.THEMES.find(theme => theme.name === name)
return theme != null
@@ -715,7 +732,7 @@ export default class MarkdownPreview extends React.Component {
: `${appPath}/node_modules/codemirror/theme/elegant.css`
}
rewriteIframe () {
rewriteIframe() {
_.forEach(
this.refs.root.contentWindow.document.querySelectorAll(
'input[type="checkbox"]'
@@ -773,7 +790,9 @@ export default class MarkdownPreview extends React.Component {
codeBlockTheme = consts.THEMES.find(theme => theme.name === codeBlockTheme)
const codeBlockThemeClassName = codeBlockTheme ? codeBlockTheme.className : 'cm-s-default'
const codeBlockThemeClassName = codeBlockTheme
? codeBlockTheme.className
: 'cm-s-default'
_.forEach(
this.refs.root.contentWindow.document.querySelectorAll('.code code'),
@@ -859,7 +878,10 @@ export default class MarkdownPreview extends React.Component {
el => {
try {
const format = el.attributes.getNamedItem('data-format').value
const chartConfig = format === 'yaml' ? yaml.load(el.innerHTML) : JSON.parse(el.innerHTML)
const chartConfig =
format === 'yaml'
? yaml.load(el.innerHTML)
: JSON.parse(el.innerHTML)
el.innerHTML = ''
const canvas = document.createElement('canvas')
@@ -882,7 +904,12 @@ export default class MarkdownPreview extends React.Component {
_.forEach(
this.refs.root.contentWindow.document.querySelectorAll('.mermaid'),
el => {
mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme, mermaidHTMLLabel)
mermaidRender(
el,
htmlTextHelper.decodeEntities(el.innerHTML),
theme,
mermaidHTMLLabel
)
}
)
@@ -904,20 +931,14 @@ export default class MarkdownPreview extends React.Component {
autoplay = 0
}
render(
<Carousel
images={images}
autoplay={autoplay}
/>,
el
)
render(<Carousel images={images} autoplay={autoplay} />, el)
}
)
const markdownPreviewIframe = document.querySelector('.MarkdownPreview')
const rect = markdownPreviewIframe.getBoundingClientRect()
const config = { attributes: true, subtree: true }
const imgObserver = new MutationObserver((mutationList) => {
const imgObserver = new MutationObserver(mutationList => {
for (const mu of mutationList) {
if (mu.target.className === 'carouselContent-enter-done') {
this.setImgOnClickEventHelper(mu.target, rect)
@@ -926,26 +947,32 @@ export default class MarkdownPreview extends React.Component {
}
})
const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('img')
const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll(
'img'
)
for (const img of imgList) {
const parentEl = img.parentElement
this.setImgOnClickEventHelper(img, rect)
imgObserver.observe(parentEl, config)
}
const aList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('a')
const aList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll(
'a'
)
for (const a of aList) {
a.removeEventListener('click', this.linkClickHandler)
a.addEventListener('click', this.linkClickHandler)
}
}
setImgOnClickEventHelper (img, rect) {
setImgOnClickEventHelper(img, rect) {
img.onclick = () => {
const widthMagnification = document.body.clientWidth / img.width
const heightMagnification = document.body.clientHeight / img.height
const baseOnWidth = widthMagnification < heightMagnification
const magnification = baseOnWidth ? widthMagnification : heightMagnification
const magnification = baseOnWidth
? widthMagnification
: heightMagnification
const zoomImgWidth = img.width * magnification
const zoomImgHeight = img.height * magnification
@@ -976,10 +1003,7 @@ export default class MarkdownPreview extends React.Component {
width: ${zoomImgWidth};
height: ${zoomImgHeight}px;
`
zoomImg.animate([
originalImgRect,
zoomInImgRect
], animationSpeed)
zoomImg.animate([originalImgRect, zoomInImgRect], animationSpeed)
const overlay = document.createElement('div')
overlay.style = `
@@ -1000,10 +1024,10 @@ export default class MarkdownPreview extends React.Component {
width: ${img.width}px;
height: ${img.height}px;
`
const zoomOutImgAnimation = zoomImg.animate([
zoomInImgRect,
originalImgRect
], animationSpeed)
const zoomOutImgAnimation = zoomImg.animate(
[zoomInImgRect, originalImgRect],
animationSpeed
)
zoomOutImgAnimation.onfinish = () => overlay.remove()
}
@@ -1012,7 +1036,7 @@ export default class MarkdownPreview extends React.Component {
}
}
handleResize () {
handleResize() {
_.forEach(
this.refs.root.contentWindow.document.querySelectorAll('svg[ratio]'),
el => {
@@ -1021,11 +1045,11 @@ export default class MarkdownPreview extends React.Component {
)
}
focus () {
focus() {
this.refs.root.focus()
}
getWindow () {
getWindow() {
return this.refs.root.contentWindow
}
@@ -1033,7 +1057,7 @@ export default class MarkdownPreview extends React.Component {
* @public
* @param {Number} targetRow
*/
scrollToRow (targetRow) {
scrollToRow(targetRow) {
const blocks = this.getWindow().document.querySelectorAll(
'body>[data-line]'
)
@@ -1054,16 +1078,16 @@ export default class MarkdownPreview extends React.Component {
* @param {Number} x
* @param {Number} y
*/
scrollTo (x, y) {
scrollTo(x, y) {
this.getWindow().document.body.scrollTo(x, y)
}
preventImageDroppedHandler (e) {
preventImageDroppedHandler(e) {
e.preventDefault()
e.stopPropagation()
}
notify (title, options) {
notify(title, options) {
if (global.process.platform === 'win32') {
options.icon = path.join(
'file://',
@@ -1074,7 +1098,7 @@ export default class MarkdownPreview extends React.Component {
return new window.Notification(title, options)
}
handleLinkClick (e) {
handleLinkClick(e) {
e.preventDefault()
e.stopPropagation()
@@ -1095,9 +1119,7 @@ export default class MarkdownPreview extends React.Component {
if (posOfHash > -1) {
const extractedId = linkHash.slice(posOfHash + 1)
const targetId = mdurl.encode(extractedId)
const targetElement = this.getWindow().document.getElementById(
targetId
)
const targetElement = this.getWindow().document.getElementById(targetId)
if (targetElement != null) {
this.scrollTo(0, targetElement.offsetTop)
@@ -1138,9 +1160,10 @@ export default class MarkdownPreview extends React.Component {
this.openExternal(href)
}
openExternal (href) {
openExternal(href) {
try {
const success = shell.openExternal(href) || shell.openExternal(decodeURI(href))
const success =
shell.openExternal(href) || shell.openExternal(decodeURI(href))
if (!success) console.error('failed to open url ' + href)
} catch (e) {
// URI Error threw from decodeURI
@@ -1148,7 +1171,7 @@ export default class MarkdownPreview extends React.Component {
}
}
render () {
render() {
const { className, style, tabIndex } = this.props
return (
<iframe

View File

@@ -8,7 +8,7 @@ import styles from './MarkdownSplitEditor.styl'
import CSSModules from 'browser/lib/CSSModules'
class MarkdownSplitEditor extends React.Component {
constructor (props) {
constructor(props) {
super(props)
this.value = props.value
this.focus = () => this.refs.code.focus()
@@ -20,19 +20,22 @@ class MarkdownSplitEditor extends React.Component {
}
}
setValue (value) {
setValue(value) {
this.refs.code.setValue(value)
}
handleOnChange (e) {
handleOnChange(e) {
this.value = this.refs.code.value
this.props.onChange(e)
}
handleScroll (e) {
handleScroll(e) {
if (!this.props.config.preview.scrollSync) return
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
const previewDoc = _.get(
this,
'refs.preview.refs.root.contentWindow.document'
)
const codeDoc = _.get(this, 'refs.code.editor.doc')
let srcTop, srcHeight, targetTop, targetHeight
@@ -49,7 +52,7 @@ class MarkdownSplitEditor extends React.Component {
targetHeight = _.get(codeDoc, 'height')
}
const distance = (targetHeight * srcTop / srcHeight) - targetTop
const distance = (targetHeight * srcTop) / srcHeight - targetTop
const framerate = 1000 / 60
const frames = 20
const refractory = frames * framerate
@@ -60,21 +63,29 @@ class MarkdownSplitEditor extends React.Component {
let scrollPos, time
const timer = setInterval(() => {
time = frame / frames
scrollPos = time < 0.5
? 2 * time * time // ease in
: -1 + (4 - 2 * time) * time // ease out
if (e.doc) _.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
else _.get(this, 'refs.code.editor').scrollTo(0, targetTop + scrollPos * distance)
scrollPos =
time < 0.5
? 2 * time * time // ease in
: -1 + (4 - 2 * time) * time // ease out
if (e.doc)
_.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
else
_.get(this, 'refs.code.editor').scrollTo(
0,
targetTop + scrollPos * distance
)
if (frame >= frames) {
clearInterval(timer)
setTimeout(() => { this.userScroll = true }, refractory)
setTimeout(() => {
this.userScroll = true
}, refractory)
}
frame++
}, framerate)
}
}
handleCheckboxClick (e) {
handleCheckboxClick(e) {
e.preventDefault()
e.stopPropagation()
const idMatch = /checkbox-([0-9]+)/
@@ -83,9 +94,9 @@ class MarkdownSplitEditor extends React.Component {
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
.split('\n')
const lineIndex =
parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
const lines = this.refs.code.value.split('\n')
const targetLine = lines[lineIndex]
let newLine = targetLine
@@ -100,12 +111,12 @@ class MarkdownSplitEditor extends React.Component {
}
}
handleMouseMove (e) {
handleMouseMove(e) {
if (this.state.isSliderFocused) {
const rootRect = this.refs.root.getBoundingClientRect()
const rootWidth = rootRect.width
const offset = rootRect.left
let newCodeEditorWidthInPercent = (e.pageX - offset) / rootWidth * 100
let newCodeEditorWidthInPercent = ((e.pageX - offset) / rootWidth) * 100
// limit minSize to 10%, maxSize to 90%
if (newCodeEditorWidthInPercent <= 10) {
@@ -122,34 +133,45 @@ class MarkdownSplitEditor extends React.Component {
}
}
handleMouseUp (e) {
handleMouseUp(e) {
e.preventDefault()
this.setState({
isSliderFocused: false
})
}
handleMouseDown (e) {
handleMouseDown(e) {
e.preventDefault()
this.setState({
isSliderFocused: true
})
}
render () {
const {config, value, storageKey, noteKey, linesHighlighted, RTL} = this.props
render() {
const {
config,
value,
storageKey,
noteKey,
linesHighlighted,
RTL
} = this.props
const storage = findStorage(storageKey)
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
let editorIndentSize = parseInt(config.editor.indentSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
const previewStyle = {}
previewStyle.width = (100 - this.state.codeEditorWidthInPercent) + '%'
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused) previewStyle.pointerEvents = 'none'
previewStyle.width = 100 - this.state.codeEditorWidthInPercent + '%'
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused)
previewStyle.pointerEvents = 'none'
return (
<div styleName='root' ref='root'
<div
styleName='root'
ref='root'
onMouseMove={e => this.handleMouseMove(e)}
onMouseUp={e => this.handleMouseUp(e)}>
onMouseUp={e => this.handleMouseUp(e)}
>
<CodeEditor
ref='code'
width={this.state.codeEditorWidthInPercent + '%'}
@@ -174,7 +196,7 @@ class MarkdownSplitEditor extends React.Component {
storageKey={storageKey}
noteKey={noteKey}
linesHighlighted={linesHighlighted}
onChange={(e) => this.handleOnChange(e)}
onChange={e => this.handleOnChange(e)}
onScroll={this.handleScroll.bind(this)}
spellCheck={config.editor.spellcheck}
enableSmartPaste={config.editor.enableSmartPaste}
@@ -184,8 +206,12 @@ class MarkdownSplitEditor extends React.Component {
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
RTL={RTL}
/>
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
/>
<div
styleName='slider'
style={{ left: this.state.codeEditorWidthInPercent + '%' }}
onMouseDown={e => this.handleMouseDown(e)}
>
<div styleName='slider-hitbox' />
</div>
<MarkdownPreview
@@ -206,7 +232,7 @@ class MarkdownSplitEditor extends React.Component {
ref='preview'
tabInde='0'
value={value}
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
onCheckboxClick={e => this.handleCheckboxClick(e)}
onScroll={this.handleScroll.bind(this)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
@@ -215,7 +241,7 @@ class MarkdownSplitEditor extends React.Component {
allowCustomCSS={config.preview.allowCustomCSS}
lineThroughCheckbox={config.preview.lineThroughCheckbox}
RTL={RTL}
/>
/>
</div>
)
}

View File

@@ -3,9 +3,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ModalEscButton.styl'
const ModalEscButton = ({
handleEscButtonClick
}) => (
const ModalEscButton = ({ handleEscButtonClick }) => (
<button styleName='escButton' onClick={handleEscButtonClick}>
<div styleName='esc-mark'>×</div>
<div>esc</div>

View File

@@ -1,24 +1,23 @@
/**
* @fileoverview Micro component for toggle SideNav
*/
* @fileoverview Micro component for toggle SideNav
*/
import PropTypes from 'prop-types'
import React from 'react'
import styles from './NavToggleButton.styl'
import CSSModules from 'browser/lib/CSSModules'
/**
* @param {boolean} isFolded
* @param {Function} handleToggleButtonClick
*/
* @param {boolean} isFolded
* @param {Function} handleToggleButtonClick
*/
const NavToggleButton = ({isFolded, handleToggleButtonClick}) => (
<button styleName='navToggle'
onClick={(e) => handleToggleButtonClick(e)}
>
{isFolded
? <i className='fa fa-angle-double-right fa-2x' />
: <i className='fa fa-angle-double-left fa-2x' />
}
const NavToggleButton = ({ isFolded, handleToggleButtonClick }) => (
<button styleName='navToggle' onClick={e => handleToggleButtonClick(e)}>
{isFolded ? (
<i className='fa fa-angle-double-right fa-2x' />
) : (
<i className='fa fa-angle-double-left fa-2x' />
)}
</button>
)

View File

@@ -22,7 +22,11 @@ const TagElement = ({ tagName, color }) => {
const style = {}
if (color) {
style.backgroundColor = color
style.color = invertColor(color, { black: '#222', white: '#f1f1f1', threshold: 0.3 })
style.color = invertColor(color, {
black: '#222',
white: '#f1f1f1',
threshold: 0.3
})
}
return (
<span styleName='item-bottom-tagList-item' key={tagName} style={style}>
@@ -44,9 +48,13 @@ const TagElementList = (tags, showTagsAlphabetically, coloredTags) => {
}
if (showTagsAlphabetically) {
return sortBy(tags).map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
return sortBy(tags).map(tag =>
TagElement({ tagName: tag, color: coloredTags[tag] })
)
} else {
return tags.map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
return tags.map(tag =>
TagElement({ tagName: tag, color: coloredTags[tag] })
)
}
}
@@ -83,13 +91,17 @@ const NoteItem = ({
draggable='true'
>
<div styleName='item-wrapper'>
{note.type === 'SNIPPET_NOTE'
? <i styleName='item-title-icon' className='fa fa-fw fa-code' />
: <i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />}
{note.type === 'SNIPPET_NOTE' ? (
<i styleName='item-title-icon' className='fa fa-fw fa-code' />
) : (
<i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />
)}
<div styleName='item-title'>
{note.title.trim().length > 0
? <Emoji text={note.title} />
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>}
{note.title.trim().length > 0 ? (
<Emoji text={note.title} />
) : (
<span styleName='item-title-empty'>{i18n.__('Empty note')}</span>
)}
</div>
<div styleName='item-middle'>
<div styleName='item-middle-time'>{dateDisplay}</div>
@@ -98,7 +110,9 @@ const NoteItem = ({
title={
viewType === 'ALL'
? storageName
: viewType === 'STORAGE' ? folderName : null
: viewType === 'STORAGE'
? folderName
: null
}
styleName='item-middle-app-meta-label'
>
@@ -109,28 +123,36 @@ const NoteItem = ({
</div>
<div styleName='item-bottom'>
<div styleName='item-bottom-tagList'>
{note.tags.length > 0
? TagElementList(note.tags, showTagsAlphabetically, coloredTags)
: <span
{note.tags.length > 0 ? (
TagElementList(note.tags, showTagsAlphabetically, coloredTags)
) : (
<span
style={{ fontStyle: 'italic', opacity: 0.5 }}
styleName='item-bottom-tagList-empty'
>
>
{i18n.__('No tags')}
</span>}
</span>
)}
</div>
<div>
{note.isStarred
? <img
{note.isStarred ? (
<img
styleName='item-star'
src='../resources/icon/icon-starred.svg'
/>
: ''}
{note.isPinned && !pathname.match(/\/starred|\/trash/)
? <i styleName='item-pin' className='fa fa-thumb-tack' />
: ''}
{note.type === 'MARKDOWN_NOTE'
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
: ''}
/>
) : (
''
)}
{note.isPinned && !pathname.match(/\/starred|\/trash/) ? (
<i styleName='item-pin' className='fa fa-thumb-tack' />
) : (
''
)}
{note.type === 'MARKDOWN_NOTE' ? (
<TodoProcess todoStatus={getTodoStatus(note.content)} />
) : (
''
)}
</div>
</div>
</div>

View File

@@ -25,10 +25,8 @@ const NoteItemSimple = ({
pathname,
storage
}) => (
<div styleName={isActive
? 'item-simple--active'
: 'item-simple'
}
<div
styleName={isActive ? 'item-simple--active' : 'item-simple'}
key={note.key}
onClick={e => handleNoteClick(e, note.key)}
onContextMenu={e => handleNoteContextMenu(e, note.key)}
@@ -36,23 +34,29 @@ const NoteItemSimple = ({
draggable='true'
>
<div styleName='item-simple-title'>
{note.type === 'SNIPPET_NOTE'
? <i styleName='item-simple-title-icon' className='fa fa-fw fa-code' />
: <i styleName='item-simple-title-icon' className='fa fa-fw fa-file-text-o' />
}
{note.isPinned && !pathname.match(/\/starred|\/trash/)
? <i styleName='item-pin' className='fa fa-thumb-tack' />
: ''
}
{note.title.trim().length > 0
? note.title
: <span styleName='item-simple-title-empty'>{i18n.__('Empty note')}</span>
}
{isAllNotesView && <div styleName='item-simple-right'>
<span styleName='item-simple-right-storageName'>
{storage.name}
</span>
</div>}
{note.type === 'SNIPPET_NOTE' ? (
<i styleName='item-simple-title-icon' className='fa fa-fw fa-code' />
) : (
<i
styleName='item-simple-title-icon'
className='fa fa-fw fa-file-text-o'
/>
)}
{note.isPinned && !pathname.match(/\/starred|\/trash/) ? (
<i styleName='item-pin' className='fa fa-thumb-tack' />
) : (
''
)}
{note.title.trim().length > 0 ? (
note.title
) : (
<span styleName='item-simple-title-empty'>{i18n.__('Empty note')}</span>
)}
{isAllNotesView && (
<div styleName='item-simple-right'>
<span styleName='item-simple-right-storageName'>{storage.name}</span>
</div>
)}
</div>
</div>
)

View File

@@ -6,7 +6,7 @@ const electron = require('electron')
const { shell } = electron
class RealtimeNotification extends React.Component {
constructor (props) {
constructor(props) {
super(props)
this.state = {
@@ -14,38 +14,46 @@ class RealtimeNotification extends React.Component {
}
}
componentDidMount () {
componentDidMount() {
this.fetchNotifications()
}
fetchNotifications () {
const notificationsUrl = 'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
fetchNotifications() {
const notificationsUrl =
'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
fetch(notificationsUrl)
.then(response => {
return response.json()
})
.then(json => {
this.setState({notifications: json.notifications})
this.setState({ notifications: json.notifications })
})
}
handleLinkClick (e) {
handleLinkClick(e) {
shell.openExternal(e.currentTarget.href)
e.preventDefault()
}
render () {
render() {
const { notifications } = this.state
const link = notifications.length > 0
? <a styleName='notification-link' href={notifications[0].linkUrl}
onClick={(e) => this.handleLinkClick(e)}
>
Info: {notifications[0].text}
</a>
: ''
const link =
notifications.length > 0 ? (
<a
styleName='notification-link'
href={notifications[0].linkUrl}
onClick={e => this.handleLinkClick(e)}
>
Info: {notifications[0].text}
</a>
) : (
''
)
return (
<div styleName='notification-area' style={this.props.style}>{link}</div>
<div styleName='notification-area' style={this.props.style}>
{link}
</div>
)
}
}

View File

@@ -16,54 +16,70 @@ import i18n from 'browser/lib/i18n'
* @return {React.Component}
*/
const SideNavFilter = ({
isFolded, isHomeActive, handleAllNotesButtonClick,
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote,
counterTotalNote, counterStarredNote, handleFilterButtonContextMenu
isFolded,
isHomeActive,
handleAllNotesButtonClick,
isStarredActive,
handleStarredButtonClick,
isTrashedActive,
handleTrashedButtonClick,
counterDelNote,
counterTotalNote,
counterStarredNote,
handleFilterButtonContextMenu
}) => (
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
<button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
<button
styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
onClick={handleAllNotesButtonClick}
>
<div styleName='iconWrap'>
<img src={isHomeActive
? '../resources/icon/icon-all-active.svg'
: '../resources/icon/icon-all.svg'
}
<img
src={
isHomeActive
? '../resources/icon/icon-all-active.svg'
: '../resources/icon/icon-all.svg'
}
/>
</div>
<span styleName='menu-button-label'>{i18n.__('All Notes')}</span>
<span styleName='counters'>{counterTotalNote}</span>
</button>
<button styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
<button
styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
onClick={handleStarredButtonClick}
>
<div styleName='iconWrap'>
<img src={isStarredActive
? '../resources/icon/icon-star-active.svg'
: '../resources/icon/icon-star-sidenav.svg'
}
<img
src={
isStarredActive
? '../resources/icon/icon-star-active.svg'
: '../resources/icon/icon-star-sidenav.svg'
}
/>
</div>
<span styleName='menu-button-label'>{i18n.__('Starred')}</span>
<span styleName='counters'>{counterStarredNote}</span>
</button>
<button styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
onClick={handleTrashedButtonClick} onContextMenu={handleFilterButtonContextMenu}
<button
styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
onClick={handleTrashedButtonClick}
onContextMenu={handleFilterButtonContextMenu}
>
<div styleName='iconWrap'>
<img src={isTrashedActive
? '../resources/icon/icon-trash-active.svg'
: '../resources/icon/icon-trash-sidenav.svg'
}
<img
src={
isTrashedActive
? '../resources/icon/icon-trash-active.svg'
: '../resources/icon/icon-trash-sidenav.svg'
}
/>
</div>
<span styleName='menu-button-label'>{i18n.__('Trash')}</span>
<span styleName='counters'>{counterDelNote}</span>
</button>
</div>
)

View File

@@ -5,7 +5,7 @@ import context from 'browser/lib/context'
import i18n from 'browser/lib/i18n'
class SnippetTab extends React.Component {
constructor (props) {
constructor(props) {
super(props)
this.state = {
@@ -14,7 +14,7 @@ class SnippetTab extends React.Component {
}
}
componentWillUpdate (nextProps) {
componentWillUpdate(nextProps) {
if (nextProps.snippet.name !== this.props.snippet.name) {
this.setState({
name: nextProps.snippet.name
@@ -22,34 +22,34 @@ class SnippetTab extends React.Component {
}
}
handleClick (e) {
handleClick(e) {
this.props.onClick(e)
}
handleContextMenu (e) {
handleContextMenu(e) {
context.popup([
{
label: i18n.__('Rename'),
click: (e) => this.handleRenameClick(e)
click: e => this.handleRenameClick(e)
}
])
}
handleRenameClick (e) {
handleRenameClick(e) {
this.startRenaming()
}
handleNameInputBlur (e) {
handleNameInputBlur(e) {
this.handleRename()
}
handleNameInputChange (e) {
handleNameInputChange(e) {
this.setState({
name: e.target.value
})
}
handleNameInputKeyDown (e) {
handleNameInputKeyDown(e) {
switch (e.keyCode) {
case 13:
this.handleRename()
@@ -63,84 +63,87 @@ class SnippetTab extends React.Component {
}
}
handleRename () {
this.setState({
isRenaming: false
}, () => {
if (this.props.snippet.name !== this.state.name) {
this.props.onRename(this.state.name)
handleRename() {
this.setState(
{
isRenaming: false
},
() => {
if (this.props.snippet.name !== this.state.name) {
this.props.onRename(this.state.name)
}
}
})
)
}
handleDeleteButtonClick (e) {
handleDeleteButtonClick(e) {
this.props.onDelete(e)
}
startRenaming () {
this.setState({
isRenaming: true
}, () => {
this.refs.name.focus()
this.refs.name.select()
})
startRenaming() {
this.setState(
{
isRenaming: true
},
() => {
this.refs.name.focus()
this.refs.name.select()
}
)
}
handleDragStart (e) {
handleDragStart(e) {
e.dataTransfer.dropEffect = 'move'
this.props.onDragStart(e)
}
handleDrop (e) {
handleDrop(e) {
this.props.onDrop(e)
}
render () {
render() {
const { isActive, snippet, isDeletable } = this.props
return (
<div styleName={isActive
? 'root--active'
: 'root'
}
>
{!this.state.isRenaming
? <button styleName='button'
onClick={(e) => this.handleClick(e)}
onDoubleClick={(e) => this.handleRenameClick(e)}
onContextMenu={(e) => this.handleContextMenu(e)}
onDragStart={(e) => this.handleDragStart(e)}
onDrop={(e) => this.handleDrop(e)}
<div styleName={isActive ? 'root--active' : 'root'}>
{!this.state.isRenaming ? (
<button
styleName='button'
onClick={e => this.handleClick(e)}
onDoubleClick={e => this.handleRenameClick(e)}
onContextMenu={e => this.handleContextMenu(e)}
onDragStart={e => this.handleDragStart(e)}
onDrop={e => this.handleDrop(e)}
draggable='true'
>
{snippet.name.trim().length > 0
? snippet.name
: <span>
{i18n.__('Unnamed')}
</span>
}
{snippet.name.trim().length > 0 ? (
snippet.name
) : (
<span>{i18n.__('Unnamed')}</span>
)}
</button>
: <input styleName='input'
) : (
<input
styleName='input'
ref='name'
value={this.state.name}
onChange={(e) => this.handleNameInputChange(e)}
onBlur={(e) => this.handleNameInputBlur(e)}
onKeyDown={(e) => this.handleNameInputKeyDown(e)}
onChange={e => this.handleNameInputChange(e)}
onBlur={e => this.handleNameInputBlur(e)}
onKeyDown={e => this.handleNameInputKeyDown(e)}
/>
}
{isDeletable &&
<button styleName='deleteButton'
onClick={(e) => this.handleDeleteButtonClick(e)}
)}
{isDeletable && (
<button
styleName='deleteButton'
onClick={e => this.handleDeleteButtonClick(e)}
>
<i className='fa fa-times' />
</button>
}
)}
</div>
)
}
}
SnippetTab.propTypes = {
}
SnippetTab.propTypes = {}
export default CSSModules(SnippetTab, styles)

View File

@@ -54,8 +54,9 @@ const StorageItem = ({
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
>
{!isFolded &&
<DraggableIcon className={styles['folderList-item-reorder']} />}
{!isFolded && (
<DraggableIcon className={styles['folderList-item-reorder']} />
)}
<span
styleName={
isFolded ? 'folderList-item-name--folded' : 'folderList-item-name'
@@ -70,11 +71,12 @@ const StorageItem = ({
? _.truncate(folderName, { length: 1, omission: '' })
: folderName}
</span>
{!isFolded &&
_.isNumber(noteCount) &&
<span styleName='folderList-item-noteCount'>{noteCount}</span>}
{isFolded &&
<span styleName='folderList-item-tooltip'>{folderName}</span>}
{!isFolded && _.isNumber(noteCount) && (
<span styleName='folderList-item-noteCount'>{noteCount}</span>
)}
{isFolded && (
<span styleName='folderList-item-tooltip'>{folderName}</span>
)}
</button>
)
}

View File

@@ -1,18 +1,20 @@
/**
* @fileoverview Micro component for showing StorageList
*/
* @fileoverview Micro component for showing StorageList
*/
import PropTypes from 'prop-types'
import React from 'react'
import styles from './StorageList.styl'
import CSSModules from 'browser/lib/CSSModules'
/**
* @param {Array} storageList
*/
* @param {Array} storageList
*/
const StorageList = ({storageList, isFolded}) => (
const StorageList = ({ storageList, isFolded }) => (
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
{storageList.length > 0 ? storageList : (
{storageList.length > 0 ? (
storageList
) : (
<div styleName='storageList-empty'>No storage mount.</div>
)}
</div>

View File

@@ -1,30 +1,58 @@
/**
* @fileoverview Micro component for showing TagList.
*/
* @fileoverview Micro component for showing TagList.
*/
import PropTypes from 'prop-types'
import React from 'react'
import styles from './TagListItem.styl'
import CSSModules from 'browser/lib/CSSModules'
/**
* @param {string} name
* @param {Function} handleClickTagListItem
* @param {Function} handleClickNarrowToTag
* @param {boolean} isActive
* @param {boolean} isRelated
* @param {string} bgColor tab backgroundColor
*/
* @param {string} name
* @param {Function} handleClickTagListItem
* @param {Function} handleClickNarrowToTag
* @param {boolean} isActive
* @param {boolean} isRelated
* @param {string} bgColor tab backgroundColor
*/
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count, color}) => (
<div styleName='tagList-itemContainer' onContextMenu={e => handleContextMenu(e, name)}>
{isRelated
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
const TagListItem = ({
name,
handleClickTagListItem,
handleClickNarrowToTag,
handleContextMenu,
isActive,
isRelated,
count,
color
}) => (
<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'} />
</button>
: <div styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} />
}
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
<span styleName='tagList-item-color' style={{backgroundColor: color || 'transparent'}} />
) : (
<div
styleName={
isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'
}
/>
)}
<button
styleName={isActive ? 'tagList-item-active' : 'tagList-item'}
onClick={() => handleClickTagListItem(name)}
>
<span
styleName='tagList-item-color'
style={{ backgroundColor: color || 'transparent' }}
/>
<span styleName='tagList-item-name'>
{`# ${name}`}
<span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span>

View File

@@ -11,17 +11,20 @@ import styles from './TodoListPercentage.styl'
* @param {number} percentageOfTodo
*/
const TodoListPercentage = ({
percentageOfTodo, onClearCheckboxClick
}) => (
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
<div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}>
const TodoListPercentage = ({ percentageOfTodo, onClearCheckboxClick }) => (
<div
styleName='percentageBar'
style={{ display: isNaN(percentageOfTodo) ? 'none' : '' }}
>
<div styleName='progressBar' style={{ width: `${percentageOfTodo}%` }}>
<div styleName='progressBarInner'>
<p styleName='percentageText'>{percentageOfTodo}%</p>
</div>
</div>
<div styleName='todoClear'>
<p styleName='todoClearText' onClick={(e) => onClearCheckboxClick(e)}>clear</p>
<p styleName='todoClearText' onClick={e => onClearCheckboxClick(e)}>
clear
</p>
</div>
</div>
)

View File

@@ -8,18 +8,21 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './TodoProcess.styl'
const TodoProcess = ({
todoStatus: {
total: totalTodo,
completed: completedTodo
}
todoStatus: { total: totalTodo, completed: completedTodo }
}) => (
<div styleName='todo-process' style={{display: totalTodo > 0 ? '' : 'none'}}>
<div
styleName='todo-process'
style={{ display: totalTodo > 0 ? '' : 'none' }}
>
<div styleName='todo-process-text'>
<i className='fa fa-fw fa-check-square-o' />
{completedTodo} of {totalTodo}
</div>
<div styleName='todo-process-bar'>
<div styleName='todo-process-bar--inner' style={{width: parseInt(completedTodo / totalTodo * 100) + '%'}} />
<div
styleName='todo-process-bar--inner'
style={{ width: parseInt((completedTodo / totalTodo) * 100) + '%' }}
/>
</div>
</div>
)

View File

@@ -7,11 +7,11 @@ const darkThemeStyling = `
fill: white;
}`
function getRandomInt (min, max) {
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min
}
function getId () {
function getId() {
const pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
let id = 'm-'
for (let i = 0; i < 7; i++) {
@@ -20,7 +20,7 @@ function getId () {
return id
}
function render (element, content, theme, enableHTMLLabel) {
function render(element, content, theme, enableHTMLLabel) {
try {
const height = element.attributes.getNamedItem('data-height')
const isPredefined = height && height.value !== 'undefined'
@@ -29,7 +29,9 @@ function render (element, content, theme, enableHTMLLabel) {
element.style.height = height.value + 'vh'
}
const isDarkTheme = uiThemes.some(item => item.name === theme && item.isDark)
const isDarkTheme = uiThemes.some(
item => item.name === theme && item.isDark
)
mermaidAPI.initialize({
theme: isDarkTheme ? 'dark' : 'default',
@@ -42,7 +44,7 @@ function render (element, content, theme, enableHTMLLabel) {
}
})
mermaidAPI.render(getId(), content, (svgGraph) => {
mermaidAPI.render(getId(), content, svgGraph => {
element.innerHTML = svgGraph
if (!isPredefined) {