[File UI] Added support for custom line positioning in file editor.
- Adds the ability to select lines from the editor, which in turn will highlight them. - Adds the ability to load a file and scroll at a specific line if the URL has with the `line` argument. - Adds the ability to maximize the file editor modal.
This commit is contained in:
parent
cc621cdca6
commit
4e5c740908
2 changed files with 160 additions and 26 deletions
|
@ -9,10 +9,15 @@
|
|||
|
||||
<div class="editor-body">
|
||||
<div class="line-numbers" ref="lineNumbers">
|
||||
<span class="line-number" v-for="n in lines" :key="n" v-text="n" />
|
||||
<span class="line-number"
|
||||
:class="{selected: selectedLine === n}"
|
||||
v-for="n in lines"
|
||||
:key="n"
|
||||
@click="selectedLine = selectedLine === n ? null : n"
|
||||
v-text="n" />
|
||||
</div>
|
||||
|
||||
<pre ref="pre"><code ref="content" v-html="displayedContent" /></pre>
|
||||
<pre ref="pre"><code ref="content" v-html="displayedContent" /><div class="selected-line" ref="selectedLine" v-if="selectedLine != null" /></pre>
|
||||
<textarea ref="textarea" v-model="content" @scroll="syncScroll" @input.stop />
|
||||
</div>
|
||||
|
||||
|
@ -54,6 +59,11 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
|
||||
line: {
|
||||
type: [String, Number],
|
||||
default: null,
|
||||
},
|
||||
|
||||
withSave: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
|
@ -71,6 +81,7 @@ export default {
|
|||
initialContentHash: 0,
|
||||
loading: false,
|
||||
saving: false,
|
||||
selectedLine: null,
|
||||
type: null,
|
||||
}
|
||||
},
|
||||
|
@ -139,6 +150,12 @@ export default {
|
|||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
if (this.selectedLine) {
|
||||
setTimeout(() => {
|
||||
this.scrollToLine(this.selectedLine)
|
||||
}, 1000)
|
||||
}
|
||||
},
|
||||
|
||||
async saveFile() {
|
||||
|
@ -190,6 +207,17 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
scrollToLine(line) {
|
||||
const offset = (line - 1) * parseFloat(getComputedStyle(this.$refs.pre).lineHeight)
|
||||
this.$refs.textarea.scrollTo({
|
||||
top: offset,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
|
||||
return offset
|
||||
},
|
||||
|
||||
highlightContent() {
|
||||
this.highlighting = true
|
||||
|
||||
|
@ -243,7 +271,7 @@ export default {
|
|||
},
|
||||
|
||||
reset() {
|
||||
this.setUrlArgs({file: null})
|
||||
this.setUrlArgs({file: null, line: null})
|
||||
this.removeBeforeUnload()
|
||||
this.removeKeyListener()
|
||||
},
|
||||
|
@ -276,9 +304,36 @@ export default {
|
|||
this.highlightedContent = this.content
|
||||
}
|
||||
},
|
||||
|
||||
selectedLine(line) {
|
||||
line = parseInt(line)
|
||||
if (isNaN(line)) {
|
||||
return
|
||||
}
|
||||
|
||||
const textarea = this.$refs.textarea
|
||||
const lines = this.content.split('\n')
|
||||
const cursor = lines.slice(0, line - 1).join('\n').length + 1
|
||||
|
||||
textarea.setSelectionRange(cursor, cursor)
|
||||
textarea.focus()
|
||||
this.setUrlArgs({line})
|
||||
this.$nextTick(() => {
|
||||
const offset = this.scrollToLine(line)
|
||||
this.$refs.selectedLine.style.top = `${offset}px`
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const args = this.getUrlArgs()
|
||||
const line = parseInt(this.line || args.line || 0)
|
||||
if (line) {
|
||||
if (!isNaN(line)) {
|
||||
this.selectedLine = line
|
||||
}
|
||||
}
|
||||
|
||||
this.loadFile()
|
||||
this.addBeforeUnload()
|
||||
this.addKeyListener()
|
||||
|
@ -332,8 +387,10 @@ $line-numbers-width: 2.5em;
|
|||
}
|
||||
|
||||
.editor-body {
|
||||
font-family: $monospace-font;
|
||||
|
||||
pre, textarea, code, .line-numbers {
|
||||
font-family: 'Fira Code', 'Noto Sans Mono', 'Inconsolata', 'Courier New', monospace;
|
||||
font-family: $monospace-font;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
|
@ -341,6 +398,10 @@ $line-numbers-width: 2.5em;
|
|||
white-space: pre;
|
||||
}
|
||||
|
||||
pre, textarea, code {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.line-numbers {
|
||||
width: $line-numbers-width;
|
||||
background: $tab-bg;
|
||||
|
@ -356,6 +417,15 @@ $line-numbers-width: 2.5em;
|
|||
width: 100%;
|
||||
text-align: right;
|
||||
padding-right: 0.25em;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: $hover-bg;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: $selected-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -372,16 +442,32 @@ $line-numbers-width: 2.5em;
|
|||
background: transparent;
|
||||
overflow-wrap: normal;
|
||||
overflow-x: scroll;
|
||||
z-index: 1;
|
||||
z-index: 2;
|
||||
color: rgba(0, 0, 0, 0);
|
||||
caret-color: black;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.selected-line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 1.5em;
|
||||
background: rgba(110, 255, 160, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.floating-btn) {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
// Fix for some hljs styles that render white text on white background
|
||||
:deep(code) {
|
||||
.hljs-subst {
|
||||
color: $selected-fg !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<FileEditor ref="fileEditor"
|
||||
:file="file"
|
||||
:is-new="isNew"
|
||||
:line="line"
|
||||
@save="$emit('save', $event)"
|
||||
v-if="file" />
|
||||
</div>
|
||||
|
@ -26,10 +27,11 @@
|
|||
import ConfirmDialog from "@/components/elements/ConfirmDialog";
|
||||
import FileEditor from "./Editor";
|
||||
import Modal from "@/components/Modal";
|
||||
import Utils from '@/Utils'
|
||||
|
||||
export default {
|
||||
emits: ['close', 'open', 'save'],
|
||||
mixins: [Modal],
|
||||
mixins: [Modal, Utils],
|
||||
components: {
|
||||
ConfirmDialog,
|
||||
FileEditor,
|
||||
|
@ -47,6 +49,11 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
|
||||
line: {
|
||||
type: [String, Number],
|
||||
default: null,
|
||||
},
|
||||
|
||||
withSave: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
|
@ -113,15 +120,28 @@ export default {
|
|||
|
||||
onClose() {
|
||||
this.$refs.fileEditor.reset()
|
||||
this.setUrlArgs({ maximized: null })
|
||||
this.$emit('close')
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
maximized() {
|
||||
this.setUrlArgs({ maximized: this.maximized })
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.maximized = !!this.getUrlArgs().maximized
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "src/style/items";
|
||||
|
||||
$maximized-modal-header-height: 2.75em;
|
||||
|
||||
.file-editor-root {
|
||||
.file-editor-modal {
|
||||
:deep(.modal) {
|
||||
|
@ -133,45 +153,73 @@ export default {
|
|||
|
||||
.modal-body {
|
||||
width: 100%;
|
||||
height: 50em;
|
||||
min-width: 30em;
|
||||
max-height: calc(100vh - 2em);
|
||||
}
|
||||
|
||||
.content {
|
||||
@extend .expand;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.maximized) {
|
||||
:deep(.body) {
|
||||
@include until($tablet) {
|
||||
width: calc(100vw - 2em);
|
||||
}
|
||||
:deep(.modal) {
|
||||
.body {
|
||||
@include until($tablet) {
|
||||
width: calc(100vw - 2em);
|
||||
}
|
||||
|
||||
@include from($tablet) {
|
||||
width: 40em;
|
||||
max-width: 100%;
|
||||
}
|
||||
@include from($tablet) {
|
||||
width: 40em;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@include from($desktop) {
|
||||
width: 100%;
|
||||
min-width: 50em;
|
||||
@include from($desktop) {
|
||||
width: 100%;
|
||||
min-width: 50em;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
height: 50em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.maximized {
|
||||
:deep(.body) {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
:deep(.modal) {
|
||||
width: calc(100vw - 2em);
|
||||
height: calc(100vh - 2em);
|
||||
|
||||
.content, .modal-body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: $maximized-modal-header-height;
|
||||
}
|
||||
|
||||
.body {
|
||||
height: calc(100% - #{$maximized-modal-header-height});
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.confirm-dialog-container {
|
||||
:deep(.modal) {
|
||||
.content {
|
||||
.body {
|
||||
min-width: 30em;
|
||||
max-width: 100%;
|
||||
}
|
||||
width: 35em !important;
|
||||
height: 9em !important;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
|
||||
.body {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue