Skip to content

Commit 7cc9043

Browse files
authored
Merge pull request #4388 from codewithvk/private/codewithvk/user_settings_vue_migration
Refactor: Move personal user settings page to Vue
2 parents 9f287ad + 3572c8b commit 7cc9043

File tree

4 files changed

+390
-212
lines changed

4 files changed

+390
-212
lines changed

src/components/DocSigningField.vue

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

0 commit comments

Comments
 (0)