@cloudron/pankow 4.2.3 → 4.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/ListItem.vue +188 -0
- package/components/Notification.vue +9 -61
- package/components/TabView.vue +26 -31
- package/fallbackImage.js +21 -17
- package/fetcher.js +47 -5
- package/gestures.js +15 -11
- package/index.js +7 -0
- package/notifyState.js +48 -0
- package/package.json +20 -2
- package/plugin.js +39 -0
- package/tooltip.js +34 -15
- package/types/fallbackImage.d.ts +5 -4
- package/types/fetcher.d.ts +60 -10
- package/types/gestures.d.ts +16 -1
- package/types/index.d.ts +4 -1
- package/types/notifyState.d.ts +8 -0
- package/types/plugin.d.ts +4 -0
- package/types/tooltip.d.ts +5 -8
- package/types/utils.d.ts +185 -25
- package/utils.js +154 -14
- package/viewers/GenericViewer.vue +20 -32
- package/viewers/ImageViewer.vue +149 -140
- package/viewers/PdfViewer.vue +27 -35
- package/viewers/TextViewer.vue +134 -136
- package/vite-plugin.js +16 -0
package/viewers/ImageViewer.vue
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
|
|
2
2
|
<template>
|
|
3
|
-
<div @mousemove="onMouseMove" class="full-page" @keyup.esc.stop="onClose" @keyup.left.stop="onPrev" @keyup.right.stop="onNext" tabindex="1" :style="{ opacity: dismissDistance ? (200 - dismissDistance)/100 : 1 }">
|
|
3
|
+
<div ref="rootEl" @mousemove="onMouseMove" class="full-page" @keyup.esc.stop="onClose" @keyup.left.stop="onPrev" @keyup.right.stop="onNext" tabindex="1" :style="{ opacity: dismissDistance ? (200 - dismissDistance)/100 : 1 }">
|
|
4
4
|
<div class="image-layer" ref="imageContainer" @scroll="onScroll($event)">
|
|
5
5
|
<div class="image" v-for="(entry, index) in entries" :key="entry.id" v-bind:id="`image${index}`" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd" :style="{ marginTop: dismissDistance ? (dismissDistance + 'px') : 0 }">
|
|
6
6
|
<img :src="entry.fullFileUrl" v-if="getFileTypeGroup(entry) === 'image'" draggable="false"/>
|
|
7
|
-
<video v-if="getFileTypeGroup(entry) !== 'image'" ref="
|
|
7
|
+
<video v-if="getFileTypeGroup(entry) !== 'image'" :ref="addVideoRef" controls>
|
|
8
8
|
<source :src="entry.fullFileUrl" />
|
|
9
9
|
</video>
|
|
10
10
|
</div>
|
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
</div>
|
|
18
18
|
</template>
|
|
19
19
|
|
|
20
|
-
<script>
|
|
20
|
+
<script setup>
|
|
21
21
|
|
|
22
|
+
import { ref, computed, onBeforeUpdate, onMounted, useTemplateRef } from 'vue';
|
|
23
|
+
import { useRouter } from 'vue-router';
|
|
22
24
|
import { getFileTypeGroup } from '../utils.js';
|
|
23
25
|
import Button from '../components/Button.vue';
|
|
24
26
|
|
|
@@ -27,144 +29,151 @@ let startY = 0;
|
|
|
27
29
|
const dismissDistanceThreshold = 150;
|
|
28
30
|
const dismissTriggerThreshold = 50;
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
onTouchStart(event) {
|
|
109
|
-
this.dismissDistance = 0;
|
|
110
|
-
touchDist = 0;
|
|
111
|
-
startY = event.changedTouches[0].pageY;
|
|
112
|
-
},
|
|
113
|
-
onTouchMove(event) {
|
|
114
|
-
touchDist = event.changedTouches[0].pageY - startY // get total dist traveled by finger while in contact with surface
|
|
115
|
-
if (touchDist > dismissTriggerThreshold) this.dismissDistance = touchDist - dismissTriggerThreshold;
|
|
116
|
-
},
|
|
117
|
-
onTouchEnd() {
|
|
118
|
-
if (touchDist > dismissDistanceThreshold) this.onClose();
|
|
119
|
-
touchDist = 0;
|
|
120
|
-
this.dismissDistance = 0;
|
|
121
|
-
},
|
|
122
|
-
open(entry, entries, instant = true) {
|
|
123
|
-
if (!entry || entry.isDirectory || !this.canHandle(entry)) return;
|
|
124
|
-
|
|
125
|
-
this.lockScroll = true;
|
|
126
|
-
this.keepOverlayAlive();
|
|
127
|
-
this.entry = entry;
|
|
128
|
-
this.entries = entries;
|
|
129
|
-
this.curIndex = entries.findIndex((item) => item.fileName === entry.fileName);
|
|
130
|
-
|
|
131
|
-
setTimeout(() => {
|
|
132
|
-
document.getElementById(`image${this.curIndex}`).scrollIntoView({ behavior: instant ? 'instant' : 'smooth' });
|
|
133
|
-
this.$el.focus();
|
|
134
|
-
}, 0);
|
|
135
|
-
|
|
136
|
-
setTimeout(() => {
|
|
137
|
-
this.lockScroll = false;
|
|
138
|
-
}, 500);
|
|
139
|
-
},
|
|
140
|
-
onDownload() {
|
|
141
|
-
if (!this.hasDownloadHandler) return;
|
|
142
|
-
this.downloadHandler(this.entry);
|
|
143
|
-
},
|
|
144
|
-
onClose() {
|
|
145
|
-
this.$emit('close');
|
|
146
|
-
|
|
147
|
-
// pause/stop video and audio playback on close
|
|
148
|
-
if (this.$refs.videoElements && this.$refs.videoElements.length) this.$refs.videoElements.forEach(e => e.pause());
|
|
149
|
-
|
|
150
|
-
// given some time for closing to keep DOM elements alive
|
|
151
|
-
setTimeout(() => {
|
|
152
|
-
this.entry = {};
|
|
153
|
-
}, 500);
|
|
154
|
-
},
|
|
155
|
-
onPrev() {
|
|
156
|
-
if (this.curIndex < 1) return;
|
|
157
|
-
this.navigationHandler(this.entries[this.curIndex-1]);
|
|
158
|
-
this.open(this.entries[this.curIndex-1], this.entries, false);
|
|
159
|
-
},
|
|
160
|
-
onNext() {
|
|
161
|
-
if (this.curIndex >= this.entries.length) return;
|
|
162
|
-
this.navigationHandler(this.entries[this.curIndex+1]);
|
|
163
|
-
this.open(this.entries[this.curIndex+1], this.entries, false);
|
|
32
|
+
const router = useRouter();
|
|
33
|
+
const emit = defineEmits([ 'close', 'saved' ]);
|
|
34
|
+
const props = defineProps({
|
|
35
|
+
downloadHandler: {
|
|
36
|
+
type: Function,
|
|
37
|
+
default: null
|
|
38
|
+
},
|
|
39
|
+
navigationHandler: {
|
|
40
|
+
type: Function,
|
|
41
|
+
default: null
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const showOverlay = ref(false);
|
|
46
|
+
const showOverlayTimer = ref(null);
|
|
47
|
+
const lockScroll = ref(false);
|
|
48
|
+
const curIndex = ref(-1);
|
|
49
|
+
const lastMousePosition = ref({});
|
|
50
|
+
const entry = ref({});
|
|
51
|
+
const entries = ref([]);
|
|
52
|
+
const dismissDistance = ref(0);
|
|
53
|
+
|
|
54
|
+
const hasDownloadHandler = computed(() => typeof props.downloadHandler === 'function');
|
|
55
|
+
const navigate = (entry) => (props.navigationHandler || ((e) => router.replace(e.resourcePath)))(entry);
|
|
56
|
+
|
|
57
|
+
const rootEl = useTemplateRef('rootEl');
|
|
58
|
+
const imageContainer = useTemplateRef('imageContainer');
|
|
59
|
+
const typeTestVideo = useTemplateRef('typeTestVideo');
|
|
60
|
+
const videoElements = ref([]);
|
|
61
|
+
|
|
62
|
+
function addVideoRef(el) {
|
|
63
|
+
if (el) videoElements.value.push(el);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
onBeforeUpdate(() => {
|
|
67
|
+
videoElements.value = [];
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
function keepOverlayAlive() {
|
|
71
|
+
showOverlay.value = true;
|
|
72
|
+
clearTimeout(showOverlayTimer.value);
|
|
73
|
+
showOverlayTimer.value = setTimeout(() => {
|
|
74
|
+
try {
|
|
75
|
+
if (document.elementFromPoint(lastMousePosition.value.x || 0, lastMousePosition.value.y || 0) !== imageContainer.value) return;
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.error('ImageViewer crash. lastMousePosition', lastMousePosition.value, e);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
showOverlay.value = false;
|
|
81
|
+
}, 2000);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function onMouseMove(event) {
|
|
85
|
+
lastMousePosition.value = { x: event.pageX, y: event.pageY };
|
|
86
|
+
keepOverlayAlive();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function canHandle(e) {
|
|
90
|
+
return (getFileTypeGroup(e) === 'image' || typeTestVideo.value.canPlayType(e.mimeType))
|
|
91
|
+
&& !(e.mimeType === 'image/heif')
|
|
92
|
+
&& !(e.mimeType === 'image/vnd.adobe.photoshop');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function onScroll() {
|
|
96
|
+
if (lockScroll.value) return;
|
|
97
|
+
|
|
98
|
+
for (let i = 1; i < entries.value.length; i++) {
|
|
99
|
+
const elem = document.getElementById('image'+i);
|
|
100
|
+
const rect = elem.getBoundingClientRect();
|
|
101
|
+
const midX = (rect.x+(rect.width/2));
|
|
102
|
+
if (midX >= (rect.width/4) && midX <= 3*(rect.width/4)) {
|
|
103
|
+
entry.value = entries.value[i];
|
|
104
|
+
|
|
105
|
+
if (curIndex.value !== i) {
|
|
106
|
+
curIndex.value = i;
|
|
107
|
+
navigate(entry.value);
|
|
164
108
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
109
|
+
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function onTouchStart(event) {
|
|
116
|
+
dismissDistance.value = 0;
|
|
117
|
+
touchDist = 0;
|
|
118
|
+
startY = event.changedTouches[0].pageY;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function onTouchMove(event) {
|
|
122
|
+
touchDist = event.changedTouches[0].pageY - startY;
|
|
123
|
+
if (touchDist > dismissTriggerThreshold) dismissDistance.value = touchDist - dismissTriggerThreshold;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function onTouchEnd() {
|
|
127
|
+
if (touchDist > dismissDistanceThreshold) onClose();
|
|
128
|
+
touchDist = 0;
|
|
129
|
+
dismissDistance.value = 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function open(e, fileList, instant = true) {
|
|
133
|
+
if (!e || e.isDirectory || !canHandle(e)) return;
|
|
134
|
+
|
|
135
|
+
lockScroll.value = true;
|
|
136
|
+
keepOverlayAlive();
|
|
137
|
+
entry.value = e;
|
|
138
|
+
entries.value = fileList;
|
|
139
|
+
curIndex.value = fileList.findIndex((item) => item.fileName === e.fileName);
|
|
140
|
+
|
|
141
|
+
setTimeout(() => {
|
|
142
|
+
document.getElementById(`image${curIndex.value}`).scrollIntoView({ behavior: instant ? 'instant' : 'smooth' });
|
|
143
|
+
rootEl.value.focus();
|
|
144
|
+
}, 0);
|
|
145
|
+
|
|
146
|
+
setTimeout(() => {
|
|
147
|
+
lockScroll.value = false;
|
|
148
|
+
}, 500);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function onDownload() {
|
|
152
|
+
if (!hasDownloadHandler.value) return;
|
|
153
|
+
props.downloadHandler(entry.value);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function onClose() {
|
|
157
|
+
emit('close');
|
|
158
|
+
|
|
159
|
+
if (videoElements.value && videoElements.value.length) videoElements.value.forEach(e => e.pause());
|
|
160
|
+
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
entry.value = {};
|
|
163
|
+
}, 500);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function onPrev() {
|
|
167
|
+
if (curIndex.value < 1) return;
|
|
168
|
+
navigate(entries.value[curIndex.value-1]);
|
|
169
|
+
open(entries.value[curIndex.value-1], entries.value, false);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function onNext() {
|
|
173
|
+
if (curIndex.value >= entries.value.length) return;
|
|
174
|
+
navigate(entries.value[curIndex.value+1]);
|
|
175
|
+
open(entries.value[curIndex.value+1], entries.value, false);
|
|
176
|
+
}
|
|
168
177
|
|
|
169
178
|
</script>
|
|
170
179
|
|
package/viewers/PdfViewer.vue
CHANGED
|
@@ -21,49 +21,41 @@
|
|
|
21
21
|
</MainLayout>
|
|
22
22
|
</template>
|
|
23
23
|
|
|
24
|
-
<script>
|
|
24
|
+
<script setup>
|
|
25
25
|
|
|
26
|
+
import { ref } from 'vue';
|
|
26
27
|
import Button from '../components/Button.vue';
|
|
27
28
|
import MainLayout from '../components/MainLayout.vue';
|
|
28
29
|
import TopBar from '../components/TopBar.vue';
|
|
29
30
|
import utils from '../utils.js';
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
name: 'PdfViewer',
|
|
33
|
-
components: {
|
|
34
|
-
Button,
|
|
35
|
-
MainLayout,
|
|
36
|
-
TopBar
|
|
37
|
-
},
|
|
38
|
-
emits: [ 'close' ],
|
|
39
|
-
props: {
|
|
40
|
-
tr: {
|
|
41
|
-
type: Function,
|
|
42
|
-
default(id) { console.warn('Missing tr for PdfViewer'); return utils.translation(id); }
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
data() {
|
|
46
|
-
return {
|
|
47
|
-
entry: null
|
|
48
|
-
};
|
|
49
|
-
},
|
|
50
|
-
methods: {
|
|
51
|
-
canHandle(entry) {
|
|
52
|
-
return entry.mimeType === 'application/pdf';
|
|
53
|
-
},
|
|
54
|
-
async open(entry) {
|
|
55
|
-
if (!entry || entry.isDirectory || !this.canHandle(entry)) return;
|
|
32
|
+
const emit = defineEmits([ 'close' ]);
|
|
56
33
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
},
|
|
62
|
-
onDownload() {
|
|
63
|
-
window.location.href = this.entry.downloadFileUrl;
|
|
64
|
-
}
|
|
34
|
+
const props = defineProps({
|
|
35
|
+
tr: {
|
|
36
|
+
type: Function,
|
|
37
|
+
default(id) { console.warn('Missing tr for PdfViewer'); return utils.translation(id); }
|
|
65
38
|
}
|
|
66
|
-
};
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const entry = ref(null);
|
|
42
|
+
|
|
43
|
+
function canHandle(e) {
|
|
44
|
+
return e.mimeType === 'application/pdf';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function open(e) {
|
|
48
|
+
if (!e || e.isDirectory || !canHandle(e)) return;
|
|
49
|
+
entry.value = e;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function onClose() {
|
|
53
|
+
emit('close');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function onDownload() {
|
|
57
|
+
window.location.href = entry.value.downloadFileUrl;
|
|
58
|
+
}
|
|
67
59
|
|
|
68
60
|
</script>
|
|
69
61
|
|
package/viewers/TextViewer.vue
CHANGED
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
</MainLayout>
|
|
23
23
|
</template>
|
|
24
24
|
|
|
25
|
-
<script>
|
|
25
|
+
<script setup>
|
|
26
26
|
|
|
27
|
+
import { ref, onMounted, onBeforeUnmount, useTemplateRef } from 'vue';
|
|
27
28
|
import { editor, languages } from 'monaco-editor';
|
|
28
29
|
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
|
|
29
30
|
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
|
|
@@ -57,146 +58,143 @@ function getLanguage(filename) {
|
|
|
57
58
|
return language ? language.id : '';
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
61
|
+
const emit = defineEmits([ 'close' ]);
|
|
62
|
+
const props = defineProps({
|
|
63
|
+
saveHandler: {
|
|
64
|
+
type: Function,
|
|
65
|
+
default() { console.warn('Missing saveHandler for TextViewer'); }
|
|
66
|
+
},
|
|
67
|
+
tr: {
|
|
68
|
+
type: Function,
|
|
69
|
+
default(id) { console.warn('Missing tr for TextViewer'); return utils.translation(id); }
|
|
70
|
+
},
|
|
71
|
+
readonly: {
|
|
72
|
+
type: Boolean,
|
|
73
|
+
default: false
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const entry = ref({});
|
|
78
|
+
const isChanged = ref(false);
|
|
79
|
+
const busySave = ref(false);
|
|
80
|
+
const lastSavedVersion = ref(0);
|
|
81
|
+
const undoPossible = ref(false);
|
|
82
|
+
const redoPossible = ref(false);
|
|
83
|
+
|
|
84
|
+
const inputDialog = useTemplateRef('inputDialog');
|
|
85
|
+
const editorView = useTemplateRef('editorView');
|
|
86
|
+
|
|
87
|
+
let editorInstance = null;
|
|
88
|
+
|
|
89
|
+
function canHandle(e) {
|
|
90
|
+
if (e.isBinary) return false;
|
|
91
|
+
return utils.getFileTypeGroup(e) === 'text' ||
|
|
92
|
+
utils.getFileTypeGroup(e) === 'application';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function open(e, content) {
|
|
96
|
+
if (!e || e.isDirectory || !canHandle(e)) return;
|
|
97
|
+
|
|
98
|
+
entry.value = e;
|
|
99
|
+
|
|
100
|
+
editorInstance.setModel(editor.createModel(content, getLanguage(e.fileName)));
|
|
101
|
+
|
|
102
|
+
const initialVersion = editorInstance.getModel().getAlternativeVersionId();
|
|
103
|
+
let currentVersion = initialVersion;
|
|
104
|
+
let lastVersion = initialVersion;
|
|
105
|
+
lastSavedVersion.value = initialVersion;
|
|
106
|
+
|
|
107
|
+
editorInstance.onDidChangeModelContent(e => {
|
|
108
|
+
const versionId = editorInstance.getModel().getAlternativeVersionId();
|
|
109
|
+
|
|
110
|
+
isChanged.value = lastSavedVersion.value !== versionId;
|
|
111
|
+
|
|
112
|
+
if (versionId < currentVersion) {
|
|
113
|
+
redoPossible.value = true;
|
|
114
|
+
if (versionId === initialVersion) {
|
|
115
|
+
undoPossible.value = false;
|
|
75
116
|
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
MainLayout,
|
|
81
|
-
TopBar,
|
|
82
|
-
Spinner
|
|
83
|
-
},
|
|
84
|
-
data() {
|
|
85
|
-
return {
|
|
86
|
-
entry: {},
|
|
87
|
-
isChanged: false,
|
|
88
|
-
busySave: false,
|
|
89
|
-
lastSavedVersion: 0
|
|
90
|
-
};
|
|
91
|
-
},
|
|
92
|
-
methods: {
|
|
93
|
-
canHandle(entry) {
|
|
94
|
-
if (entry.isBinary) return false;
|
|
95
|
-
return utils.getFileTypeGroup(entry) === 'text' ||
|
|
96
|
-
utils.getFileTypeGroup(entry) === 'application';
|
|
97
|
-
},
|
|
98
|
-
open(entry, content) {
|
|
99
|
-
if (!entry || entry.isDirectory || !this.canHandle(entry)) return;
|
|
100
|
-
|
|
101
|
-
this.entry = entry;
|
|
102
|
-
|
|
103
|
-
this.editor.setModel(editor.createModel(content, getLanguage(entry.fileName)));
|
|
104
|
-
|
|
105
|
-
const initialVersion = this.editor.getModel().getAlternativeVersionId();
|
|
106
|
-
let currentVersion = initialVersion;
|
|
107
|
-
let lastVersion = initialVersion;
|
|
108
|
-
this.lastSavedVersion = initialVersion;
|
|
109
|
-
|
|
110
|
-
this.editor.onDidChangeModelContent(e => {
|
|
111
|
-
const versionId = this.editor.getModel().getAlternativeVersionId();
|
|
112
|
-
|
|
113
|
-
this.isChanged = this.lastSavedVersion !== versionId;
|
|
114
|
-
|
|
115
|
-
if (versionId < currentVersion) {
|
|
116
|
-
this.redoPossible = true;
|
|
117
|
-
// no more undo possible
|
|
118
|
-
if (versionId === initialVersion) {
|
|
119
|
-
this.undoPossible = false;
|
|
120
|
-
}
|
|
121
|
-
} else {
|
|
122
|
-
if (versionId <= lastVersion) {
|
|
123
|
-
// redoing the last change
|
|
124
|
-
if (versionId == lastVersion) {
|
|
125
|
-
this.redoPossible = false;
|
|
126
|
-
}
|
|
127
|
-
} else { // adding new change, disable redo when adding new changes
|
|
128
|
-
this.redoPossible = false;
|
|
129
|
-
if (currentVersion > lastVersion) {
|
|
130
|
-
lastVersion = currentVersion;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
this.undoPossible = true;
|
|
134
|
-
}
|
|
135
|
-
currentVersion = versionId;
|
|
136
|
-
});
|
|
137
|
-
},
|
|
138
|
-
async onClose() {
|
|
139
|
-
if (this.isChanged) {
|
|
140
|
-
const yes = await this.$refs.inputDialog.confirm({
|
|
141
|
-
message: this.tr('filemanager.textEditorCloseDialog.title'),
|
|
142
|
-
confirmStyle: 'danger',
|
|
143
|
-
confirmLabel: 'Discard changes and close',
|
|
144
|
-
rejectLabel: 'Cancel'
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
if (!yes) return;
|
|
148
|
-
|
|
149
|
-
this.editor.setModel(editor.createModel(''));
|
|
150
|
-
this.$emit('close');
|
|
151
|
-
this.isChanged = false;
|
|
152
|
-
} else {
|
|
153
|
-
this.editor.setModel(editor.createModel(''));
|
|
154
|
-
this.$emit('close');
|
|
117
|
+
} else {
|
|
118
|
+
if (versionId <= lastVersion) {
|
|
119
|
+
if (versionId == lastVersion) {
|
|
120
|
+
redoPossible.value = false;
|
|
155
121
|
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
await this.saveHandler(this.entry, this.editor.getValue());
|
|
161
|
-
|
|
162
|
-
this.isChanged = false;
|
|
163
|
-
this.lastSavedVersion = this.editor.getModel().getAlternativeVersionId();
|
|
164
|
-
|
|
165
|
-
this.busySave = false;
|
|
166
|
-
this.editor.focus();
|
|
167
|
-
},
|
|
168
|
-
keydownHandler(event) {
|
|
169
|
-
if((navigator.platform.match('Mac') ? event.metaKey : event.ctrlKey) && event.key === 's') {
|
|
170
|
-
event.preventDefault();
|
|
171
|
-
if (this.isChanged) this.onSave();
|
|
122
|
+
} else {
|
|
123
|
+
redoPossible.value = false;
|
|
124
|
+
if (currentVersion > lastVersion) {
|
|
125
|
+
lastVersion = currentVersion;
|
|
172
126
|
}
|
|
173
|
-
},
|
|
174
|
-
beforeunloadHandler(event) {
|
|
175
|
-
// this prevents accidental closing or reloading
|
|
176
|
-
if (this.isChanged) event.preventDefault();
|
|
177
127
|
}
|
|
178
|
-
|
|
179
|
-
mounted() {
|
|
180
|
-
const theme = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'vs-dark' : 'vs-light';
|
|
181
|
-
|
|
182
|
-
this.editor = editor.create(this.$refs.editorView, {
|
|
183
|
-
automaticLayout: true,
|
|
184
|
-
value: '',
|
|
185
|
-
theme
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
|
189
|
-
editor.setTheme(event.matches ? 'vs-dark' : 'vs-light');
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
window.addEventListener('keydown', this.keydownHandler);
|
|
193
|
-
window.addEventListener('beforeunload', this.beforeunloadHandler);
|
|
194
|
-
},
|
|
195
|
-
beforeUnmount() {
|
|
196
|
-
window.removeEventListener('keydown', this.keydownHandler);
|
|
197
|
-
window.removeEventListener('beforeunload', this.beforeunloadHandler);
|
|
128
|
+
undoPossible.value = true;
|
|
198
129
|
}
|
|
199
|
-
|
|
130
|
+
currentVersion = versionId;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function onClose() {
|
|
135
|
+
if (isChanged.value) {
|
|
136
|
+
const yes = await inputDialog.value.confirm({
|
|
137
|
+
message: props.tr('filemanager.textEditorCloseDialog.title'),
|
|
138
|
+
confirmStyle: 'danger',
|
|
139
|
+
confirmLabel: 'Discard changes and close',
|
|
140
|
+
rejectLabel: 'Cancel'
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (!yes) return;
|
|
144
|
+
|
|
145
|
+
editorInstance.setModel(editor.createModel(''));
|
|
146
|
+
emit('close');
|
|
147
|
+
isChanged.value = false;
|
|
148
|
+
} else {
|
|
149
|
+
editorInstance.setModel(editor.createModel(''));
|
|
150
|
+
emit('close');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function onSave() {
|
|
155
|
+
busySave.value = true;
|
|
156
|
+
|
|
157
|
+
await props.saveHandler(entry.value, editorInstance.getValue());
|
|
158
|
+
|
|
159
|
+
isChanged.value = false;
|
|
160
|
+
lastSavedVersion.value = editorInstance.getModel().getAlternativeVersionId();
|
|
161
|
+
|
|
162
|
+
busySave.value = false;
|
|
163
|
+
editorInstance.focus();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function keydownHandler(event) {
|
|
167
|
+
if((navigator.platform.match('Mac') ? event.metaKey : event.ctrlKey) && event.key === 's') {
|
|
168
|
+
event.preventDefault();
|
|
169
|
+
if (isChanged.value) onSave();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function beforeunloadHandler(event) {
|
|
174
|
+
if (isChanged.value) event.preventDefault();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
onMounted(() => {
|
|
178
|
+
const theme = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'vs-dark' : 'vs-light';
|
|
179
|
+
|
|
180
|
+
editorInstance = editor.create(editorView.value, {
|
|
181
|
+
automaticLayout: true,
|
|
182
|
+
value: '',
|
|
183
|
+
theme
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
|
187
|
+
editor.setTheme(event.matches ? 'vs-dark' : 'vs-light');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
window.addEventListener('keydown', keydownHandler);
|
|
191
|
+
window.addEventListener('beforeunload', beforeunloadHandler);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
onBeforeUnmount(() => {
|
|
195
|
+
window.removeEventListener('keydown', keydownHandler);
|
|
196
|
+
window.removeEventListener('beforeunload', beforeunloadHandler);
|
|
197
|
+
});
|
|
200
198
|
|
|
201
199
|
</script>
|
|
202
200
|
|