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>