user_settings.vue (22828B)
1 <template>
2 <div class="settings panel panel-default">
3 <div class="panel-heading">
4 <div class="title">
5 {{ $t('settings.user_settings') }}
6 </div>
7 <transition name="fade">
8 <template v-if="currentSaveStateNotice">
9 <div
10 v-if="currentSaveStateNotice.error"
11 class="alert error"
12 @click.prevent
13 >
14 {{ $t('settings.saving_err') }}
15 </div>
16
17 <div
18 v-if="!currentSaveStateNotice.error"
19 class="alert transparent"
20 @click.prevent
21 >
22 {{ $t('settings.saving_ok') }}
23 </div>
24 </template>
25 </transition>
26 </div>
27 <div class="panel-body profile-edit">
28 <tab-switcher>
29 <div :label="$t('settings.profile_tab')">
30 <div class="setting-item">
31 <h2>{{ $t('settings.name_bio') }}</h2>
32 <p>{{ $t('settings.name') }}</p>
33 <EmojiInput
34 v-model="newName"
35 enable-emoji-picker
36 :suggest="emojiSuggestor"
37 >
38 <input
39 id="username"
40 v-model="newName"
41 classname="name-changer"
42 >
43 </EmojiInput>
44 <p>{{ $t('settings.bio') }}</p>
45 <EmojiInput
46 v-model="newBio"
47 enable-emoji-picker
48 :suggest="emojiUserSuggestor"
49 >
50 <textarea
51 v-model="newBio"
52 classname="bio"
53 />
54 </EmojiInput>
55 <p>
56 <Checkbox v-model="newLocked">
57 {{ $t('settings.lock_account_description') }}
58 </Checkbox>
59 </p>
60 <div>
61 <label for="default-vis">{{ $t('settings.default_vis') }}</label>
62 <div
63 id="default-vis"
64 class="visibility-tray"
65 >
66 <scope-selector
67 :show-all="true"
68 :user-default="newDefaultScope"
69 :initial-scope="newDefaultScope"
70 :on-scope-change="changeVis"
71 />
72 </div>
73 </div>
74 <p>
75 <Checkbox v-model="newNoRichText">
76 {{ $t('settings.no_rich_text_description') }}
77 </Checkbox>
78 </p>
79 <p>
80 <Checkbox v-model="hideFollows">
81 {{ $t('settings.hide_follows_description') }}
82 </Checkbox>
83 </p>
84 <p class="setting-subitem">
85 <Checkbox
86 v-model="hideFollowsCount"
87 :disabled="!hideFollows"
88 >
89 {{ $t('settings.hide_follows_count_description') }}
90 </Checkbox>
91 </p>
92 <p>
93 <Checkbox v-model="hideFollowers">
94 {{ $t('settings.hide_followers_description') }}
95 </Checkbox>
96 </p>
97 <p class="setting-subitem">
98 <Checkbox
99 v-model="hideFollowersCount"
100 :disabled="!hideFollowers"
101 >
102 {{ $t('settings.hide_followers_count_description') }}
103 </Checkbox>
104 </p>
105 <p>
106 <Checkbox v-model="allowFollowingMove">
107 {{ $t('settings.allow_following_move') }}
108 </Checkbox>
109 </p>
110 <p v-if="role === 'admin' || role === 'moderator'">
111 <Checkbox v-model="showRole">
112 <template v-if="role === 'admin'">
113 {{ $t('settings.show_admin_badge') }}
114 </template>
115 <template v-if="role === 'moderator'">
116 {{ $t('settings.show_moderator_badge') }}
117 </template>
118 </Checkbox>
119 </p>
120 <p>
121 <Checkbox v-model="discoverable">
122 {{ $t('settings.discoverable') }}
123 </Checkbox>
124 </p>
125 <button
126 :disabled="newName && newName.length === 0"
127 class="btn btn-default"
128 @click="updateProfile"
129 >
130 {{ $t('general.submit') }}
131 </button>
132 </div>
133 <div class="setting-item">
134 <h2>{{ $t('settings.avatar') }}</h2>
135 <p class="visibility-notice">
136 {{ $t('settings.avatar_size_instruction') }}
137 </p>
138 <p>{{ $t('settings.current_avatar') }}</p>
139 <img
140 :src="user.profile_image_url_original"
141 class="current-avatar"
142 >
143 <p>{{ $t('settings.set_new_avatar') }}</p>
144 <button
145 v-show="pickAvatarBtnVisible"
146 id="pick-avatar"
147 class="btn"
148 type="button"
149 >
150 {{ $t('settings.upload_a_photo') }}
151 </button>
152 <image-cropper
153 trigger="#pick-avatar"
154 :submit-handler="submitAvatar"
155 @open="pickAvatarBtnVisible=false"
156 @close="pickAvatarBtnVisible=true"
157 />
158 </div>
159 <div class="setting-item">
160 <h2>{{ $t('settings.profile_banner') }}</h2>
161 <p>{{ $t('settings.current_profile_banner') }}</p>
162 <img
163 :src="user.cover_photo"
164 class="banner"
165 >
166 <p>{{ $t('settings.set_new_profile_banner') }}</p>
167 <img
168 v-if="bannerPreview"
169 class="banner"
170 :src="bannerPreview"
171 >
172 <div>
173 <input
174 type="file"
175 @change="uploadFile('banner', $event)"
176 >
177 </div>
178 <i
179 v-if="bannerUploading"
180 class=" icon-spin4 animate-spin uploading"
181 />
182 <button
183 v-else-if="bannerPreview"
184 class="btn btn-default"
185 @click="submitBanner"
186 >
187 {{ $t('general.submit') }}
188 </button>
189 <div
190 v-if="bannerUploadError"
191 class="alert error"
192 >
193 Error: {{ bannerUploadError }}
194 <i
195 class="button-icon icon-cancel"
196 @click="clearUploadError('banner')"
197 />
198 </div>
199 </div>
200 <div class="setting-item">
201 <h2>{{ $t('settings.profile_background') }}</h2>
202 <p>{{ $t('settings.set_new_profile_background') }}</p>
203 <img
204 v-if="backgroundPreview"
205 class="bg"
206 :src="backgroundPreview"
207 >
208 <div>
209 <input
210 type="file"
211 @change="uploadFile('background', $event)"
212 >
213 </div>
214 <i
215 v-if="backgroundUploading"
216 class=" icon-spin4 animate-spin uploading"
217 />
218 <button
219 v-else-if="backgroundPreview"
220 class="btn btn-default"
221 @click="submitBg"
222 >
223 {{ $t('general.submit') }}
224 </button>
225 <div
226 v-if="backgroundUploadError"
227 class="alert error"
228 >
229 Error: {{ backgroundUploadError }}
230 <i
231 class="button-icon icon-cancel"
232 @click="clearUploadError('background')"
233 />
234 </div>
235 </div>
236 </div>
237
238 <div :label="$t('settings.security_tab')">
239 <div class="setting-item">
240 <h2>{{ $t('settings.change_email') }}</h2>
241 <div>
242 <p>{{ $t('settings.new_email') }}</p>
243 <input
244 v-model="newEmail"
245 type="email"
246 autocomplete="email"
247 >
248 </div>
249 <div>
250 <p>{{ $t('settings.current_password') }}</p>
251 <input
252 v-model="changeEmailPassword"
253 type="password"
254 autocomplete="current-password"
255 >
256 </div>
257 <button
258 class="btn btn-default"
259 @click="changeEmail"
260 >
261 {{ $t('general.submit') }}
262 </button>
263 <p v-if="changedEmail">
264 {{ $t('settings.changed_email') }}
265 </p>
266 <template v-if="changeEmailError !== false">
267 <p>{{ $t('settings.change_email_error') }}</p>
268 <p>{{ changeEmailError }}</p>
269 </template>
270 </div>
271
272 <div class="setting-item">
273 <h2>{{ $t('settings.change_password') }}</h2>
274 <div>
275 <p>{{ $t('settings.current_password') }}</p>
276 <input
277 v-model="changePasswordInputs[0]"
278 type="password"
279 >
280 </div>
281 <div>
282 <p>{{ $t('settings.new_password') }}</p>
283 <input
284 v-model="changePasswordInputs[1]"
285 type="password"
286 >
287 </div>
288 <div>
289 <p>{{ $t('settings.confirm_new_password') }}</p>
290 <input
291 v-model="changePasswordInputs[2]"
292 type="password"
293 >
294 </div>
295 <button
296 class="btn btn-default"
297 @click="changePassword"
298 >
299 {{ $t('general.submit') }}
300 </button>
301 <p v-if="changedPassword">
302 {{ $t('settings.changed_password') }}
303 </p>
304 <p v-else-if="changePasswordError !== false">
305 {{ $t('settings.change_password_error') }}
306 </p>
307 <p v-if="changePasswordError">
308 {{ changePasswordError }}
309 </p>
310 </div>
311
312 <div class="setting-item">
313 <h2>{{ $t('settings.oauth_tokens') }}</h2>
314 <table class="oauth-tokens">
315 <thead>
316 <tr>
317 <th>{{ $t('settings.app_name') }}</th>
318 <th>{{ $t('settings.valid_until') }}</th>
319 <th />
320 </tr>
321 </thead>
322 <tbody>
323 <tr
324 v-for="oauthToken in oauthTokens"
325 :key="oauthToken.id"
326 >
327 <td>{{ oauthToken.appName }}</td>
328 <td>{{ oauthToken.validUntil }}</td>
329 <td class="actions">
330 <button
331 class="btn btn-default"
332 @click="revokeToken(oauthToken.id)"
333 >
334 {{ $t('settings.revoke_token') }}
335 </button>
336 </td>
337 </tr>
338 </tbody>
339 </table>
340 </div>
341 <mfa />
342 <div class="setting-item">
343 <h2>{{ $t('settings.delete_account') }}</h2>
344 <p v-if="!deletingAccount">
345 {{ $t('settings.delete_account_description') }}
346 </p>
347 <div v-if="deletingAccount">
348 <p>{{ $t('settings.delete_account_instructions') }}</p>
349 <p>{{ $t('login.password') }}</p>
350 <input
351 v-model="deleteAccountConfirmPasswordInput"
352 type="password"
353 >
354 <button
355 class="btn btn-default"
356 @click="deleteAccount"
357 >
358 {{ $t('settings.delete_account') }}
359 </button>
360 </div>
361 <p v-if="deleteAccountError !== false">
362 {{ $t('settings.delete_account_error') }}
363 </p>
364 <p v-if="deleteAccountError">
365 {{ deleteAccountError }}
366 </p>
367 <button
368 v-if="!deletingAccount"
369 class="btn btn-default"
370 @click="confirmDelete"
371 >
372 {{ $t('general.submit') }}
373 </button>
374 </div>
375 </div>
376
377 <div
378 v-if="pleromaBackend"
379 :label="$t('settings.notifications')"
380 >
381 <div class="setting-item">
382 <h2>{{ $t('settings.notification_setting_filters') }}</h2>
383 <div class="select-multiple">
384 <span class="label">{{ $t('settings.notification_setting') }}</span>
385 <ul class="option-list">
386 <li>
387 <Checkbox v-model="notificationSettings.follows">
388 {{ $t('settings.notification_setting_follows') }}
389 </Checkbox>
390 </li>
391 <li>
392 <Checkbox v-model="notificationSettings.followers">
393 {{ $t('settings.notification_setting_followers') }}
394 </Checkbox>
395 </li>
396 <li>
397 <Checkbox v-model="notificationSettings.non_follows">
398 {{ $t('settings.notification_setting_non_follows') }}
399 </Checkbox>
400 </li>
401 <li>
402 <Checkbox v-model="notificationSettings.non_followers">
403 {{ $t('settings.notification_setting_non_followers') }}
404 </Checkbox>
405 </li>
406 </ul>
407 </div>
408 </div>
409
410 <div class="setting-item">
411 <h2>{{ $t('settings.notification_setting_privacy') }}</h2>
412 <p>
413 <Checkbox v-model="notificationSettings.privacy_option">
414 {{ $t('settings.notification_setting_privacy_option') }}
415 </Checkbox>
416 </p>
417 </div>
418 <div class="setting-item">
419 <p>{{ $t('settings.notification_mutes') }}</p>
420 <p>{{ $t('settings.notification_blocks') }}</p>
421 <button
422 class="btn btn-default"
423 @click="updateNotificationSettings"
424 >
425 {{ $t('general.submit') }}
426 </button>
427 </div>
428 </div>
429
430 <div
431 v-if="pleromaBackend"
432 :label="$t('settings.data_import_export_tab')"
433 >
434 <div class="setting-item">
435 <h2>{{ $t('settings.follow_import') }}</h2>
436 <p>{{ $t('settings.import_followers_from_a_csv_file') }}</p>
437 <Importer
438 :submit-handler="importFollows"
439 :success-message="$t('settings.follows_imported')"
440 :error-message="$t('settings.follow_import_error')"
441 />
442 </div>
443 <div class="setting-item">
444 <h2>{{ $t('settings.follow_export') }}</h2>
445 <Exporter
446 :get-content="getFollowsContent"
447 filename="friends.csv"
448 :export-button-label="$t('settings.follow_export_button')"
449 />
450 </div>
451 <div class="setting-item">
452 <h2>{{ $t('settings.block_import') }}</h2>
453 <p>{{ $t('settings.import_blocks_from_a_csv_file') }}</p>
454 <Importer
455 :submit-handler="importBlocks"
456 :success-message="$t('settings.blocks_imported')"
457 :error-message="$t('settings.block_import_error')"
458 />
459 </div>
460 <div class="setting-item">
461 <h2>{{ $t('settings.block_export') }}</h2>
462 <Exporter
463 :get-content="getBlocksContent"
464 filename="blocks.csv"
465 :export-button-label="$t('settings.block_export_button')"
466 />
467 </div>
468 </div>
469
470 <div :label="$t('settings.blocks_tab')">
471 <div class="profile-edit-usersearch-wrapper">
472 <Autosuggest
473 :filter="filterUnblockedUsers"
474 :query="queryUserIds"
475 :placeholder="$t('settings.search_user_to_block')"
476 >
477 <BlockCard
478 slot-scope="row"
479 :user-id="row.item"
480 />
481 </Autosuggest>
482 </div>
483 <BlockList
484 :refresh="true"
485 :get-key="identity"
486 >
487 <template
488 slot="header"
489 slot-scope="{selected}"
490 >
491 <div class="profile-edit-bulk-actions">
492 <ProgressButton
493 v-if="selected.length > 0"
494 class="btn btn-default"
495 :click="() => blockUsers(selected)"
496 >
497 {{ $t('user_card.block') }}
498 <template slot="progress">
499 {{ $t('user_card.block_progress') }}
500 </template>
501 </ProgressButton>
502 <ProgressButton
503 v-if="selected.length > 0"
504 class="btn btn-default"
505 :click="() => unblockUsers(selected)"
506 >
507 {{ $t('user_card.unblock') }}
508 <template slot="progress">
509 {{ $t('user_card.unblock_progress') }}
510 </template>
511 </ProgressButton>
512 </div>
513 </template>
514 <template
515 slot="item"
516 slot-scope="{item}"
517 >
518 <BlockCard :user-id="item" />
519 </template>
520 <template slot="empty">
521 {{ $t('settings.no_blocks') }}
522 </template>
523 </BlockList>
524 </div>
525
526 <div :label="$t('settings.mutes_tab')">
527 <tab-switcher>
528 <div label="Users">
529 <div class="profile-edit-usersearch-wrapper">
530 <Autosuggest
531 :filter="filterUnMutedUsers"
532 :query="queryUserIds"
533 :placeholder="$t('settings.search_user_to_mute')"
534 >
535 <MuteCard
536 slot-scope="row"
537 :user-id="row.item"
538 />
539 </Autosuggest>
540 </div>
541 <MuteList
542 :refresh="true"
543 :get-key="identity"
544 >
545 <template
546 slot="header"
547 slot-scope="{selected}"
548 >
549 <div class="profile-edit-bulk-actions">
550 <ProgressButton
551 v-if="selected.length > 0"
552 class="btn btn-default"
553 :click="() => muteUsers(selected)"
554 >
555 {{ $t('user_card.mute') }}
556 <template slot="progress">
557 {{ $t('user_card.mute_progress') }}
558 </template>
559 </ProgressButton>
560 <ProgressButton
561 v-if="selected.length > 0"
562 class="btn btn-default"
563 :click="() => unmuteUsers(selected)"
564 >
565 {{ $t('user_card.unmute') }}
566 <template slot="progress">
567 {{ $t('user_card.unmute_progress') }}
568 </template>
569 </ProgressButton>
570 </div>
571 </template>
572 <template
573 slot="item"
574 slot-scope="{item}"
575 >
576 <MuteCard :user-id="item" />
577 </template>
578 <template slot="empty">
579 {{ $t('settings.no_mutes') }}
580 </template>
581 </MuteList>
582 </div>
583
584 <div :label="$t('settings.domain_mutes')">
585 <div class="profile-edit-domain-mute-form">
586 <input
587 v-model="newDomainToMute"
588 :placeholder="$t('settings.type_domains_to_mute')"
589 type="text"
590 @keyup.enter="muteDomain"
591 >
592 <ProgressButton
593 class="btn btn-default"
594 :click="muteDomain"
595 >
596 {{ $t('domain_mute_card.mute') }}
597 <template slot="progress">
598 {{ $t('domain_mute_card.mute_progress') }}
599 </template>
600 </ProgressButton>
601 </div>
602 <DomainMuteList
603 :refresh="true"
604 :get-key="identity"
605 >
606 <template
607 slot="header"
608 slot-scope="{selected}"
609 >
610 <div class="profile-edit-bulk-actions">
611 <ProgressButton
612 v-if="selected.length > 0"
613 class="btn btn-default"
614 :click="() => unmuteDomains(selected)"
615 >
616 {{ $t('domain_mute_card.unmute') }}
617 <template slot="progress">
618 {{ $t('domain_mute_card.unmute_progress') }}
619 </template>
620 </ProgressButton>
621 </div>
622 </template>
623 <template
624 slot="item"
625 slot-scope="{item}"
626 >
627 <DomainMuteCard :domain="item" />
628 </template>
629 <template slot="empty">
630 {{ $t('settings.no_mutes') }}
631 </template>
632 </DomainMuteList>
633 </div>
634 </tab-switcher>
635 </div>
636 </tab-switcher>
637 </div>
638 </div>
639 </template>
640
641 <script src="./user_settings.js">
642 </script>
643
644 <style lang="scss">
645 @import '../../_variables.scss';
646
647 .profile-edit {
648 .bio {
649 margin: 0;
650 }
651
652 .visibility-tray {
653 padding-top: 5px;
654 }
655
656 input[type=file] {
657 padding: 5px;
658 height: auto;
659 }
660
661 .banner {
662 max-width: 100%;
663 }
664
665 .uploading {
666 font-size: 1.5em;
667 margin: 0.25em;
668 }
669
670 .name-changer {
671 width: 100%;
672 }
673
674 .bg {
675 max-width: 100%;
676 }
677
678 .current-avatar {
679 display: block;
680 width: 150px;
681 height: 150px;
682 border-radius: $fallback--avatarRadius;
683 border-radius: var(--avatarRadius, $fallback--avatarRadius);
684 }
685
686 .oauth-tokens {
687 width: 100%;
688
689 th {
690 text-align: left;
691 }
692
693 .actions {
694 text-align: right;
695 }
696 }
697
698 &-usersearch-wrapper {
699 padding: 1em;
700 }
701
702 &-bulk-actions {
703 text-align: right;
704 padding: 0 1em;
705 min-height: 28px;
706
707 button {
708 width: 10em;
709 }
710 }
711
712 &-domain-mute-form {
713 padding: 1em;
714 display: flex;
715 flex-direction: column;
716
717 button {
718 align-self: flex-end;
719 margin-top: 1em;
720 width: 10em;
721 }
722 }
723
724 .setting-subitem {
725 margin-left: 1.75em;
726 }
727 }
728 </style>