diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 7f6b4bbd..c36a50c1 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -292,6 +292,10 @@ export default class CodeEditor extends React.Component { this.editor.on('cursorActivity', this.editorActivityHandler) this.editor.on('changes', this.editorActivityHandler) } + + this.setState({ + clientWidth: this.refs.root.clientWidth + }) } expandSnippet (line, cursor, cm, snippets) { @@ -441,6 +445,14 @@ export default class CodeEditor extends React.Component { 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() } @@ -604,7 +616,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/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 5ddc7598..eacc4e21 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -18,8 +18,11 @@ import mdurl from 'mdurl' import exportNote from 'browser/main/lib/dataApi/exportNote' import { escapeHtmlCharacters } from 'browser/lib/utils' import yaml from 'js-yaml' +import context from 'browser/lib/context' +import i18n from 'browser/lib/i18n' +import fs from 'fs' -const { remote } = require('electron') +const { remote, shell } = require('electron') const attachmentManagement = require('../main/lib/dataApi/attachmentManagement') const { app } = remote @@ -28,6 +31,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() @@ -162,7 +167,6 @@ const scrollBarDarkStyle = ` } ` -const { shell } = require('electron') const OSX = global.process.platform === 'darwin' const defaultFontFamily = ['helvetica', 'arial', 'sans-serif'] @@ -220,8 +224,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) { @@ -705,7 +733,6 @@ export default class MarkdownPreview extends React.Component { el.addEventListener('click', this.linkClickHandler) }) } catch (e) { - console.error(e) el.className = 'flowchart-error' el.innerHTML = 'Flowchart parse error: ' + e.message } @@ -726,7 +753,6 @@ export default class MarkdownPreview extends React.Component { el.addEventListener('click', this.linkClickHandler) }) } catch (e) { - console.error(e) el.className = 'sequence-error' el.innerHTML = 'Sequence diagram parse error: ' + e.message } @@ -752,7 +778,6 @@ export default class MarkdownPreview extends React.Component { const chart = new Chart(canvas, chartConfig) } catch (e) { - console.error(e) el.className = 'chart-error' el.innerHTML = 'chartjs diagram parse error: ' + e.message } 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) && -
+ return `${token.fileName}` }, flowchart: token => { - return `${token.content}+ return `${token.fileName}` }, mermaid: token => { - return `${token.content}+ return `${token.fileName}` }, sequence: token => { - return `${token.content}+ return `${token.fileName}` } }, token => { - return `${token.content}+ return `${token.fileName} ${createGutter(token.content, token.firstLineNumber)}${token.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/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 82073162..e4493a80 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,6 +61,7 @@ class MarkdownNoteDetail extends React.Component { const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT' this.handleSwitchMode(reversedType) }) + ee.on('code:generate-toc', this.generateToc) } componentWillReceiveProps (nextProps) { @@ -75,6 +78,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() } @@ -262,6 +266,11 @@ class MarkdownNoteDetail extends React.Component { } } + handleGenerateToc () { + const editor = this.refs.content.refs.code.editor + markdownToc.generateInEditor(editor) + } + handleFocus (e) { this.focus() } @@ -363,6 +372,7 @@ class MarkdownNoteDetail extends React.Component {diff --git a/browser/main/Detail/NoteDetailInfo.styl b/browser/main/Detail/NoteDetailInfo.styl index 8d454203..7166a497 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 diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 3564d6bf..9356a02c 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) { @@ -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() } @@ -746,6 +759,7 @@ class SnippetNoteDetail extends React.Component { this.handleChange(e)} />