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 @@ foozar
- 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 @@ foozar
- 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 @@