Skip to content

Commit c202b79

Browse files
committed
Refactor: Move personal user settings page to Vue
Signed-off-by: codewithvk <vivek.javiya@collabora.com>
1 parent 705cdf6 commit c202b79

File tree

4 files changed

+413
-221
lines changed

4 files changed

+413
-221
lines changed

src/components/DocSigningField.vue

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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 class="doc-signing-field">
8+
<NcTextArea :value="localValue"
9+
:label="label"
10+
:disabled="disabled"
11+
:placeholder="placeholder"
12+
:helper-text="helperText"
13+
@update:value="handleUpdate" />
14+
<div class="doc-signing-actions">
15+
<NcButton type="secondary" @click="onClickSave">
16+
{{ t('richdocuments', 'Save') }}
17+
</NcButton>
18+
<NcButton type="secondary" :title="t('richdocuments', 'Remove')" @click="onClickRemove">
19+
<DeleteIcon :size="20" />
20+
</NcButton>
21+
</div>
22+
</div>
23+
</template>
24+
25+
<script>
26+
import NcTextArea from '@nextcloud/vue/dist/Components/NcTextArea.js'
27+
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
28+
import DeleteIcon from 'vue-material-design-icons/Delete.vue'
29+
30+
export default {
31+
name: 'DocSigningField',
32+
components: {
33+
NcTextArea,
34+
NcButton,
35+
DeleteIcon,
36+
},
37+
props: {
38+
label: {
39+
type: String,
40+
required: true,
41+
},
42+
value: {
43+
type: String,
44+
default: '',
45+
},
46+
disabled: {
47+
type: Boolean,
48+
default: false,
49+
},
50+
placeholder: {
51+
type: String,
52+
default: '',
53+
},
54+
helperText: {
55+
type: String,
56+
default: '',
57+
},
58+
},
59+
data() {
60+
return {
61+
localValue: this.value,
62+
}
63+
},
64+
watch: {
65+
value(newVal) {
66+
this.localValue = newVal
67+
},
68+
},
69+
methods: {
70+
t(scope, text) {
71+
return window.t ? window.t(scope, text) : text
72+
},
73+
handleUpdate(val) {
74+
this.localValue = val
75+
this.$emit('update:value', val)
76+
},
77+
onClickSave() {
78+
this.$emit('save', this.localValue)
79+
},
80+
onClickRemove() {
81+
this.$emit('remove')
82+
},
83+
},
84+
}
85+
</script>
86+
87+
<style scoped>
88+
.doc-signing-field {
89+
display: flex;
90+
flex-direction: column;
91+
}
92+
.doc-signing-actions {
93+
display: flex;
94+
align-items: center;
95+
gap: 1rem;
96+
}
97+
</style>
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<template>
7+
<NcSettingsSection :name="t('richdocuments', 'Nextcloud Office')"
8+
:description="t('richdocuments', 'Personal Settings for Nextcloud Office')"
9+
:limit-width="true">
10+
<span id="documents-admin-msg" class="msg" />
11+
12+
<!-- Template folder selection -->
13+
<div class="template-folder-settings">
14+
<div class="template-input-wrapper">
15+
<NcTextField v-model="templateFolder"
16+
:label="t('richdocuments', 'Select a template directory')"
17+
:disabled="true" />
18+
</div>
19+
<NcButton id="templateSelectButton"
20+
type="secondary"
21+
@click="onTemplateSelectButtonClick">
22+
<span class="icon-folder"
23+
:title="t('richdocuments', 'Select a personal template folder')"
24+
data-toggle="tooltip" />
25+
</NcButton>
26+
<NcButton id="templateResetButton"
27+
type="secondary"
28+
:title="t('richdocuments', 'Remove personal template folder')"
29+
@click="resetTemplate">
30+
<DeleteIcon :size="20" />
31+
</NcButton>
32+
</div>
33+
<p>
34+
<em>
35+
{{ t('richdocuments', 'Templates inside of this directory will be added to the template selector of Nextcloud Office.') }}
36+
</em>
37+
</p>
38+
39+
<!-- Zotero -->
40+
<div class="zotero-section">
41+
<p><strong>{{ t('richdocuments', 'Zotero') }}</strong></p>
42+
<template v-if="hasZoteroSupport">
43+
<div class="input-wrapper">
44+
<div class="zotero-inline">
45+
<div class="zotero-input-wrapper">
46+
<NcTextField id="zoteroAPIKeyField"
47+
:value="zoteroAPIKey"
48+
:label="t('richdocuments', 'Enter Zotero API Key')"
49+
@update:value="setZoteroAPIKey" />
50+
</div>
51+
<NcButton id="zoteroAPIKeySave"
52+
type="secondary"
53+
@click="saveZoteroAPIKey">
54+
{{ t('richdocuments', 'Save') }}
55+
</NcButton>
56+
<NcButton id="zoteroAPIKeyRemove"
57+
type="secondary"
58+
:title="t('richdocuments', 'Remove Zotero API Key')"
59+
@click="resetZoteroAPI">
60+
<DeleteIcon :size="20" />
61+
</NcButton>
62+
</div>
63+
<p>
64+
<em>
65+
{{ t('richdocuments', 'To use Zotero specify your API key here. You can create your API key in your') }}
66+
<a href="https://www.zotero.org/settings/keys" target="_blank">
67+
{{ t('richdocuments', 'Zotero account API settings.') }}
68+
</a>
69+
</em>
70+
</p>
71+
</div>
72+
</template>
73+
<p v-else>
74+
<em>
75+
{{ t('richdocuments', 'This instance does not support Zotero, because the feature is missing or disabled. Please contact the administration.') }}
76+
</em>
77+
</p>
78+
</div>
79+
80+
<!-- Document signing -->
81+
<div class="docsign-section">
82+
<p class="doc_sign_head">
83+
<strong>{{ t('richdocuments', 'Document signing') }}</strong>
84+
</p>
85+
<template v-if="hasDocumentSigningSupport">
86+
<div class="input-wrapper">
87+
<!-- Document Signing Cert -->
88+
<DocSigningField v-model="documentSigningCert"
89+
:label="t('richdocuments', 'Enter document signing cert (in PEM format)')"
90+
@save="val => setDocumentSigningCert(val)"
91+
@remove="() => setDocumentSigningCert('')" />
92+
<!-- Document Signing Key -->
93+
<DocSigningField v-model="documentSigningKey"
94+
:label="t('richdocuments', 'Enter document signing key')"
95+
@save="val => setDocumentSigningKey(val)"
96+
@remove="() => setDocumentSigningKey('')" />
97+
<!-- Document Signing CA -->
98+
<DocSigningField v-model="documentSigningCa"
99+
:label="t('richdocuments', 'Enter document signing CA chain')"
100+
@save="val => setDocumentSigningCa(val)"
101+
@remove="() => setDocumentSigningCa('')" />
102+
<p>
103+
<em>
104+
{{ t('richdocuments', 'To use document signing, specify your signing certificate, key and CA chain here.') }}
105+
</em>
106+
</p>
107+
</div>
108+
</template>
109+
<p v-else>
110+
<em>
111+
{{ t('richdocuments', 'This instance does not support document signing, because the feature is missing or disabled. Please contact the administrator.') }}
112+
</em>
113+
</p>
114+
</div>
115+
</NcSettingsSection>
116+
</template>
117+
118+
<script>
119+
import { generateFilePath } from '@nextcloud/router'
120+
import { showError, showSuccess } from '@nextcloud/dialogs'
121+
import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js'
122+
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
123+
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
124+
import DocSigningField from './DocSigningField.vue'
125+
import DeleteIcon from 'vue-material-design-icons/Delete.vue'
126+
127+
export default {
128+
name: 'PersonalSettings',
129+
components: {
130+
NcSettingsSection,
131+
NcTextField,
132+
NcButton,
133+
DocSigningField,
134+
DeleteIcon,
135+
},
136+
props: {
137+
initial: {
138+
type: Object,
139+
required: true,
140+
},
141+
},
142+
data() {
143+
return {
144+
templateFolder: this.initial.templateFolder || '',
145+
hasZoteroSupport: this.initial.hasZoteroSupport || false,
146+
zoteroAPIKey: this.initial.zoteroAPIKey || '',
147+
hasDocumentSigningSupport: this.initial.hasDocumentSigningSupport || false,
148+
documentSigningCert: this.initial.documentSigningCert || '',
149+
documentSigningKey: this.initial.documentSigningKey || '',
150+
documentSigningCa: this.initial.documentSigningCa || '',
151+
}
152+
},
153+
methods: {
154+
setZoteroAPIKey(newVal) {
155+
this.zoteroAPIKey = newVal
156+
},
157+
onTemplateSelectButtonClick() {
158+
OC.dialogs.filepicker(
159+
this.t('richdocuments', 'Select a personal template folder'),
160+
(datapath) => {
161+
this.updateSetting(
162+
{ templateFolder: datapath },
163+
() => { this.templateFolder = datapath },
164+
() => {},
165+
)
166+
},
167+
false,
168+
'httpd/unix-directory',
169+
true,
170+
OC.dialogs.FILEPICKER_TYPE_CHOOSE,
171+
)
172+
},
173+
resetTemplate() {
174+
this.updateSetting(
175+
{ templateFolder: '' },
176+
() => { this.templateFolder = '' },
177+
() => {},
178+
)
179+
},
180+
saveZoteroAPIKey() {
181+
this.updateSetting(
182+
{ zoteroAPIKeyInput: this.zoteroAPIKey },
183+
() => { showSuccess(this.t('richdocuments', 'Zotero API key saved')) },
184+
() => { showError(this.t('richdocuments', 'Failed to update the Zotero API key')) },
185+
)
186+
},
187+
resetZoteroAPI() {
188+
this.updateSetting(
189+
{ zoteroAPIKeyInput: '' },
190+
() => { this.zoteroAPIKey = '' },
191+
() => { showError(this.t('richdocuments', 'Failed to reset the Zotero API key')) },
192+
)
193+
},
194+
setDocumentSigningCert(val) {
195+
this.updateSetting(
196+
{ documentSigningCertInput: val },
197+
() => {
198+
this.documentSigningCert = val
199+
if (val === '') {
200+
showSuccess(this.t('richdocuments', 'Document signing cert removed'))
201+
} else {
202+
showSuccess(this.t('richdocuments', 'Document signing cert saved'))
203+
}
204+
},
205+
() => { showError(this.t('richdocuments', 'Failed to update the document signing CA chain')) },
206+
)
207+
},
208+
setDocumentSigningKey(val) {
209+
this.updateSetting(
210+
{ documentSigningKeyInput: val },
211+
() => {
212+
this.documentSigningKey = val
213+
if (val === '') {
214+
showSuccess(this.t('richdocuments', 'Document signing key removed'))
215+
} else {
216+
showSuccess(this.t('richdocuments', 'Document signing key saved'))
217+
}
218+
},
219+
() => { showError(this.t('richdocuments', 'Failed to update the document signing CA chain')) },
220+
)
221+
},
222+
setDocumentSigningCa(val) {
223+
this.updateSetting(
224+
{ documentSigningCaInput: val },
225+
() => {
226+
this.documentSigningCa = val
227+
if (val === '') {
228+
showSuccess(this.t('richdocuments', 'Document signing CA chain removed'))
229+
} else {
230+
showSuccess(this.t('richdocuments', 'Document signing CA chain saved'))
231+
}
232+
},
233+
() => { showError(this.t('richdocuments', 'Failed to update the document signing CA chain')) },
234+
)
235+
},
236+
updateSetting(data, successCallback, errorCallback) {
237+
OC.msg.startAction('#documents-admin-msg', this.t('richdocuments', 'Saving …'))
238+
const request = new XMLHttpRequest()
239+
request.open('POST', generateFilePath('richdocuments', 'ajax', 'personal.php'), true)
240+
request.setRequestHeader('Content-Type', 'application/json')
241+
request.setRequestHeader('requesttoken', OC.requestToken)
242+
request.onload = function() {
243+
if (request.status >= 200 && request.status < 400) {
244+
const response = JSON.parse(request.response)
245+
OC.msg.finishedAction('#documents-admin-msg', response)
246+
successCallback(response)
247+
} else {
248+
errorCallback(this.response)
249+
}
250+
}
251+
request.onerror = function() {
252+
errorCallback(this.response)
253+
}
254+
request.send(JSON.stringify(data))
255+
},
256+
},
257+
}
258+
</script>
259+
260+
<style scoped>
261+
.template-folder-settings {
262+
display: flex;
263+
align-items: center;
264+
gap: 1rem;
265+
margin-bottom: 2rem;
266+
}
267+
.template-input-wrapper {
268+
width: 300px;
269+
flex-shrink: 0;
270+
}
271+
.input-wrapper {
272+
display: flex;
273+
flex-direction: column;
274+
gap: 1rem;
275+
}
276+
.zotero-section p {
277+
margin-top: 5px !important;
278+
}
279+
.zotero-inline {
280+
display: flex;
281+
align-items: center;
282+
gap: 1rem;
283+
}
284+
.doc_sign_head {
285+
padding-top: 10px;
286+
padding-bottom: 5px;
287+
}
288+
.msg {
289+
display: inline-block;
290+
margin-bottom: 1rem;
291+
color: var(--color-warning);
292+
}
293+
.icon-folder::before {
294+
content: "\1F4C1";
295+
}
296+
</style>

0 commit comments

Comments
 (0)