mirror of
https://github.com/marktext/marktext.git
synced 2025-05-03 00:01:19 +08:00
Remember the file's scroll position as long as it stays open
By default returns the user to the same scroll position on a document where they left off (except if changed to code editing before switching back, or vice versa). Only saved in memory could be added to future session saves for #998.
This commit is contained in:
parent
03afc1ec29
commit
b6754741f9
@ -82,6 +82,7 @@ interface IDocumentState
|
||||
index: number
|
||||
},
|
||||
cursor: any,
|
||||
firstViewportVisibleItem: any,
|
||||
wordCount: {
|
||||
paragraph: number,
|
||||
word: number,
|
||||
|
@ -81,6 +81,7 @@ class ContentState {
|
||||
this._selectedImage = null
|
||||
this.dropAnchor = null
|
||||
this.prevCursor = null
|
||||
this.firstViewportVisibleItem = null
|
||||
this.historyTimer = null
|
||||
this.history = new History(this)
|
||||
this.turndownConfig = Object.assign({}, DEFAULT_TURNDOWN_CONFIG, { bulletListMarker })
|
||||
@ -345,6 +346,43 @@ class ContentState {
|
||||
return this.cursor
|
||||
}
|
||||
|
||||
getBlockKeyByIndex (index) {
|
||||
let arr = index.split('.')
|
||||
const travel = blocks => {
|
||||
let pos = arr.shift()
|
||||
let num = parseInt(pos)
|
||||
if (num !== pos || Number.isInteger(num) === false) { return null }
|
||||
pos = num
|
||||
if (blocks.length <= pos) { return null }
|
||||
let block = blocks[pos]
|
||||
if (arr.length === 0) { return block.key } else { return travel(block.children) }
|
||||
}
|
||||
return travel(this.blocks)
|
||||
}
|
||||
|
||||
getBlockIndex (key) {
|
||||
if (!key) return null
|
||||
let result = null
|
||||
const travel = blocks => {
|
||||
for (let index = 0; index < blocks.length; index++) {
|
||||
const block = blocks[index]
|
||||
if (block.key === key) {
|
||||
result = `${index}`
|
||||
return true
|
||||
}
|
||||
if (block.children.length) {
|
||||
if (travel(block.children)) {
|
||||
result = `${index}.${result}`
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
travel(this.blocks)
|
||||
return result
|
||||
}
|
||||
|
||||
getBlock (key) {
|
||||
if (!key) return null
|
||||
let result = null
|
||||
|
@ -111,6 +111,7 @@ import '@/assets/themes/codemirror/one-dark.css'
|
||||
// import 'view-image/lib/imgViewer.css'
|
||||
import CloseIcon from '@/assets/icons/close.svg'
|
||||
|
||||
// Minus 67 here is the Y offset of the container itself, before we didn't include that in the scroll calculations, as we do now this keeps the actual Y offset the same
|
||||
const STANDAR_Y = 320
|
||||
|
||||
export default {
|
||||
@ -501,8 +502,20 @@ export default {
|
||||
},
|
||||
|
||||
currentFile: function (value, oldValue) {
|
||||
if (this.sourceCode === false && oldValue && oldValue !== value) { // Cannot use the changed event above as it happens after we have switched to the new file
|
||||
let firstViewportVisibleItem = this.getFirstElementInViewport()
|
||||
if (firstViewportVisibleItem) { oldValue.firstViewportVisibleItem = 'M' + this.editor.contentState.getBlockIndex(firstViewportVisibleItem.id) } else { oldValue.firstViewportVisibleItem = 'Z' }// undefining if already set
|
||||
}
|
||||
|
||||
if (value && value !== oldValue) {
|
||||
this.scrollToCursor(0)
|
||||
if (this.sourceCode === false && value.firstViewportVisibleItem && value.firstViewportVisibleItem.startsWith('M')) {
|
||||
let indexStr = value.firstViewportVisibleItem.substring(1)
|
||||
this.$nextTick(() => {
|
||||
let elemId = this.editor.contentState.getBlockKeyByIndex(indexStr)
|
||||
if (!elemId) { return }
|
||||
this.scrollToElement('#' + elemId, 15, true)
|
||||
})
|
||||
} else { this.scrollToCursor(0) }
|
||||
// Hide float tools if needed.
|
||||
this.editor && this.editor.hideAllFloatTools()
|
||||
}
|
||||
@ -690,7 +703,6 @@ export default {
|
||||
//
|
||||
// this.setImageViewerVisible(true)
|
||||
// })
|
||||
|
||||
this.editor.on('selectionChange', changes => {
|
||||
const { y } = changes.cursorCoords
|
||||
if (this.typewriter) {
|
||||
@ -1065,14 +1077,51 @@ export default {
|
||||
return this.scrollToElement(`#${slug}`)
|
||||
},
|
||||
|
||||
scrollToElement (selector) {
|
||||
getFirstElementInViewport () {
|
||||
let node = this.editor.container
|
||||
if (node.childNodes.length === 0) { return null }
|
||||
let offsetY = node.scrollTop
|
||||
node = node.childNodes[0]// this gets us to the editors primary div
|
||||
if (offsetY === 0) {
|
||||
if (node.childNodes.length === 0) { return null }
|
||||
return node.childNodes[0]
|
||||
}
|
||||
|
||||
const nodeStack = []
|
||||
|
||||
while (node) {
|
||||
// Only iterate over elements and text nodes
|
||||
if (node.nodeType > 3) {
|
||||
node = nodeStack.pop()
|
||||
continue
|
||||
}
|
||||
if (node.offsetTop >= offsetY) { return node }
|
||||
|
||||
if (node.nodeType === 1) {
|
||||
// this is an element
|
||||
// add all its children to the stack
|
||||
let i = node.childNodes.length - 1
|
||||
while (i >= 0) {
|
||||
nodeStack.push(node.childNodes[i])
|
||||
i -= 1
|
||||
}
|
||||
}
|
||||
|
||||
node = nodeStack.pop()
|
||||
}
|
||||
},
|
||||
|
||||
scrollToElement (selector, duration = 300, dontAddStandardHeadroom = false) {
|
||||
// Scroll to search highlight word
|
||||
const { container } = this.editor
|
||||
const anchor = document.querySelector(selector)
|
||||
if (anchor) {
|
||||
const { y } = anchor.getBoundingClientRect()
|
||||
const DURATION = 300
|
||||
animatedScrollTo(container, container.scrollTop + y - STANDAR_Y, DURATION)
|
||||
const DURATION = duration
|
||||
const anchorY = anchor.getBoundingClientRect().y
|
||||
const containerY = container.getBoundingClientRect().y
|
||||
|
||||
const add = dontAddStandardHeadroom ? 0 : STANDAR_Y
|
||||
animatedScrollTo(container, container.scrollTop + anchorY - containerY - add, DURATION)
|
||||
}
|
||||
},
|
||||
|
||||
@ -1229,7 +1278,7 @@ export default {
|
||||
if (cursor) {
|
||||
editor.setMarkdown(markdown, cursor, true)
|
||||
} else {
|
||||
editor.setMarkdown(markdown)
|
||||
editor.setMarkdown(markdown, null, true)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -10,10 +10,13 @@
|
||||
import codeMirror, { setMode, setCursorAtLastLine, setTextDirection } from '../../codeMirror'
|
||||
import { wordCount as getWordCount } from 'muya/lib/utils'
|
||||
import { mapState } from 'vuex'
|
||||
import { adjustCursor } from '../../util'
|
||||
import { adjustCursor, animatedScrollTo } from '../../util'
|
||||
import bus from '../../bus'
|
||||
import { oneDarkThemes, railscastsThemes } from '@/config'
|
||||
|
||||
// Same as editor.vue
|
||||
const STANDAR_Y = 320
|
||||
|
||||
export default {
|
||||
props: {
|
||||
markdown: String,
|
||||
@ -43,6 +46,18 @@ export default {
|
||||
},
|
||||
|
||||
watch: {
|
||||
currentTab: function (value, oldValue) {
|
||||
if (!this.sourceCode) { return }
|
||||
// Don't need to worry about setting the line on the oldValue already did in prepareTabSwitch
|
||||
if (value && value !== oldValue) {
|
||||
if (value.firstViewportVisibleItem && value.firstViewportVisibleItem.startsWith('S')) {
|
||||
let line = value.firstViewportVisibleItem.substring(1)
|
||||
this.$nextTick(() => {
|
||||
this.scrollToLineNumberInViewport(line, 15, true)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
textDirection: function (value, oldValue) {
|
||||
const { editor } = this
|
||||
if (value !== oldValue && editor) {
|
||||
@ -237,15 +252,28 @@ export default {
|
||||
},
|
||||
// Commit changes from old tab. Problem: tab was already switched, so commit changes with old tab id.
|
||||
prepareTabSwitch () {
|
||||
let firstViewportVisibleItem = 'S' + this.getFirstLineNumberInViewport()
|
||||
if (this.sourceCode === false) { // this shouldn't happen
|
||||
firstViewportVisibleItem = undefined
|
||||
}
|
||||
|
||||
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.$store.dispatch('LISTEN_FOR_CONTENT_CHANGE', { id: this.tabId, markdown, cursor, firstViewportVisibleItem })
|
||||
this.tabId = null // invalidate tab id
|
||||
}
|
||||
},
|
||||
|
||||
scrollToLineNumberInViewport (line, duration = 300, dontAddStandardHeadroom = false) {
|
||||
let pos = this.editor.charCoords({ line: line, ch: 0 }, 'local').top
|
||||
const DURATION = duration
|
||||
if (!dontAddStandardHeadroom) { pos -= STANDAR_Y }
|
||||
animatedScrollTo(this.$el, pos, DURATION)
|
||||
},
|
||||
getFirstLineNumberInViewport () {
|
||||
return this.editor.coordsChar({ left: 0, top: this.$el.scrollTop }, 'local').line
|
||||
},
|
||||
handleSelectAll () {
|
||||
if (!this.sourceCode) {
|
||||
return
|
||||
|
389
src/renderer/components/editorWithTabs/sourceCode.vue.orig
Normal file
389
src/renderer/components/editorWithTabs/sourceCode.vue.orig
Normal file
@ -0,0 +1,389 @@
|
||||
<template>
|
||||
<div
|
||||
class="source-code"
|
||||
ref="sourceCode"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import codeMirror, { setMode, setCursorAtLastLine, setTextDirection } from '../../codeMirror'
|
||||
import { wordCount as getWordCount } from 'muya/lib/utils'
|
||||
import { mapState } from 'vuex'
|
||||
import { adjustCursor, animatedScrollTo } from '../../util'
|
||||
import bus from '../../bus'
|
||||
import { oneDarkThemes, railscastsThemes } from '@/config'
|
||||
|
||||
const STANDAR_Y = 320
|
||||
|
||||
export default {
|
||||
props: {
|
||||
markdown: String,
|
||||
cursor: Object,
|
||||
textDirection: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
theme: state => state.preferences.theme,
|
||||
sourceCode: state => state.preferences.sourceCode,
|
||||
currentTab: state => state.editor.currentFile
|
||||
})
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
contentState: null,
|
||||
editor: null,
|
||||
commitTimer: null,
|
||||
viewDestroyed: false,
|
||||
tabId: null
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
currentTab: function (value, oldValue) {
|
||||
if (! this.sourceCode)
|
||||
return;
|
||||
//Don't need to worry about setting the line on the oldValue already did in prepareTabSwitch
|
||||
if (value && value !== oldValue) {
|
||||
if (value.firstViewportVisibleItem && value.firstViewportVisibleItem.startsWith("S")){
|
||||
let line = value.firstViewportVisibleItem.substring(1);
|
||||
this.$nextTick(() => {
|
||||
this.scrollToLineNumberInViewport(line, 15,true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
textDirection: function (value, oldValue) {
|
||||
const { editor } = this
|
||||
if (value !== oldValue && editor) {
|
||||
setTextDirection(editor, value)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
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 = {
|
||||
value: markdown,
|
||||
lineNumbers: true,
|
||||
autofocus: true,
|
||||
lineWrapping: true,
|
||||
styleActiveLine: true,
|
||||
direction: textDirection,
|
||||
// The amount of updates needed when scrolling. Settings this to >Infinity< or use CSS
|
||||
// >height: auto< result in bad performance because the whole document is always rendered.
|
||||
// Since we are using >height: auto< setting this to >Infinity< to fix #171. The best
|
||||
// solution would be to set a fixed height like in #791 but then the scrollbar is not on
|
||||
// the right side. Please also see CodeMirror#1104.
|
||||
viewportMargin: Infinity,
|
||||
lineNumberFormatter (line) {
|
||||
if (line % 10 === 0 || line === 1) {
|
||||
return line
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set theme
|
||||
if (railscastsThemes.includes(theme)) {
|
||||
codeMirrorConfig.theme = 'railscasts'
|
||||
} else if (oneDarkThemes.includes(theme)) {
|
||||
codeMirrorConfig.theme = 'one-dark'
|
||||
}
|
||||
|
||||
// Init CodeMirror
|
||||
const editor = this.editor = codeMirror(container, codeMirrorConfig)
|
||||
|
||||
bus.$on('file-loaded', this.handleFileChange)
|
||||
bus.$on('invalidate-image-cache', this.handleInvalidateImageCache)
|
||||
bus.$on('file-changed', this.handleFileChange)
|
||||
bus.$on('selectAll', this.handleSelectAll)
|
||||
bus.$on('image-action', this.handleImageAction)
|
||||
bus.$on('undo', this.handleUndo)
|
||||
bus.$on('redo', this.handleRedo)
|
||||
bus.$on('find', this.handleFind)
|
||||
bus.$on('replace', this.handleReplace)
|
||||
bus.$on('findNext', this.handleFindNext)
|
||||
bus.$on('findPrev', this.handleFindPrev)
|
||||
document.addEventListener('click', this.docClick)
|
||||
document.addEventListener('keyup', this.docKeyup)
|
||||
|
||||
setMode(editor, 'markdown')
|
||||
this.listenChange()
|
||||
|
||||
// NOTE: Cursor may be not null but the inner values are.
|
||||
if (cursor && cursor.anchor && cursor.focus) {
|
||||
const { anchor, focus } = cursor
|
||||
editor.setSelection(anchor, focus, { scroll: true }) // Scroll the focus into view.
|
||||
} 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.handleFileChange)
|
||||
bus.$off('invalidate-image-cache', this.handleInvalidateImageCache)
|
||||
bus.$off('file-changed', this.handleFileChange)
|
||||
bus.$off('selectAll', this.handleSelectAll)
|
||||
bus.$off('image-action', this.handleImageAction)
|
||||
bus.$off('undo', this.handleUndo)
|
||||
bus.$off('redo', this.handleRedo)
|
||||
bus.$off('find', this.handleFind)
|
||||
bus.$off('replace', this.handleReplace)
|
||||
bus.$off('findNext', this.handleFindNext)
|
||||
bus.$off('findPrev', this.handleFindPrev)
|
||||
document.removeEventListener('click', this.docClick)
|
||||
document.removeEventListener('keyup', this.docKeyup)
|
||||
|
||||
const { editor } = this
|
||||
const { cursor, markdown } = this.getMarkdownAndCursor(editor)
|
||||
bus.$emit('file-changed', { id: this.tabId, markdown, cursor, renderCursor: true })
|
||||
},
|
||||
methods: {
|
||||
handleImageAction ({ id, result, alt }) {
|
||||
const { editor } = this
|
||||
const value = editor.getValue()
|
||||
const focus = editor.getCursor('focus')
|
||||
const anchor = editor.getCursor('anchor')
|
||||
const lines = value.split('\n')
|
||||
const index = lines.findIndex(line => line.indexOf(id) > 0)
|
||||
|
||||
if (index > -1) {
|
||||
const oldLine = lines[index]
|
||||
lines[index] = oldLine.replace(new RegExp(`!\\[${id}\\]\\(.*\\)`), ``)
|
||||
const newValue = lines.join('\n')
|
||||
editor.setValue(newValue)
|
||||
const match = /(!\[.*\]\(.*\))/.exec(oldLine)
|
||||
if (!match) {
|
||||
// User maybe delete `![]()` structure, and the match is null.
|
||||
return
|
||||
}
|
||||
const range = {
|
||||
start: match.index,
|
||||
end: match.index + match[1].length
|
||||
}
|
||||
const delta = alt.length + result.length + 5 - match[1].length
|
||||
|
||||
const adjust = pointer => {
|
||||
if (!pointer) {
|
||||
return
|
||||
}
|
||||
if (pointer.line !== index) {
|
||||
return
|
||||
}
|
||||
if (pointer.ch <= range.start) {
|
||||
// do nothing.
|
||||
} else if (pointer.ch > range.start && pointer.ch < range.end) {
|
||||
pointer.ch = range.start + alt.length + result.length + 5
|
||||
} else {
|
||||
pointer.ch += delta
|
||||
}
|
||||
}
|
||||
|
||||
adjust(focus)
|
||||
adjust(anchor)
|
||||
if (focus && anchor) {
|
||||
editor.setSelection(anchor, focus, { scroll: true })
|
||||
} else {
|
||||
setCursorAtLastLine()
|
||||
}
|
||||
}
|
||||
},
|
||||
listenChange () {
|
||||
const { editor } = this
|
||||
editor.on('cursorActivity', cm => {
|
||||
const { cursor, markdown } = this.getMarkdownAndCursor(cm)
|
||||
// Attention: the cursor may be `{focus: null, anchor: null}` when press `backspace`
|
||||
const wordCount = getWordCount(markdown)
|
||||
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)
|
||||
})
|
||||
},
|
||||
// Another tab was selected - only listen to get changes but don't set history or other things.
|
||||
handleFileChange ({ id, markdown, cursor }) {
|
||||
this.prepareTabSwitch()
|
||||
|
||||
const { editor } = this
|
||||
if (typeof markdown === 'string') {
|
||||
editor.setValue(markdown)
|
||||
}
|
||||
// Cursor is null when loading a file or creating a new tab in source code mode.
|
||||
if (cursor) {
|
||||
const { anchor, focus } = cursor
|
||||
editor.setSelection(anchor, focus, { scroll: true }) // Scroll the focus into view.
|
||||
} else {
|
||||
setCursorAtLastLine(editor)
|
||||
}
|
||||
this.tabId = id
|
||||
},
|
||||
// Get markdown and cursor from CodeMirror.
|
||||
getMarkdownAndCursor (cm) {
|
||||
let focus = cm.getCursor('head')
|
||||
let anchor = cm.getCursor('anchor')
|
||||
const markdown = cm.getValue()
|
||||
const convertToMuyaCursor = cursor => {
|
||||
const line = cm.getLine(cursor.line)
|
||||
const preLine = cm.getLine(cursor.line - 1)
|
||||
const nextLine = cm.getLine(cursor.line + 1)
|
||||
return adjustCursor(cursor, preLine, line, nextLine)
|
||||
}
|
||||
|
||||
anchor = convertToMuyaCursor(anchor) // Selection start as Muya cursor
|
||||
focus = convertToMuyaCursor(focus) // Selection end as Muya cursor
|
||||
|
||||
// Normalize cursor that `anchor` is always before `focus` because
|
||||
// this is the expected behavior in Muya.
|
||||
if (anchor && focus && anchor.line > focus.line) {
|
||||
const tmpCursor = focus
|
||||
focus = anchor
|
||||
anchor = tmpCursor
|
||||
}
|
||||
return { cursor: { focus, anchor }, markdown }
|
||||
},
|
||||
// Commit changes from old tab. Problem: tab was already switched, so commit changes with old tab id.
|
||||
prepareTabSwitch () {
|
||||
|
||||
let firstViewportVisibleItem = "S" + this.getFirstLineNumberInViewport();
|
||||
if (this.sourceCode == false)//this shouldn't happen
|
||||
firstViewportVisibleItem = undefined;
|
||||
|
||||
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, firstViewportVisibleItem })
|
||||
this.tabId = null // invalidate tab id
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
scrollToLineNumberInViewport(line, duration=300, dontAddStandardHeadroom=false){
|
||||
let pos = this.editor.charCoords({line:line,ch:0},"local").top;
|
||||
const DURATION = duration;
|
||||
if (! dontAddStandardHeadroom)
|
||||
pos -= STANDAR_Y;
|
||||
animatedScrollTo(this.$el, pos, DURATION)
|
||||
},
|
||||
getFirstLineNumberInViewport(){
|
||||
return this.editor.coordsChar({left:0,top:this.$el.scrollTop},"local").line;
|
||||
},
|
||||
=======
|
||||
handleUndo () {
|
||||
if (this.editor && this.sourceCode) {
|
||||
this.editor.execCommand('undo')
|
||||
}
|
||||
},
|
||||
|
||||
handleRedo () {
|
||||
if (this.editor && this.sourceCode) {
|
||||
this.editor.execCommand('redo')
|
||||
}
|
||||
},
|
||||
docKeyup (event) {
|
||||
if (this.editor && this.sourceCode && event.key === 'Escape') {
|
||||
this.editor.execCommand('clearSearch')
|
||||
}
|
||||
},
|
||||
|
||||
docClick () {
|
||||
if (this.editor && this.sourceCode)
|
||||
this.editor.execCommand('clearSearch')
|
||||
},
|
||||
handleFind () {
|
||||
if (this.editor && this.sourceCode) {
|
||||
this.editor.execCommand('findPersistent')
|
||||
}
|
||||
},
|
||||
|
||||
handleReplace () {
|
||||
if (this.editor && this.sourceCode) {
|
||||
this.editor.execCommand('replace')
|
||||
}
|
||||
},
|
||||
|
||||
handleFindNext () {
|
||||
if (this.editor && this.sourceCode) {
|
||||
this.editor.execCommand('findNext')
|
||||
}
|
||||
},
|
||||
|
||||
handleFindPrev () {
|
||||
if (this.editor && this.sourceCode) {
|
||||
this.editor.execCommand('findPrev')
|
||||
}
|
||||
},
|
||||
|
||||
>>>>>>> sourcecode_editor_find_undo_redo_fixes
|
||||
handleSelectAll () {
|
||||
if (!this.sourceCode) {
|
||||
return
|
||||
}
|
||||
|
||||
const { editor } = this
|
||||
if (editor && editor.hasFocus()) {
|
||||
this.editor.execCommand('selectAll')
|
||||
} else {
|
||||
const activeElement = document.activeElement
|
||||
const nodeName = activeElement.nodeName
|
||||
if (nodeName === 'INPUT' || nodeName === 'TEXTAREA') {
|
||||
activeElement.select()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleInvalidateImageCache () {
|
||||
if (this.editor) {
|
||||
this.editor.invalidateImageCache()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.source-code {
|
||||
height: calc(100vh - var(--titleBarHeight));
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
}
|
||||
.source-code .CodeMirror {
|
||||
height: auto;
|
||||
margin: 50px auto;
|
||||
max-width: var(--editorAreaWidth);
|
||||
background: transparent;
|
||||
}
|
||||
.source-code .CodeMirror-gutters {
|
||||
border-right: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
.source-code .CodeMirror-activeline-background,
|
||||
.source-code .CodeMirror-activeline-gutter {
|
||||
background: var(--floatHoverColor);
|
||||
}
|
||||
</style>
|
@ -247,6 +247,11 @@ const mutations = {
|
||||
state.currentFile.history = history
|
||||
}
|
||||
},
|
||||
SET_FIRST_VIS_ITEM (state, firstViewportVisibleItem) {
|
||||
if (hasKeys(state.currentFile)) {
|
||||
state.currentFile.firstViewportVisibleItem = firstViewportVisibleItem
|
||||
}
|
||||
},
|
||||
CLOSE_TABS (state, tabIdList) {
|
||||
if (!tabIdList || tabIdList.length === 0) return
|
||||
|
||||
@ -922,7 +927,7 @@ const actions = {
|
||||
|
||||
// Content change from realtime preview editor and source code editor
|
||||
// WORKAROUND: id is "muya" if changes come from muya and not source code editor! So we don't have to apply the workaround.
|
||||
LISTEN_FOR_CONTENT_CHANGE ({ commit, dispatch, state, rootState }, { id, markdown, wordCount, cursor, history, toc }) {
|
||||
LISTEN_FOR_CONTENT_CHANGE ({ commit, dispatch, state, rootState }, { id, markdown, wordCount, cursor, history, toc, firstViewportVisibleItem }) {
|
||||
const { autoSave } = rootState.preferences
|
||||
const {
|
||||
id: currentId,
|
||||
@ -952,6 +957,10 @@ const actions = {
|
||||
if (history) {
|
||||
tab.history = history
|
||||
}
|
||||
// Set Line or First Visible Item Index
|
||||
if (firstViewportVisibleItem) {
|
||||
tab.firstViewportVisibleItem = firstViewportVisibleItem
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -978,6 +987,9 @@ const actions = {
|
||||
if (history) {
|
||||
commit('SET_HISTORY', history)
|
||||
}
|
||||
if (firstViewportVisibleItem) {
|
||||
commit('SET_FIRST_VIS_ITEM', firstViewportVisibleItem)
|
||||
}
|
||||
// Set toc
|
||||
if (toc && !equal(toc, listToc)) {
|
||||
commit('SET_TOC', toc)
|
||||
|
Loading…
Reference in New Issue
Block a user