diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 089e183a..6a26c06f 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -41,6 +41,7 @@ foo
bar
zar - Add new themes: Ulysses Light, Graphite Light, Material Dark and One Dark. - Watch file changed in tabs and show a notice(autoSave is `false`) or update the file(autoSave is `true`) - Support input inline Ruby charactors as raw html (#257) +- Added unsaved tab indicator **:butterfly:Optimization** @@ -109,6 +110,8 @@ foo
bar
zar - Fixed bug when combine pre list and next list into one when inline update #707 - Fix renderer error when selection in sidebar (#625) - Fixed list parse error [more info](https://github.com/marktext/marktext/issues/831#issuecomment-477719256) +- Fixed source code mode tab switching +- Fixed source code mode to preview switching ### 0.13.65 diff --git a/src/main/window.js b/src/main/window.js index b7d4ad3c..f8719389 100644 --- a/src/main/window.js +++ b/src/main/window.js @@ -218,6 +218,7 @@ class AppWindow { newTab (win, filePath) { this.watcher.watch(win, filePath, 'file') loadMarkdownFile(filePath).then(rawDocument => { + appMenu.addRecentlyUsedDocument(filePath) newTab(win, rawDocument) }).catch(err => { // TODO: Handle error --> create a end-user error handler. diff --git a/src/muya/lib/ui/emojis/index.js b/src/muya/lib/ui/emojis/index.js index 64a9f847..40c4e70b 100644 --- a/src/muya/lib/ui/emojis/index.js +++ b/src/muya/lib/ui/emojis/index.js @@ -28,7 +28,7 @@ export const validEmoji = text => { */ export const checkEditEmoji = node => { - if (node.classList.contains(CLASS_OR_ID['AG_EMOJI_MARKED_TEXT'])) { + if (node && node.classList.contains(CLASS_OR_ID['AG_EMOJI_MARKED_TEXT'])) { return node } return false diff --git a/src/renderer/assets/themes/one-dark.theme.css b/src/renderer/assets/themes/one-dark.theme.css index 6db787f6..8b373a41 100644 --- a/src/renderer/assets/themes/one-dark.theme.css +++ b/src/renderer/assets/themes/one-dark.theme.css @@ -59,6 +59,10 @@ background: #4d78cc !important; } +.drop-container.active { + border: 1px dashed #4d78cc !important; +} + .title-bar .frameless-titlebar-button > div > svg { fill: #ffffff; } @@ -84,6 +88,8 @@ .side-bar-toc .no-data svg { fill: #4d78cc !important; } + +.recent-files-projects a, .open-project a { color: #9da5b4 !important; border: 1px solid #181a1f !important; @@ -91,6 +97,11 @@ background-image: linear-gradient(#3a3f4b, #353b45) !important; box-shadow: none !important; } +.recent-files-projects a:hover, +.open-project a:hover { + color: #d7dae0 !important; + background-image: linear-gradient(#3e4451, #3a3f4b) !important; +} .editor-tabs { border-bottom: 1px solid #181a1f; @@ -111,6 +122,9 @@ height: auto !important; background: #4d78cc !important; } +.tabs-container svg.close-icon #unsaved-circle-icon { + fill: #4d78cc; +} :not(pre) > code[class*="language-"], pre.ag-paragraph { diff --git a/src/renderer/components/editorWithTabs/editor.vue b/src/renderer/components/editorWithTabs/editor.vue index 2b13898f..c987b2c5 100644 --- a/src/renderer/components/editorWithTabs/editor.vue +++ b/src/renderer/components/editorWithTabs/editor.vue @@ -261,7 +261,8 @@ }) this.editor.on('change', changes => { - this.$store.dispatch('LISTEN_FOR_CONTENT_CHANGE', changes) + // WORKAROUND: "id: 'muya'" + this.$store.dispatch('LISTEN_FOR_CONTENT_CHANGE', Object.assign(changes, { id: 'muya' })) }) this.editor.on('format-click', ({ event, formatType, data }) => { @@ -445,7 +446,7 @@ }, // listen for `open-single-file` event, it will call this method only when open a new file. - setMarkdownToEditor (markdown) { + setMarkdownToEditor ({id, markdown}) { const { editor } = this if (editor) { editor.clearHistory() @@ -455,7 +456,7 @@ }, // listen for markdown change form source mode or change tabs etc - handleMarkdownChange ({ markdown, cursor, renderCursor, history }) { + handleMarkdownChange ({ id, markdown, cursor, renderCursor, history }) { const { editor } = this if (editor) { if (history) { @@ -488,12 +489,15 @@ bus.$off('undo', this.handleUndo) bus.$off('redo', this.handleRedo) bus.$off('export', this.handleExport) + bus.$off('print-service-clearup', this.handlePrintServiceClearup) bus.$off('paragraph', this.handleEditParagraph) bus.$off('format', this.handleInlineFormat) bus.$off('searchValue', this.handleSearch) bus.$off('replaceValue', this.handReplace) bus.$off('find', this.handleFind) - bus.$off('dotu-select', this.handleSelect) + bus.$off('insert-image', this.handleSelect) + bus.$off('image-uploaded', this.handleUploadedImage) + bus.$off('file-changed', this.handleMarkdownChange) bus.$off('editor-blur', this.blurEditor) bus.$off('image-auto-path', this.handleImagePath) bus.$off('copyAsMarkdown', this.handleCopyPaste) diff --git a/src/renderer/components/editorWithTabs/sourceCode.vue b/src/renderer/components/editorWithTabs/sourceCode.vue index 62f952bc..f44e7f0b 100644 --- a/src/renderer/components/editorWithTabs/sourceCode.vue +++ b/src/renderer/components/editorWithTabs/sourceCode.vue @@ -26,14 +26,18 @@ computed: { ...mapState({ - 'theme': state => state.preferences.theme + 'theme': state => state.preferences.theme, + 'currentTab': state => state.editor.currentFile, }) }, data () { return { contentState: null, - editor: null + editor: null, + commitTimer: null, + viewDestroyed: false, + tabId: null } }, @@ -48,6 +52,8 @@ created () { this.$nextTick(() => { + // TODO: Should we load markdown from the tab or mapped vue property? + const { id } = this.currentTab const { markdown = '', theme, cursor, textDirection } = this const container = this.$refs.sourceCode const codeMirrorConfig = { @@ -71,6 +77,7 @@ codeMirrorConfig.theme = 'one-dark' } const editor = this.editor = codeMirror(container, codeMirrorConfig) + bus.$on('file-loaded', this.setMarkdown) bus.$on('file-changed', this.handleMarkdownChange) bus.$on('dotu-select', this.handleSelectDoutu) @@ -82,13 +89,22 @@ } else { setCursorAtLastLine(editor) } + this.tabId = id }) }, beforeDestroy () { + // NOTE: Clear timer and manually commit changes. After mode switching and cleanup may follow + // further key inputs, so ignore all inputs. + this.viewDestroyed = true + if (this.commitTimer) clearTimeout(this.commitTimer) + bus.$off('file-loaded', this.setMarkdown) + bus.$off('file-changed', this.handleMarkdownChange) bus.$off('dotu-select', this.handleSelectDoutu) - const { markdown, cursor } = this - bus.$emit('file-changed', { markdown, cursor, renderCursor: true }) + + const { editor } = this + const { cursor, markdown } = this.getMarkdownAndCursor(editor) + bus.$emit('file-changed', { id: this.tabId, markdown, cursor, renderCursor: true }) }, methods: { handleSelectDoutu (url) { @@ -99,29 +115,37 @@ }, listenChange () { const { editor } = this - let timer = null - editor.on('cursorActivity', (cm, event) => { - let cursor = cm.getCursor() - const markdown = cm.getValue() + editor.on('cursorActivity', cm => { + const { cursor, markdown } = this.getMarkdownAndCursor(cm) const wordCount = getWordCount(markdown) - const line = cm.getLine(cursor.line) - const preLine = cm.getLine(cursor.line - 1) - const nextLine = cm.getLine(cursor.line + 1) - cursor = adjustCursor(cursor, preLine, line, nextLine) - if (timer) clearTimeout(timer) - timer = setTimeout(() => { - this.$store.dispatch('LISTEN_FOR_CONTENT_CHANGE', { markdown, wordCount, cursor }) + if (this.commitTimer) clearTimeout(this.commitTimer) + this.commitTimer = setTimeout(() => { + // See "beforeDestroy" note + if (!this.viewDestroyed) { + if (this.tabId) { + this.$store.dispatch('LISTEN_FOR_CONTENT_CHANGE', { id: this.tabId, markdown, wordCount, cursor }) + } else { + // This may occur during tab switching but should not occur otherwise. + console.warn(`LISTEN_FOR_CONTENT_CHANGE: Cannot commit changes because not tab id was set!`) + } + } }, 1000) }) }, - setMarkdown (markdown) { + // A new file was opened or new tab was added. + setMarkdown ({ id, markdown }) { + this.prepareTabSwitch() + const { editor } = this editor.setValue(markdown) - // // NOTE: Don't set the cursor because we load a new file - no tab switch. + // NOTE: Don't set the cursor because we load a new file. setCursorAtLastLine(editor) + this.tabId = id }, - // Only listen to get changes. Do not set history or other things. - handleMarkdownChange({ markdown, cursor, renderCursor, history }) { + // Another tab was selected - only listen to get changes but don't set history or other things. + handleMarkdownChange ({ id, markdown, cursor, renderCursor, history }) { + this.prepareTabSwitch() + const { editor } = this editor.setValue(markdown) // Cursor is null when loading a file or creating a new tab in source code mode. @@ -130,6 +154,27 @@ } else { setCursorAtLastLine(editor) } + this.tabId = id + }, + // Get markdown and cursor from CodeMirror. + getMarkdownAndCursor (cm) { + let cursor = cm.getCursor() + const markdown = cm.getValue() + const line = cm.getLine(cursor.line) + const preLine = cm.getLine(cursor.line - 1) + const nextLine = cm.getLine(cursor.line + 1) + cursor = adjustCursor(cursor, preLine, line, nextLine) + return { cursor, markdown } + }, + // Commit changes from old tab. Problem: tab was already switched, so commit changes with old tab id. + prepareTabSwitch () { + if (this.commitTimer) clearTimeout(this.commitTimer) + if (this.tabId) { + const { editor } = this + const { cursor, markdown } = this.getMarkdownAndCursor(editor) + this.$store.dispatch('LISTEN_FOR_CONTENT_CHANGE', { id: this.tabId, markdown, cursor }) + this.tabId = null // invalidate tab id + } } } } diff --git a/src/renderer/components/editorWithTabs/tabs.vue b/src/renderer/components/editorWithTabs/tabs.vue index ed3798ce..1ecee136 100644 --- a/src/renderer/components/editorWithTabs/tabs.vue +++ b/src/renderer/components/editorWithTabs/tabs.vue @@ -11,10 +11,11 @@ @click.stop="selectFile(file)" > {{ file.filename }} -
  • @@ -49,6 +50,9 @@