Skip to content

Commit 48b14f3

Browse files
Merge pull request #8037 from nextcloud/chore/split-source-view
chore(split): SourceView from ViewerComponent
2 parents 3c13e47 + bdc845b commit 48b14f3

2 files changed

Lines changed: 161 additions & 98 deletions

File tree

src/components/SourceView.vue

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<template>
7+
<div
8+
id="editor-container"
9+
data-text-el="editor-container"
10+
class="text-editor source-viewer">
11+
<Component
12+
:is="readerComponent"
13+
:content="content"
14+
:file-id="fileid"
15+
:read-only="true"
16+
:show-menu-bar="false" />
17+
<NcButton
18+
v-if="isEmbedded"
19+
class="toggle-interactive"
20+
@click="$emit('edit')">
21+
{{ t('text', 'Edit') }}
22+
<template #icon>
23+
<PencilOutlineIcon />
24+
</template>
25+
</NcButton>
26+
</div>
27+
</template>
28+
29+
<script>
30+
import axios from '@nextcloud/axios'
31+
import { getClient, getRootPath } from '@nextcloud/files/dav'
32+
import { t } from '@nextcloud/l10n'
33+
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
34+
import Vue from 'vue'
35+
import PencilOutlineIcon from 'vue-material-design-icons/PencilOutline.vue'
36+
import MarkdownContentEditor from './Editor/MarkdownContentEditor.vue'
37+
import PlainTextReader from './PlainTextReader.vue'
38+
39+
export default {
40+
name: 'SourceView',
41+
components: {
42+
NcButton: Vue.extend(NcButton),
43+
PencilOutlineIcon: Vue.extend(PencilOutlineIcon),
44+
PlainTextReader: Vue.extend(PlainTextReader),
45+
MarkdownContentEditor: Vue.extend(MarkdownContentEditor),
46+
},
47+
inject: ['isEmbedded'],
48+
props: {
49+
filename: {
50+
type: String,
51+
default: null,
52+
},
53+
fileid: {
54+
type: Number,
55+
default: null,
56+
},
57+
mime: {
58+
type: String,
59+
default: null,
60+
},
61+
source: {
62+
type: String,
63+
default: undefined,
64+
},
65+
},
66+
data() {
67+
return {
68+
content: '',
69+
}
70+
},
71+
computed: {
72+
isEncrypted() {
73+
return this.$attrs.e2EeIsEncrypted || false
74+
},
75+
76+
isMarkdown() {
77+
return (
78+
this.mime === 'text/markdown' || this.mime === 'text/x-web-markdown'
79+
)
80+
},
81+
82+
readerComponent() {
83+
return this.isMarkdown ? MarkdownContentEditor : PlainTextReader
84+
},
85+
},
86+
87+
watch: {
88+
source() {
89+
this.loadFileContent()
90+
},
91+
},
92+
93+
mounted() {
94+
this.loadFileContent()
95+
},
96+
97+
methods: {
98+
async loadFileContent() {
99+
if (this.isEncrypted) {
100+
this.content = await this.fetchDecryptedContent()
101+
this.contentLoaded = true
102+
} else {
103+
const response = await axios.get(this.source)
104+
this.content = response.data
105+
this.contentLoaded = true
106+
}
107+
this.$emit('loaded', true)
108+
},
109+
async fetchDecryptedContent() {
110+
const client = getClient()
111+
const response = await client.getFileContents(
112+
`${getRootPath()}${this.filename}`,
113+
{ details: true },
114+
)
115+
const blob = new Blob([response.data], {
116+
type: response.headers['content-type'],
117+
})
118+
const reader = new FileReader()
119+
reader.readAsText(blob)
120+
return new Promise((resolve) => {
121+
reader.onload = () => {
122+
resolve(reader.result)
123+
}
124+
})
125+
},
126+
t,
127+
},
128+
}
129+
</script>
130+
<style lang="scss" scoped>
131+
.source-viewer {
132+
display: block;
133+
134+
.editor__content-wrapper {
135+
margin-top: var(--header-height);
136+
}
137+
138+
.toggle-interactive {
139+
position: sticky;
140+
bottom: 0;
141+
right: 0;
142+
z-index: 1;
143+
margin-left: auto;
144+
margin-right: 0;
145+
}
146+
}
147+
</style>

src/components/ViewerComponent.vue

Lines changed: 14 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -13,46 +13,26 @@
1313
:share-token="shareToken"
1414
:class="{ 'text-editor--embedding': isEmbedded }"
1515
:mime="mime" />
16-
<div
16+
<SourceView
1717
v-else
18-
id="editor-container"
19-
data-text-el="editor-container"
20-
class="text-editor source-viewer">
21-
<Component
22-
:is="readerComponent"
23-
:content="content"
24-
:file-id="fileid"
25-
:read-only="true"
26-
:show-menu-bar="false" />
27-
<NcButton v-if="isEmbedded" class="toggle-interactive" @click="toggleEdit">
28-
{{ t('text', 'Edit') }}
29-
<template #icon>
30-
<PencilOutlineIcon />
31-
</template>
32-
</NcButton>
33-
</div>
18+
:fileid="fileid"
19+
:filename="filename"
20+
:mime="mime"
21+
:source="source"
22+
v-bind="$attrs"
23+
@loaded="onLoaded"
24+
@edit="toggleEdit" />
3425
</template>
3526

3627
<script>
37-
import axios from '@nextcloud/axios'
38-
import { getClient, getRootPath } from '@nextcloud/files/dav'
39-
import { t } from '@nextcloud/l10n'
4028
import { getSharingToken } from '@nextcloud/sharing/public'
41-
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
42-
import Vue from 'vue'
43-
import PencilOutlineIcon from 'vue-material-design-icons/PencilOutline.vue'
44-
import MarkdownContentEditor from './Editor/MarkdownContentEditor.vue'
45-
import PlainTextReader from './PlainTextReader.vue'
46-
4729
import getEditorInstance from './Editor.singleton.js'
30+
import SourceView from './SourceView.vue'
4831
4932
export default {
5033
name: 'ViewerComponent',
5134
components: {
52-
NcButton: Vue.extend(NcButton),
53-
PencilOutlineIcon: Vue.extend(PencilOutlineIcon),
54-
PlainTextReader: Vue.extend(PlainTextReader),
55-
MarkdownContentEditor: Vue.extend(MarkdownContentEditor),
35+
SourceView,
5636
Editor: getEditorInstance,
5737
},
5838
provide() {
@@ -100,7 +80,6 @@ export default {
10080
},
10181
data() {
10282
return {
103-
content: '',
10483
hasToggledInteractiveEmbedding: false,
10584
}
10685
},
@@ -116,67 +95,21 @@ export default {
11695
&& !this.hasToggledInteractiveEmbedding
11796
)
11897
},
119-
120-
isEncrypted() {
121-
return this.$attrs.e2EeIsEncrypted || false
122-
},
123-
124-
isMarkdown() {
125-
return (
126-
this.mime === 'text/markdown' || this.mime === 'text/x-web-markdown'
127-
)
128-
},
129-
130-
/** @return {boolean} */
131-
readerComponent() {
132-
return this.isMarkdown ? MarkdownContentEditor : PlainTextReader
133-
},
134-
},
135-
136-
watch: {
137-
source() {
138-
this.loadFileContent()
139-
},
14098
},
14199
142100
mounted() {
143-
this.loadFileContent()
101+
if (!this.useSourceView) {
102+
this.onLoaded()
103+
}
144104
},
145105
146106
methods: {
147-
async loadFileContent() {
148-
if (this.useSourceView) {
149-
if (this.isEncrypted) {
150-
this.content = await this.fetchDecryptedContent()
151-
this.contentLoaded = true
152-
} else {
153-
const response = await axios.get(this.source)
154-
this.content = response.data
155-
this.contentLoaded = true
156-
}
157-
}
107+
async onLoaded() {
158108
this.$emit('update:loaded', true)
159109
},
160110
toggleEdit() {
161111
this.hasToggledInteractiveEmbedding = true
162112
},
163-
async fetchDecryptedContent() {
164-
const client = getClient()
165-
const response = await client.getFileContents(
166-
`${getRootPath()}${this.filename}`,
167-
{ details: true },
168-
)
169-
const blob = new Blob([response.data], {
170-
type: response.headers['content-type'],
171-
})
172-
const reader = new FileReader()
173-
reader.readAsText(blob)
174-
return new Promise((resolve) => {
175-
reader.onload = () => {
176-
resolve(reader.result)
177-
}
178-
})
179-
},
180113
t,
181114
},
182115
}
@@ -192,23 +125,6 @@ export default {
192125
position: relative;
193126
background-color: var(--color-main-background);
194127
195-
&.source-viewer {
196-
display: block;
197-
198-
.editor__content-wrapper {
199-
margin-top: var(--header-height);
200-
}
201-
202-
.toggle-interactive {
203-
position: sticky;
204-
bottom: 0;
205-
right: 0;
206-
z-index: 1;
207-
margin-left: auto;
208-
margin-right: 0;
209-
}
210-
}
211-
212128
&.text-editor--embedding {
213129
min-height: 400px;
214130
}

0 commit comments

Comments
 (0)